# 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) ```prisma 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) ```prisma 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 1. Run the migration to generate and apply the schema changes in the database development environment: ```bash cd apps/backend && bunx prisma migrate dev --name add_menu_image_fields ``` *Note: Using `bunx` matches the workspace runner environment (as specified in package.json dev scripts).* 2. Prisma client will be automatically generated upon a successful migration. If not, generate manually using: ```bash 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) ```typescript 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) ```typescript 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):** ```typescript 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):** ```typescript const menu = await prisma.menu.create({ data: { name, rootImage, items: items ? { create: items } : undefined }, include: { items: true } }); ``` * **Validation Schema (around Line 50):** ```typescript 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):** ```typescript 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 `rootImage` to `updateData` (around Line 69):** ```typescript 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):** ```typescript 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):** ```typescript 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):** ```typescript 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`: ```typescript 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_menu` Input Schema:** Add `rootImage` property to top-level properties: ```json "rootImage": { "type": "string", "description": "Optional root image URL for the menu" } ``` Add `columnImage` and `hoverImage` to item properties: ```json "columnImage": { "type": "string", "description": "Optional column image URL for the menu item" }, "hoverImage": { "type": "string", "description": "Optional hover image URL for the menu item" } ``` * **`handleMenuTool` logic for `upsert_menu`:** Check and pass `rootImage` in the body payload (around Line 227): ```typescript if (params.rootImage !== undefined) { body.rootImage = params.rootImage; } else if (existingMenu?.rootImage !== undefined) { body.rootImage = existingMenu.rootImage; } ``` Update dryRun output preview (around Line 253): ```typescript preview: { name: params.name, itemCount: items.length, hasPayload: !!body.payload, isActive: body.isActive, rootImage: body.rootImage } ``` Update actual successful response payload to expose `rootImage` (around Line 273): ```typescript 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 1. **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 dev` before starting work. 2. **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 to `null` or `undefined`. Ensure frontend consumption handles `null` values gracefully (e.g. using optional chaining `menu.rootImage` and fallback components). 3. **Monorepo Dependency Syncing:** * *Risk:* If `@homaroma/types` is 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 build` or `bunx tsc`) after updating the package.