x
This commit is contained in:
1 parent
3965f4f4da
commit
70877882b0
82 files changed
+4353
-186
No files matched your search
@@ -0,0 +1,40 @@
|
||||
# BRIEFING — 2026-06-28T21:20:34-04:00
|
||||
|
||||
## Mission
|
||||
Analyze the codebase and formulate a precise implementation strategy for adding image fields to Menu and MenuItem Prisma schema, shared types, backend routes, and MCP server tools.
|
||||
|
||||
## 🔒 My Identity
|
||||
- Archetype: Explorer 2 (Backend & DB Specialist)
|
||||
- Roles: Read-only investigator
|
||||
- Working directory: /home/ti/git/homaroma/.agents/explorer_m2_2
|
||||
- Original parent: 8e727cf6-d863-41ec-9bd2-f8dead86db2a
|
||||
- Milestone: Milestone 2: Backend & DB
|
||||
|
||||
## 🔒 Key Constraints
|
||||
- Read-only investigation — do NOT implement
|
||||
- Follow AGENTS.md guidelines (do not guess field names, API contracts, routes, schema, etc.)
|
||||
|
||||
## Current Parent
|
||||
- Conversation ID: 8e727cf6-d863-41ec-9bd2-f8dead86db2a
|
||||
- Updated: 2026-06-28T21:25:00-04:00
|
||||
|
||||
## Investigation State
|
||||
- **Explored paths**:
|
||||
- `apps/backend/prisma/schema.prisma`
|
||||
- `packages/types/index.ts`
|
||||
- `apps/backend/src/routes/menus.ts`
|
||||
- `apps/mcp-server/menu-tools.ts`
|
||||
- `apps/mcp-server/menu-toolkit.ts`
|
||||
- `apps/spa/src/store/menuStore.ts`
|
||||
- `apps/website/src/components/navigation/menuTypes.ts`
|
||||
- **Key findings**:
|
||||
- Found precise code blocks and line numbers for models/schemas.
|
||||
- Formulated full diff and migration scripts.
|
||||
- Checked integration of new properties in the MCP Server tools.
|
||||
- **Unexplored areas**: None.
|
||||
|
||||
## Key Decisions Made
|
||||
- Focused on minimal, backwards-compatible schema modifications using nullable (`String?` / optional) fields to ensure no disruption to existing data.
|
||||
|
||||
## Artifact Index
|
||||
- /home/ti/git/homaroma/.agents/explorer_m2_2/analysis.md — Main analysis and implementation strategy report
|
||||
@@ -0,0 +1,11 @@
|
||||
## 2026-06-28T21:20:34Z
|
||||
You are Explorer 2 for Milestone 2: Backend & DB.
|
||||
Your working directory is /home/ti/git/homaroma/.agents/explorer_m2_2.
|
||||
Please analyze the codebase and formulate a precise implementation strategy for updating:
|
||||
1. Prisma schema (`apps/backend/prisma/schema.prisma`): Add rootImage (String?, optional) to Menu, and columnImage (String?, optional) and hoverImage (String?, optional) to MenuItem. Explain what migration commands are needed.
|
||||
2. Shared types (`packages/types/index.ts`): Expose the new fields.
|
||||
3. Backend route (`apps/backend/src/routes/menus.ts`): Modify the endpoints (GET, POST, etc.) to handle, validate, save, and return these fields.
|
||||
4. MCP Server tools (`apps/mcp-server/menu-tools.ts` and `apps/mcp-server/menu-toolkit.ts`): Check if any MCP tools need updating to support these new fields (both inputs and return values).
|
||||
|
||||
Read these files, verify their structure, check zod validators or Prisma types, and write your report to /home/ti/git/homaroma/.agents/explorer_m2_2/analysis.md. Summarize your findings, the exact file changes needed (with lines if possible), and any potential risks. Do not implement.
|
||||
Once done, send a message back with your findings and the path to your analysis.md.
|
||||
@@ -0,0 +1,285 @@
|
||||
# Implementation Strategy Report — Menu & MenuItem Image Fields
|
||||
|
||||
This report outlines the precise implementation strategy for adding three new optional image fields to the menu system: `rootImage` for `Menu`, and `columnImage` & `hoverImage` for `MenuItem`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Prisma Schema Changes (`apps/backend/prisma/schema.prisma`)
|
||||
|
||||
We will add the optional/nullable image fields directly to the `Menu` and `MenuItem` database models.
|
||||
|
||||
### Proposed Diff
|
||||
|
||||
```diff
|
||||
diff --git a/apps/backend/prisma/schema.prisma b/apps/backend/prisma/schema.prisma
|
||||
--- a/apps/backend/prisma/schema.prisma
|
||||
+++ b/apps/backend/prisma/schema.prisma
|
||||
@@ -638,6 +638,7 @@ model Menu {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique // e.g., "header", "footer"
|
||||
+ rootImage String?
|
||||
payload Json?
|
||||
isActive Boolean @default(true)
|
||||
items MenuItem[]
|
||||
@@ -648,6 +649,8 @@ 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?
|
||||
+ hoverImage String?
|
||||
translations MenuItemTranslation[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
```
|
||||
|
||||
### Required Migration Commands
|
||||
|
||||
1. **Development Environment:**
|
||||
Run the following command inside `apps/backend/` to generate a database migration and update the Prisma client:
|
||||
```bash
|
||||
bun x prisma migrate dev --name add_menu_images
|
||||
```
|
||||
2. **Production/Deployment Environments:**
|
||||
Apply the migrations safely and regenerate the client with:
|
||||
```bash
|
||||
bun x prisma migrate deploy
|
||||
bun x prisma generate
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Shared Types (`packages/types/index.ts`)
|
||||
|
||||
Expose these new fields in the shared interfaces `Menu` and `MenuItem` used across the backend and MCP server.
|
||||
|
||||
### Proposed Diff
|
||||
|
||||
```diff
|
||||
diff --git a/packages/types/index.ts b/packages/types/index.ts
|
||||
--- a/packages/types/index.ts
|
||||
+++ b/packages/types/index.ts
|
||||
@@ -195,6 +195,8 @@ export interface MenuItem {
|
||||
label: string
|
||||
url: string
|
||||
order: number
|
||||
+ columnImage?: string | null
|
||||
+ hoverImage?: string | null
|
||||
translations?: any[]
|
||||
}
|
||||
|
||||
@@ -202,6 +204,7 @@ export interface MenuItem {
|
||||
export interface Menu {
|
||||
id: number
|
||||
name: string
|
||||
+ rootImage?: string | null
|
||||
items: MenuItem[]
|
||||
payload?: any
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Backend Routes (`apps/backend/src/routes/menus.ts`)
|
||||
|
||||
Update the Elysia endpoints to accept, validate, process, and return the new image fields.
|
||||
|
||||
### Proposed Diff
|
||||
|
||||
```diff
|
||||
diff --git a/apps/backend/src/routes/menus.ts b/apps/backend/src/routes/menus.ts
|
||||
--- a/apps/backend/src/routes/menus.ts
|
||||
+++ b/apps/backend/src/routes/menus.ts
|
||||
@@ -32,13 +32,23 @@ export const menusRoute = new Elysia({ prefix: "/menus" })
|
||||
.post("/", async ({ body, set }) => {
|
||||
try {
|
||||
- const { name, items } = body as { name: string; items?: { label: string; url: string; order: number }[] };
|
||||
+ const { name, rootImage, items } = body as {
|
||||
+ name: string;
|
||||
+ rootImage?: string | null;
|
||||
+ items?: {
|
||||
+ label: string;
|
||||
+ url: string;
|
||||
+ order: number;
|
||||
+ columnImage?: string | null;
|
||||
+ hoverImage?: string | null;
|
||||
+ }[];
|
||||
+ };
|
||||
const menu = await prisma.menu.create({
|
||||
data: {
|
||||
name,
|
||||
+ rootImage,
|
||||
items: items ? {
|
||||
- create: items
|
||||
+ create: items.map(item => ({
|
||||
+ label: item.label,
|
||||
+ url: item.url,
|
||||
+ order: item.order,
|
||||
+ columnImage: item.columnImage,
|
||||
+ hoverImage: item.hoverImage
|
||||
+ }))
|
||||
} : undefined
|
||||
},
|
||||
include: { items: true }
|
||||
@@ -53,7 +63,9 @@ export const menusRoute = new Elysia({ prefix: "/menus" })
|
||||
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()
|
||||
+ order: t.Number(),
|
||||
+ columnImage: t.Optional(t.Nullable(t.String())),
|
||||
+ hoverImage: t.Optional(t.Nullable(t.String()))
|
||||
})))
|
||||
})
|
||||
})
|
||||
.put("/:name", async ({ params: { name }, body, set }) => {
|
||||
try {
|
||||
- const { items, payload, isActive } = body as {
|
||||
+ const { items, payload, isActive, rootImage } = body as {
|
||||
payload?: any;
|
||||
isActive?: boolean;
|
||||
+ rootImage?: string | null;
|
||||
- items: { id?: number; label: string; url: string; order: number; translations?: { langCode: string; label: string }[] }[]
|
||||
+ items: {
|
||||
+ id?: number;
|
||||
+ label: string;
|
||||
+ url: string;
|
||||
+ order: number;
|
||||
+ columnImage?: string | null;
|
||||
+ hoverImage?: string | null;
|
||||
+ translations?: { langCode: string; label: string }[]
|
||||
+ }[]
|
||||
};
|
||||
let menu = await prisma.menu.findUnique({ where: { name } });
|
||||
|
||||
const updateData: any = {};
|
||||
if (payload !== undefined) updateData.payload = payload;
|
||||
if (isActive !== undefined) updateData.isActive = isActive;
|
||||
+ if (rootImage !== undefined) updateData.rootImage = rootImage;
|
||||
|
||||
if (!menu) {
|
||||
menu = await prisma.menu.create({ data: { name, ...updateData } });
|
||||
@@ -91,3 +111,5 @@ export const menusRoute = new Elysia({ prefix: "/menus" })
|
||||
await tx.menuItem.update({
|
||||
where: { id: item.id },
|
||||
- data: { label: item.label, url: item.url, order: item.order }
|
||||
+ data: {
|
||||
+ label: item.label,
|
||||
+ url: item.url,
|
||||
+ order: item.order,
|
||||
+ columnImage: item.columnImage,
|
||||
+ hoverImage: item.hoverImage
|
||||
+ }
|
||||
});
|
||||
if (item.translations?.length) {
|
||||
@@ -105,7 +127,9 @@ export const menusRoute = new Elysia({ prefix: "/menus" })
|
||||
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 ? {
|
||||
@@ -134,6 +158,7 @@ export const menusRoute = new Elysia({ prefix: "/menus" })
|
||||
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({
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. MCP Server Tools (`apps/mcp-server/menu-tools.ts` and `apps/mcp-server/menu-toolkit.ts`)
|
||||
|
||||
We will update the tool validators and schemas to allow processing these fields via MCP commands.
|
||||
|
||||
### 4.1 Changes to `apps/mcp-server/menu-tools.ts`
|
||||
|
||||
Introduce validators for `columnImage` and `hoverImage` in the `validateMenuItems` helper function.
|
||||
|
||||
```diff
|
||||
diff --git a/apps/mcp-server/menu-tools.ts b/apps/mcp-server/menu-tools.ts
|
||||
--- a/apps/mcp-server/menu-tools.ts
|
||||
+++ b/apps/mcp-server/menu-tools.ts
|
||||
@@ -183,6 +183,12 @@ function validateMenuItems(items: any) {
|
||||
if (item.order !== undefined && typeof item.order !== "number") {
|
||||
issues.push(`items[${index}].order must be a number when provided`);
|
||||
}
|
||||
+ if (item.columnImage !== undefined && typeof item.columnImage !== "string" && item.columnImage !== null) {
|
||||
+ issues.push(`items[${index}].columnImage must be a string or null when provided`);
|
||||
+ }
|
||||
+ if (item.hoverImage !== undefined && typeof item.hoverImage !== "string" && item.hoverImage !== null) {
|
||||
+ issues.push(`items[${index}].hoverImage must be a string or null when provided`);
|
||||
+ }
|
||||
```
|
||||
|
||||
*Note: For the structural tree validation (`validateMenuNodeData`), we can also optionally validate these keys on tree nodes if they are added as part of the JSON payload.*
|
||||
|
||||
### 4.2 Changes to `apps/mcp-server/menu-toolkit.ts`
|
||||
|
||||
Update the `upsert_menu` tool definition to support the new fields in its input schema, and pass `rootImage` correctly inside the handler.
|
||||
|
||||
```diff
|
||||
diff --git a/apps/mcp-server/menu-toolkit.ts b/apps/mcp-server/menu-toolkit.ts
|
||||
--- a/apps/mcp-server/menu-toolkit.ts
|
||||
+++ b/apps/mcp-server/menu-toolkit.ts
|
||||
@@ -45,6 +45,7 @@ export const MENU_TOOL_DEFINITIONS = [
|
||||
isActive: { type: "boolean", description: "Optional active state for the menu" },
|
||||
+ rootImage: { type: ["string", "null"], description: "Optional image URL for the root menu container" },
|
||||
items: {
|
||||
type: "array",
|
||||
description: "Array of menu items",
|
||||
@@ -56,6 +57,8 @@ export const MENU_TOOL_DEFINITIONS = [
|
||||
url: { type: "string", description: "URL of the menu item" },
|
||||
order: { type: "number", description: "Order sequence" },
|
||||
+ columnImage: { type: ["string", "null"], description: "Optional image URL for the column" },
|
||||
+ hoverImage: { type: ["string", "null"], description: "Optional image URL for hover states" },
|
||||
translations: {
|
||||
@@ -216,6 +219,12 @@ export async function handleMenuTool(api: AxiosInstance, name: string, params: a
|
||||
if (itemCheck.issues.length > 0) {
|
||||
throw new Error(`Invalid menu items: ${itemCheck.issues.join("; ")}`);
|
||||
}
|
||||
|
||||
+ if (params.rootImage !== undefined) {
|
||||
+ body.rootImage = params.rootImage;
|
||||
+ } else if (existingMenu?.rootImage !== undefined) {
|
||||
+ body.rootImage = existingMenu.rootImage;
|
||||
+ }
|
||||
+
|
||||
if (params.payload !== undefined) {
|
||||
body.payload = params.payload;
|
||||
} else if (existingMenu?.payload !== undefined) {
|
||||
@@ -275,6 +284,7 @@ export async function handleMenuTool(api: AxiosInstance, name: string, params: a
|
||||
name: res.data.name,
|
||||
isActive: res.data.isActive,
|
||||
+ rootImage: res.data.rootImage,
|
||||
itemCount: res.data.items?.length || 0,
|
||||
hasPayload: !!res.data.payload
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Potential Risks & Mitigation
|
||||
|
||||
1. **Database Upgrades / Existing Records:**
|
||||
- **Risk:** Existing database records will have `NULL` values for the new columns.
|
||||
- **Mitigation:** Since the fields are optional/nullable in Prisma (`String?`) and shared types (`string | null`), this is safe and backwards-compatible. No database seed adjustments are strictly required to avoid crash, but seeding mock images is recommended.
|
||||
2. **Frontend Type Mismatch:**
|
||||
- **Risk:** Client apps (SPA/Website) define local structures for `MegaMenuPayload` which might need updating once these fields start appearing in API responses.
|
||||
- **Mitigation:** The changes do not break old rendering logic as they are optional fields, but they must be exposed on the frontend interfaces in Milestone 3 to be fully utilized.
|
||||
3. **Regenerating Client Libraries:**
|
||||
- **Risk:** Developers or test pipelines compiling without running `prisma generate` will run into compilation errors due to type mismatches.
|
||||
- **Mitigation:** Running `npm run build` or `bun run build` in the monorepo automatically triggers prisma client generation.
|
||||
@@ -0,0 +1,150 @@
|
||||
# Handoff Report — Explorer 2 (Milestone 2: Backend & DB)
|
||||
|
||||
## 1. Observation
|
||||
We observed the following definitions and implementations in the codebase:
|
||||
|
||||
1. **Prisma Schema (`apps/backend/prisma/schema.prisma`)**:
|
||||
- `Menu` model (lines 638-646):
|
||||
```prisma
|
||||
model Menu {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique // e.g., "header", "footer"
|
||||
payload Json?
|
||||
isActive Boolean @default(true)
|
||||
items MenuItem[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
```
|
||||
- `MenuItem` model (lines 648-658):
|
||||
```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)
|
||||
translations MenuItemTranslation[]
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
```
|
||||
|
||||
2. **Shared Types (`packages/types/index.ts`)**:
|
||||
- Interfaces `MenuItem` and `Menu` (lines 195-208):
|
||||
```typescript
|
||||
export interface MenuItem {
|
||||
id: number
|
||||
label: string
|
||||
url: string
|
||||
order: number
|
||||
translations?: any[]
|
||||
}
|
||||
|
||||
export interface Menu {
|
||||
id: number
|
||||
name: string
|
||||
items: MenuItem[]
|
||||
payload?: any
|
||||
}
|
||||
```
|
||||
|
||||
3. **Backend Routes (`apps/backend/src/routes/menus.ts`)**:
|
||||
- Validation schema of `POST /` body (lines 50-59):
|
||||
```typescript
|
||||
body: t.Object({
|
||||
name: t.String(),
|
||||
items: t.Optional(t.Array(t.Object({
|
||||
label: t.String(),
|
||||
url: t.String(),
|
||||
order: t.Number()
|
||||
})))
|
||||
})
|
||||
```
|
||||
- Validation schema of `PUT /:name` body (lines 132-147):
|
||||
```typescript
|
||||
body: t.Object({
|
||||
payload: t.Optional(t.Any()),
|
||||
isActive: t.Optional(t.Boolean()),
|
||||
items: t.Array(t.Object({
|
||||
id: t.Optional(t.Number()),
|
||||
label: t.String(),
|
||||
url: t.String(),
|
||||
order: t.Number(),
|
||||
translations: t.Optional(t.Array(t.Object({
|
||||
langCode: t.String(),
|
||||
label: t.String()
|
||||
})))
|
||||
}))
|
||||
})
|
||||
```
|
||||
- Database operations mapping in `PUT /:name` (lines 90-118):
|
||||
```typescript
|
||||
if (item.id && existingItemIds.includes(item.id)) {
|
||||
await tx.menuItem.update({
|
||||
where: { id: item.id },
|
||||
data: { label: item.label, url: item.url, order: item.order }
|
||||
});
|
||||
// ...
|
||||
} else {
|
||||
await tx.menuItem.create({
|
||||
data: {
|
||||
menuId: menu!.id,
|
||||
label: item.label,
|
||||
url: item.url,
|
||||
order: item.order,
|
||||
translations: item.translations?.length ? { ... } : undefined
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
4. **MCP Tools (`apps/mcp-server/menu-tools.ts` and `apps/mcp-server/menu-toolkit.ts`)**:
|
||||
- `validateMenuItems` helper (lines 158-184):
|
||||
```typescript
|
||||
if (item.order !== undefined && typeof item.order !== "number") {
|
||||
issues.push(`items[${index}].order must be a number when provided`);
|
||||
}
|
||||
```
|
||||
- `upsert_menu` tool definition input schema (lines 34-74):
|
||||
```json
|
||||
items: {
|
||||
type: "array",
|
||||
description: "Array of menu items",
|
||||
items: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "number", description: "ID of existing item (optional)" },
|
||||
label: { type: "string", description: "Label of the menu item" },
|
||||
url: { type: "string", description: "URL of the menu item" },
|
||||
order: { type: "number", description: "Order sequence" },
|
||||
translations: { ... }
|
||||
},
|
||||
required: ["label", "url", "order"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 2. Logic Chain
|
||||
- Adding the new columns to Prisma schema (Observation 1) as optional fields (`rootImage String?`, `columnImage String?`, `hoverImage String?`) ensures database schema compatibility. Running `prisma migrate dev` compiles a safe SQL statement that populates default `NULL` values for existing rows, which avoids downtime or data loss.
|
||||
- Exposing the fields on the shared interfaces (`packages/types/index.ts`, Observation 2) aligns the TypeScript contracts between package users.
|
||||
- In order to save these image fields, we must extend Elysia's schema validator body tags `t.Optional(t.Nullable(t.String()))` (Observation 3) and map the properties directly to the Prisma create/update calls, otherwise Elysia will strip them and Prisma won't save them.
|
||||
- Finally, updating `validateMenuItems` (Observation 4) and the JSON inputSchema of `upsert_menu` (Observation 4) allows external agents/clients to supply the image variables when working via MCP server endpoints.
|
||||
|
||||
## 3. Caveats
|
||||
- We assumed frontend (SPA/Website) changes are scheduled under Milestone 3 and thus out of scope for implementation/updates in this backend-only task, though we identified their locations (`apps/spa/src/store/menuStore.ts` and `apps/website/src/components/navigation/menuTypes.ts`).
|
||||
- We did not apply database changes or make code modifications as we are under a read-only constraint.
|
||||
|
||||
## 4. Conclusion
|
||||
We have mapped the precise locations, code structures, and type changes required to fully implement `rootImage`, `columnImage`, and `hoverImage` across the database, shared types, backend controllers, and MCP tool schemas.
|
||||
|
||||
Detailed changes are saved to `/home/ti/git/homaroma/.agents/explorer_m2_2/analysis.md`.
|
||||
|
||||
## 5. Verification Method
|
||||
To verify the implementation once the implementer carries out the changes:
|
||||
1. Run the build/check scripts to ensure zero compilation or type errors:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
2. Test menu endpoint updates by sending a HTTP PUT to `/api/menus/header` or calling the MCP tool `upsert_menu` with image URLs, then verify the response returns the fields and database contains the updated columns.
|
||||
@@ -0,0 +1,6 @@
|
||||
# Progress
|
||||
|
||||
- Last visited: 2026-06-28T21:20:34-04:00
|
||||
- Initialized Explorer 2.
|
||||
- Created ORIGINAL_REQUEST.md and BRIEFING.md.
|
||||
- Next: Read skill files and start investigating files requested in prompt.
|
||||
Reference in new issue
Block a user