7.3 KiB
7.3 KiB
Review Report — M2 Backend & DB Review (v3)
Verdict: REQUEST_CHANGES
Findings
1. [Critical] Label Corruption Bug in applyMenuUpdateTranslation
- What: Existing menu node labels are corrupted when updating translation fields if the label was originally a primitive string.
- Where:
apps/mcp-server/menu-tools.ts(Line 302) - Why:
The implementation attempts to merge translation updates into the existing label object via:
If
found.node[labelKey] = { ...(found.node[labelKey] || {}), ...translations };found.node[labelKey]is a primitive string (e.g."Shop"), spreading the string in JavaScript converts it into an index-mapped object (i.e.{ "0": "S", "1": "h", "2": "o", "3": "p" }). The resulting label object becomes{ "0": "S", "1": "h", "2": "o", "3": "p", "th": "..." }, which breaks frontend label rendering. - Suggestion:
Check the type of the existing label before spreading it:
const existingLabel = found.node[labelKey]; const baseObj = typeof existingLabel === "string" ? { en: existingLabel } : (existingLabel || {}); found.node[labelKey] = { ...baseObj, ...translations };
2. [Major] Duplicate Link Auditing Logic Defect in audit_missing_pages
- What: The duplicate menu link checker in the
audit_missing_pagestool never reports duplicates. - Where:
apps/mcp-server/menu-toolkit.ts(Line 304-323) - Why:
The tool maps and converts the list of URLs using a Map to deduplicate them before performing the audit:
Because the entries in
const uniqueUrls = [...new Map(urls.map((entry) => [entry.url, entry])).values()];uniqueUrlsare already distinct, the subsequent duplication checkseenUrls.has(url)inside theuniqueUrls.forEachloop is guaranteed to always evaluate tofalse. - Suggestion:
Iterate over the raw
urlslist directly to perform the auditing and duplicate reporting:urls.forEach(({ url, label, source }) => { if (seenUrls.has(url)) { duplicates.push({ menuName: menu.name, url, label: label || url, source }); return; } seenUrls.add(url); // ... rest of the audit checks });
3. [Major] Validation Bypass in validateMenuNodeData (Leaf Link under Root)
- What: Leaf link nodes can be added directly under the root menu, bypassing the Mega Menu structure constraint.
- Where:
apps/mcp-server/menu-tools.ts(Line 128) - Why:
The function
validateMenuNodeDatarestricts leaf link insertion under the root using the check:However, inif (parentId === "root" && hasUrl && structuralKeys.length === 0)add_menu_nodeorbulk_menu_operations, theparentIdis optional and can beundefinedornull(which defaults to root insertion inapplyMenuAddNode). WhenparentIdis omitted, the checkparentId === "root"is bypassed, allowing the node to be created directly at the root level. - Suggestion:
Update the condition to match root defaulting:
if ((!parentId || parentId === "root") && hasUrl && structuralKeys.length === 0)
4. [Major] Missing Schema Validation & Potential Crash in PATCH /items/:id/translations
- What: The route has no TypeBox validation schema, making it susceptible to runtime crash errors on malformed payloads.
- Where:
apps/backend/src/routes/menus.ts(Line 183) - Why:
No schema constraints are supplied to the Elysia
.patchdefinition. The request body is directly cast viabody as any. If a request is sent with an empty or non-JSON body,payloadisnull, and evaluatingpayload.labelthrowsTypeError: Cannot read properties of null (reading 'label')which crashes the request handler with a 500 status. - Suggestion:
Define a proper Elysia TypeBox schema validator for the parameters and the body:
{ params: t.Object({ id: t.Numeric() }), body: t.Object({ label: t.Union([t.String(), t.Record(t.String(), t.String())]) }) }
5. [Major] TS Compiler Errors in Backend Routes (src/routes/menus.ts)
- What: The file contains 7 TypeScript compiler errors under strict checking mode.
- Where:
apps/backend/src/routes/menus.ts(Lines 2, 3, 32, 74) - Why:
- Lines 2 & 3: Mismatched ES imports under the
nodenextmodule resolution profile (Relative import paths need explicit file extensions...). - Lines 32 & 74: Implicit
anytype on destructured parameters (body,set, andname) due to generic type inference limitations.
- Lines 2 & 3: Mismatched ES imports under the
- Suggestion:
- Add explicit
.jsextensions to local relative imports. - Add type annotations to destructured parameter arguments in POST and PUT route handlers.
- Add explicit
6. [Major] Missing id in GET /menus/:name Fallback Payload
- What: The fallback object returned when a menu is not found violates the TypeScript
Menuinterface. - Where:
apps/backend/src/routes/menus.ts(Line 27) - Why:
If the requested menu doesn't exist, the route returns:
This object lacks the
return { name, items: [], payload: null, rootImage: null, isActive: false };id: numberproperty, which is required by theMenuinterface in@homaroma/types(packages/types/index.ts). A frontend consumer accessingmenu.idwill receiveundefined, which may lead to React key errors or TypeScript violations. - Suggestion:
Return a default fallback numeric ID (e.g.
id: 0) in the returned object:return { id: 0, name, items: [], payload: null, rootImage: null, isActive: false };
7. [Minor] Schema Validation Bypass in upsert_menu parameter mapping
- What: Malformed
itemsparameter types are silently ignored instead of throwing schema errors. - Where:
apps/mcp-server/menu-toolkit.ts(Line 217) - Why:
When the
itemsparameter is passed, the tool does:Ifconst items = params.items !== undefined ? (Array.isArray(params.items) ? params.items : []) : ...params.itemsis not an array (e.g., a string or object), the tool silently replaces it with[]rather than throwing a validation error, bypassing the schema constraints of the tool definition. - Suggestion:
Throw a validation error if
params.itemsis provided but is not an array.
8. [Minor] Snapshots Volatility
- What: Captured menu snapshots are volatile.
- Where:
apps/mcp-server/menu-tools.ts(Line 223) - Why:
Snapshots are stored in an in-memory
Mapinside the MCP server process. If the server process restarts (which happens on file saves, container restarts, or scale-downs), all captured snapshots are permanently lost. - Suggestion: Document this process-local limitation, or migrate snapshot storage to the database schema.
Verified Claims
- Prisma Schema columns successfully migrated → Verified via
bunx prisma migrate status→ PASS (Database schema is up-to-date and PostgreSQL hasrootImage,columnImage, andhoverImagecolumns). - TypeScript matches DB columns → Verified via
packages/types/index.tsfile audit → PASS (Shared TypeScript interfaces have matching columns). - Integration Tests compile and run → Verified via
bun test_menu_comprehensive.ts→ PASS (Integration checks completed successfully, but highlighted the schema validation bypass foritemsparameters).