Files
wekan/models/lib/restLabel.js
T
Lauri Ojansivu 0e9aed5a4b Fix #5510: adding a board label via the REST API no longer errors/hangs
PUT /api/boards/:boardId/labels only wrote an HTTP response inside
`if (hasOwnProperty(req.body, "label"))` and read req.body.label.color/.name
unconditionally. Two consequences:

- A body without a `label` key fell through the `if` and NEVER sent a response,
  so the request hung until the client timed out (the reported "timeout").
- A body where `label` was a bare string (e.g. { "label": "labelName" }, the
  exact reported payload) produced name === undefined / color === undefined and
  pushed a schema-invalid label via Boards.direct, returning 200 for garbage.
- The catch returned { code: 200, data: error }, masking real failures as
  success (same anti-pattern already fixed for the board create/delete handlers).

The handler now returns a 404 for an unknown board, delegates input handling to
a new pure, Meteor-free helper models/lib/restLabel.js (buildBoardLabel), and
always sends a JSON response: 2xx for a well-formed create (documented contract
unchanged), 400 with a clear message for bad input, or the real error status in
the catch. buildBoardLabel accepts { label: { name, color } } (color defaulted
when absent, rejected when unknown) and, for convenience, a bare string taken as
the label name. Added tests/restLabel.test.cjs covering these cases.
2026-07-09 02:14:07 +03:00

82 lines
3.1 KiB
JavaScript

'use strict';
// Pure helper for the PUT /api/boards/:boardId/labels REST endpoint. Extracted so
// the input validation / label-doc building is unit-testable in plain Node without
// Meteor (mirrors models/lib/boardSortReorder.js and models/lib/reconcileBoardTeamMembers.js).
//
// #5510 (adding a board label via the REST API fails with an internal server error /
// timeout): the old handler read req.body.label.color / .name unconditionally and only
// sent a response inside `if (hasOwnProperty(req.body, 'label'))`. So:
// - a body WITHOUT a `label` key fell through and NEVER wrote an HTTP response, so the
// request hung until the client timed out (the reported "timeout"); and
// - a body where `label` is a bare string (e.g. { "label": "labelName" }, the exact
// payload in the report) produced name === undefined and color === undefined and
// pushed a schema-invalid label, returning 200 for garbage.
//
// buildBoardLabel normalizes the accepted shapes and always yields a definite outcome:
// - { ok: true, name, color } -> caller pushes { _id, name, color }
// - { ok: false, status, error } -> caller returns that JSON error (never hangs)
//
// Accepted request bodies (all resolve to a label with a name and a valid color):
// { "label": { "name": "Bug", "color": "red" } } documented shape (unchanged)
// { "label": { "name": "Bug" } } color defaults sensibly
// { "label": "Bug" } string is taken as the name
// Default when no color is supplied. 'green' is a visible, always-allowed label color.
const DEFAULT_LABEL_COLOR = 'green';
function buildBoardLabel(rawLabel, allowedColors) {
const colors = Array.isArray(allowedColors) ? allowedColors : [];
if (rawLabel === undefined || rawLabel === null) {
return {
ok: false,
status: 400,
error:
"Missing 'label'. Send { \"label\": { \"name\": \"...\", \"color\": \"green\" } } or { \"label\": \"...\" }.",
};
}
let name;
let color;
if (typeof rawLabel === 'string') {
// { "label": "labelName" } — treat the string as the label name.
name = rawLabel;
color = undefined;
} else if (typeof rawLabel === 'object') {
name = rawLabel.name;
color = rawLabel.color;
} else {
return {
ok: false,
status: 400,
error: "'label' must be an object { name, color } or a string name.",
};
}
// name is optional in the schema; coerce a provided value to a string, else ''.
name = name === undefined || name === null ? '' : String(name);
// color is required in the schema (allowedValues LABEL_COLORS). Default when
// absent; reject an explicit but unknown color rather than pushing an invalid doc.
if (color === undefined || color === null || color === '') {
color = DEFAULT_LABEL_COLOR;
} else {
color = String(color);
if (colors.length && !colors.includes(color)) {
return {
ok: false,
status: 400,
error: `Invalid label color '${color}'. Allowed colors: ${colors.join(', ')}.`,
};
}
}
return { ok: true, name, color };
}
module.exports = {
DEFAULT_LABEL_COLOR,
buildBoardLabel,
};