8.8 KiB
8.8 KiB
Milestone 2: Backend & DB — Menu Images Field Addition Strategy
Explorer 1 Analysis Report
This report outlines the precise implementation strategy for adding optional menu images fields (rootImage on Menu, and columnImage / hoverImage on MenuItem) across the codebase.
1. Prisma Schema Changes (apps/backend/prisma/schema.prisma)
We will add the new optional image fields to the Menu and MenuItem models.
Model Menu Updates (around Line 638)
model Menu {
id Int @id @default(autoincrement())
name String @unique // e.g., "header", "footer"
payload Json?
rootImage String? // <-- Added optional root image for the menu
isActive Boolean @default(true)
items MenuItem[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Model MenuItem Updates (around Line 648)
model MenuItem {
id Int @id @default(autoincrement())
menuId Int
menu Menu @relation(fields: [menuId], references: [id], onDelete: Cascade)
label String
url String
order Int @default(0)
columnImage String? // <-- Added optional column image URL
hoverImage String? // <-- Added optional hover image URL
translations MenuItemTranslation[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Migration Commands Required
- Run the migration to generate and apply the schema changes in the database development environment:
Note: Using
cd apps/backend && bunx prisma migrate dev --name add_menu_image_fieldsbunxmatches the workspace runner environment (as specified in package.json dev scripts). - Prisma client will be automatically generated upon a successful migration. If not, generate manually using:
cd apps/backend && bunx prisma generate
2. Shared Types Updates (packages/types/index.ts)
Modify the interfaces to expose the newly added fields.
Interface MenuItem (around Line 195)
export interface MenuItem {
id: number
label: string
url: string
order: number
columnImage?: string | null // <-- Added
hoverImage?: string | null // <-- Added
translations?: any[]
}
Interface Menu (around Line 203)
export interface Menu {
id: number
name: string
items: MenuItem[]
rootImage?: string | null // <-- Added
payload?: any
}
3. Backend Route Updates (apps/backend/src/routes/menus.ts)
Update the Elysia endpoints to handle, validate, save, and return these new fields.
1) POST / Endpoint
- Body Destructuring & Destructuring Cast (around Line 34):
const { name, rootImage, items } = body as { name: string; rootImage?: string; items?: { label: string; url: string; order: number; columnImage?: string; hoverImage?: string }[] }; - Database Creation Payload (around Line 35):
const menu = await prisma.menu.create({ data: { name, rootImage, items: items ? { create: items } : undefined }, include: { items: true } }); - Validation Schema (around Line 50):
body: t.Object({ name: t.String(), rootImage: t.Optional(t.Nullable(t.String())), items: t.Optional(t.Array(t.Object({ label: t.String(), url: t.String(), order: t.Number(), columnImage: t.Optional(t.Nullable(t.String())), hoverImage: t.Optional(t.Nullable(t.String())) }))) })
2) PUT /:name Endpoint
- Body Destructuring & Type Casting (around Line 62):
const { items, payload, isActive, rootImage } = body as { payload?: any; isActive?: boolean; rootImage?: string | null; items: { id?: number; label: string; url: string; order: number; columnImage?: string | null; hoverImage?: string | null; translations?: { langCode: string; label: string }[] }[] }; - Map
rootImagetoupdateData(around Line 69):const updateData: any = {}; if (payload !== undefined) updateData.payload = payload; if (isActive !== undefined) updateData.isActive = isActive; if (rootImage !== undefined) updateData.rootImage = rootImage; - Transaction - Item Update (around Line 90):
await tx.menuItem.update({ where: { id: item.id }, data: { label: item.label, url: item.url, order: item.order, columnImage: item.columnImage !== undefined ? item.columnImage : undefined, hoverImage: item.hoverImage !== undefined ? item.hoverImage : undefined } }); - Transaction - Item Create (around Line 105):
await tx.menuItem.create({ data: { menuId: menu!.id, label: item.label, url: item.url, order: item.order, columnImage: item.columnImage, hoverImage: item.hoverImage, translations: item.translations?.length ? { create: item.translations.map(t => ({ langCode: t.langCode, label: t.label })) } : undefined } }); - Validation Schema (around Line 133):
body: t.Object({ payload: t.Optional(t.Any()), isActive: t.Optional(t.Boolean()), rootImage: t.Optional(t.Nullable(t.String())), items: t.Array(t.Object({ id: t.Optional(t.Number()), label: t.String(), url: t.String(), order: t.Number(), columnImage: t.Optional(t.Nullable(t.String())), hoverImage: t.Optional(t.Nullable(t.String())), translations: t.Optional(t.Array(t.Object({ langCode: t.String(), label: t.String() }))) })) })
4. MCP Server Tools Updates
1) Tool Input Validation (apps/mcp-server/menu-tools.ts around Line 181)
Add input check in validateMenuItems:
if (item.columnImage !== undefined && item.columnImage !== null && typeof item.columnImage !== "string") {
issues.push(`items[${index}].columnImage must be a string when provided`);
}
if (item.hoverImage !== undefined && item.hoverImage !== null && typeof item.hoverImage !== "string") {
issues.push(`items[${index}].hoverImage must be a string when provided`);
}
2) Tool Schema and Handler (apps/mcp-server/menu-toolkit.ts)
upsert_menuInput Schema: AddrootImageproperty to top-level properties:Add"rootImage": { "type": "string", "description": "Optional root image URL for the menu" }columnImageandhoverImageto item properties:"columnImage": { "type": "string", "description": "Optional column image URL for the menu item" }, "hoverImage": { "type": "string", "description": "Optional hover image URL for the menu item" }handleMenuToollogic forupsert_menu: Check and passrootImagein the body payload (around Line 227):Update dryRun output preview (around Line 253):if (params.rootImage !== undefined) { body.rootImage = params.rootImage; } else if (existingMenu?.rootImage !== undefined) { body.rootImage = existingMenu.rootImage; }Update actual successful response payload to exposepreview: { name: params.name, itemCount: items.length, hasPayload: !!body.payload, isActive: body.isActive, rootImage: body.rootImage }rootImage(around Line 273):text: JSON.stringify( { success: true, operation: "upsert_menu", name: res.data.name, isActive: res.data.isActive, rootImage: res.data.rootImage, itemCount: res.data.items?.length || 0, hasPayload: !!res.data.payload }, null, 2 )
5. Potential Risks & Mitigation
- Migration Conflicts:
- Risk: Running migrations in environments where database instances are out of sync can lead to conflicts.
- Mitigation: Ensure other developers pull changes and run
bunx prisma migrate devbefore starting work.
- Backward Compatibility:
- Risk: Existing menus or components that do not expect nullable/optional fields might break if type checks are strictly checking properties.
- Mitigation: Use
String?/ optional properties which evaluate tonullorundefined. Ensure frontend consumption handlesnullvalues gracefully (e.g. using optional chainingmenu.rootImageand fallback components).
- Monorepo Dependency Syncing:
- Risk: If
@homaroma/typesis updated but workspace dependencies aren't built or re-linked, typescript compilation might fail on clients. - Mitigation: Run workspace-wide build or type checks (
npm run buildorbunx tsc) after updating the package.
- Risk: If