fix(photos): treat category_id 0 as uncategorized instead of storing it
Genuine product bug, found behind the adminPhotos.reference suite (which was
failing for an unrelated reason -- see below).
parseInt('0') is 0 and !isNaN(0) is true, so a '0' category_id was written
literally. photo_categories.id is an increments() column, so 0 can never be a
real category, and every read path already assumes it cannot happen: the list
mapper does `category_id || type` (0 is falsy, renders as uncategorized) and
the list filter explicitly skips '0'. The result was a filter black hole -- the
photo matches no numeric category filter, and misses the "uncategorized"
filter too because that is whereNull(). Displayed as uncategorized, reachable
by nothing.
null rather than a 400: unparseable input ('abc' -> NaN) already falls through
to null, so 400ing on '0' while silently accepting 'abc' would be incoherent,
and '0' is just the HTML <select> shape where the "none" option carries
value="0".
Fixed at all three call sites that share the branch -- PATCH /photos/:photoId,
POST /photos/bulk-update, and the upload route, where the dangling 0 was
written at creation time and the scope-validation guard
(`if (parsedCategoryId && ...)`) skipped on the falsy 0 and let it in
unvalidated. Only the PATCH one was behind the failing test; leaving the other
two would have left the bad state creatable.
The suite's 3 failures were all masked by a fixture gap, not this bug: it
stubs middleware/auth but not middleware/permissions, so requirePermission's
admin_users JOIN roles query hit tables the fixture never creates and every
request 500'd before reaching a handler. Stub it, bring the photos fixture up
to the 7 migrations it had drifted behind, and correct a stale 200 that became
202 when uploads went async in 851744c3.
Known adjacent gap, not fixed (wider than this bug): PATCH and bulk-update
accept any positive category_id with no existence or scope check, unlike the
upload route which validates event_id = X OR is_global per #500/#525 -- so a
photo can be PATCHed into another event's category.
Refs testplan REPORT.md #22 (Part 1.2.01).
This commit is contained in:
@@ -243,8 +243,11 @@ router.post('/:eventId/upload', adminAuth, requirePermission('photos.upload'), r
|
||||
}
|
||||
|
||||
// Parse category_id to number if provided (handle string values like 'individual', 'collage')
|
||||
// Same 0-is-not-a-category rule as the PATCH route below: '0' is truthy, so
|
||||
// it parsed to 0 and the scope-validation guard (`if (parsedCategoryId && ...)`)
|
||||
// then skipped on the falsy 0 and let it into the insert unvalidated.
|
||||
const rawParsed = category_id ? parseInt(category_id, 10) : NaN;
|
||||
const parsedCategoryId = !isNaN(rawParsed) ? rawParsed : null;
|
||||
const parsedCategoryId = rawParsed > 0 ? rawParsed : null;
|
||||
|
||||
// Determine photo type and category name
|
||||
let photoType = 'individual'; // default
|
||||
@@ -846,9 +849,16 @@ router.patch('/:eventId/photos/:photoId', adminAuth, requirePermission('photos.e
|
||||
// Explicitly clear category
|
||||
updateData.category_id = null;
|
||||
} else {
|
||||
// Handle numeric category IDs from photo_categories table
|
||||
// Handle numeric category IDs from photo_categories table.
|
||||
// 0 and negatives mean "no category", not category zero: photo_categories.id
|
||||
// is an increments() column so it starts at 1, and a <select> whose "none"
|
||||
// option carries value="0" is exactly how '0' reaches this route. Storing 0
|
||||
// left the photo in a black hole — the grid's category filters never match
|
||||
// it, and the "uncategorized" filter is whereNull() so it misses it too,
|
||||
// while the list mapper renders it as uncategorized because 0 is falsy.
|
||||
// NaN (unparseable input) already fell through to null and still does.
|
||||
const numericCategoryId = parseInt(category_id, 10);
|
||||
if (!isNaN(numericCategoryId)) {
|
||||
if (numericCategoryId > 0) {
|
||||
updateData.category_id = numericCategoryId;
|
||||
} else {
|
||||
updateData.category_id = null;
|
||||
@@ -1031,8 +1041,9 @@ router.post('/:eventId/photos/bulk-update', adminAuth, requirePermission('photos
|
||||
updateData.category_id = null;
|
||||
} else {
|
||||
// Handle numeric category IDs from photo_categories table
|
||||
// (0/negative mean "no category" — see the PATCH route above)
|
||||
const numericCategoryId = parseInt(updates.category_id, 10);
|
||||
if (!isNaN(numericCategoryId)) {
|
||||
if (numericCategoryId > 0) {
|
||||
updateData.category_id = numericCategoryId;
|
||||
} else {
|
||||
updateData.category_id = null;
|
||||
|
||||
Reference in New Issue
Block a user