Files
2026-06-28 21:34:58 -04:00

289 lines
8.8 KiB
Markdown

# 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.