13 KiB
13 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 16-multi-user-data-model | 03 | execute | 3 |
|
|
true |
|
|
Purpose: Routes are the HTTP boundary. They extract userId (set by requireAuth middleware in Plan 01) and pass it to services (updated in Plan 02). MCP tools are the programmatic boundary. Together they ensure every data operation is scoped to the authenticated user.
Output: All route files and MCP tools pass userId to service calls. Settings use per-user composite key.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/phases/16-multi-user-data-model/16-CONTEXT.md @.planning/phases/16-multi-user-data-model/16-RESEARCH.md @.planning/phases/16-multi-user-data-model/16-01-SUMMARY.md @.planning/phases/16-multi-user-data-model/16-02-SUMMARY.md @src/server/routes/items.ts @src/server/routes/categories.ts @src/server/routes/threads.ts @src/server/routes/setups.ts @src/server/routes/settings.ts @src/server/routes/totals.ts @src/server/routes/auth.ts @src/server/routes/images.ts @src/server/mcp/index.ts @src/server/mcp/tools/items.ts @src/server/mcp/tools/categories.ts @src/server/mcp/tools/threads.ts @src/server/mcp/tools/setups.tsRoute pattern (what every handler must do):
app.get("/", async (c) => {
const db = c.get("db");
const userId = c.get("userId");
const result = await serviceFunction(db, userId, ...);
return c.json(result);
});
MCP pattern (what must change):
// Before: createMcpServer(db: Db)
// After: createMcpServer(db: Db, userId: number)
// Before: registerItemTools(db)
// After: registerItemTools(db, userId)
Settings pattern (composite key):
// Before: eq(settings.key, key)
// After: and(eq(settings.userId, userId), eq(settings.key, key))
// Insert with onConflict must target [settings.userId, settings.key]
**items.ts**: Extract userId, pass to getAllItems(db, userId), getItemById(db, userId, id), createItem(db, userId, data), updateItem(db, userId, id, data), deleteItem(db, userId, id).
**categories.ts**: Extract userId, pass to getAllCategories(db, userId), getCategoryById(db, userId, id), createCategory(db, userId, data), updateCategory(db, userId, id, data), deleteCategory(db, userId, id).
**threads.ts**: Extract userId, pass to all thread service calls including addCandidate, updateCandidate, removeCandidate, resolveThread.
**setups.ts**: Extract userId, pass to all setup service calls including syncSetupItems.
**totals.ts**: Extract userId, pass to getTotals(db, userId) or equivalent.
**settings.ts** per D-06: This route does inline DB queries (no service file). Update to:
- GET `/:key`: Add userId to the where clause: `and(eq(settings.userId, userId), eq(settings.key, key))`
- PUT `/:key`: Update the upsert to use composite conflict target: `.onConflictDoUpdate({ target: [settings.userId, settings.key], set: { value: body.value } })` and include userId in the insert values: `.values({ userId, key, value: body.value })`
- Import `and` from `drizzle-orm` and `settings` from schema
**auth.ts**: Extract userId, pass to createApiKey(db, userId, name), listApiKeys(db, userId), deleteApiKey(db, userId, id). Auth routes that don't need userId (login, me, setup) can skip it.
**images.ts**: This route handles image uploads which don't directly involve userId scoping on the images table (images are stored by filename, not in a user-scoped table). However, if the route calls any service that now requires userId, pass it. Read the file first to determine what changes are needed.
IMPORTANT: The `Env` type annotation on each Hono app may need updating to include `userId` in the Variables type:
```typescript
type Env = { Variables: { db?: any; userId?: number } };
```
for f in src/server/routes/items.ts src/server/routes/categories.ts src/server/routes/threads.ts src/server/routes/setups.ts src/server/routes/settings.ts src/server/routes/totals.ts src/server/routes/auth.ts; do echo "$f: $(grep -c 'c.get("userId")' $f)"; done
- Every route handler in items.ts, categories.ts, threads.ts, setups.ts, totals.ts, auth.ts contains `c.get("userId")`
- Every service function call includes userId as the second argument
- settings.ts uses `and(eq(settings.userId, userId), eq(settings.key, key))` for reads
- settings.ts upsert targets `[settings.userId, settings.key]` for composite conflict
- settings.ts insert includes userId in values
- Env type includes `userId` in Variables
- No service call is missing the userId parameter
All route handlers extract userId from context and pass to every service call. Settings routes use composite key.
Task 2: Update MCP server and tool registrations with userId
src/server/mcp/index.ts, src/server/mcp/tools/items.ts, src/server/mcp/tools/categories.ts, src/server/mcp/tools/threads.ts, src/server/mcp/tools/setups.ts, src/server/mcp/resources/collection.ts
src/server/mcp/index.ts, src/server/mcp/tools/items.ts, src/server/mcp/tools/categories.ts, src/server/mcp/tools/threads.ts, src/server/mcp/tools/setups.ts, src/server/mcp/resources/collection.ts
Per D-13 and Research pitfall 5:
1. **Update `createMcpServer` signature** in `src/server/mcp/index.ts`:
Change from `createMcpServer(db: Db)` to `createMcpServer(db: Db, userId: number)`.
Pass userId to all `register*Tools` calls:
- `registerItemTools(db, userId)`
- `registerCategoryTools(db, userId)`
- `registerThreadTools(db, userId)`
- `registerSetupTools(db, userId)`
- `getCollectionSummary(db, userId)`
(registerImageTools has no db/userId dependency so leave unchanged)
2. **Update MCP auth middleware** to resolve userId:
The MCP auth middleware in `mcpRoutes.use("/*", ...)` currently calls `verifyAccessToken` and `verifyApiKey` which now return `{ userId } | null`. Store the userId and make it available to the POST handler.
Use the Hono context to pass userId, similar to the main API middleware:
```typescript
mcpRoutes.use("/*", async (c, next) => {
const db = c.get("db") ?? prodDb;
// Try Bearer token first (OAuth)
const authHeader = c.req.header("Authorization");
if (authHeader?.startsWith("Bearer ")) {
const token = authHeader.slice(7);
const result = await verifyAccessToken(db, token);
if (result) {
c.set("userId", result.userId);
return next();
}
return c.json({ error: "invalid_token" }, 401);
}
// Try API key
const apiKey = c.req.header("X-API-Key");
if (apiKey) {
const result = await verifyApiKey(db, apiKey);
if (result) {
c.set("userId", result.userId);
return next();
}
return c.json({ error: "Invalid API key" }, 401);
}
// ... rest of auth handling
});
```
3. **Update MCP POST handler** to pass userId when creating MCP server:
In the `mcpRoutes.post("/", ...)` handler, extract userId from context and pass to createMcpServer:
```typescript
const userId = c.get("userId");
const server = createMcpServer(db, userId);
```
4. **Store userId alongside transport** in the session map per Research pitfall 5:
Change `transports` map type from `Map<string, Transport>` to `Map<string, { transport: Transport, userId: number }>`.
When reusing an existing session, extract userId from the stored session data (no need to recreate MCP server -- the session was already initialized with the correct userId).
5. **Update each tool registration file** to accept and use userId:
- `src/server/mcp/tools/items.ts`: `registerItemTools(db: Db, userId: number)` -- pass userId to all item service calls
- `src/server/mcp/tools/categories.ts`: `registerCategoryTools(db: Db, userId: number)` -- pass userId to all category service calls
- `src/server/mcp/tools/threads.ts`: `registerThreadTools(db: Db, userId: number)` -- pass userId to all thread service calls
- `src/server/mcp/tools/setups.ts`: `registerSetupTools(db: Db, userId: number)` -- pass userId to all setup service calls
6. **Update `getCollectionSummary`** in `src/server/mcp/resources/collection.ts`:
Add userId parameter, scope the summary queries to the user's data only.
grep -c "userId: number" src/server/mcp/index.ts && grep "createMcpServer(db, userId)" src/server/mcp/index.ts | wc -l && grep -c "userId: number" src/server/mcp/tools/items.ts && grep -c "userId: number" src/server/mcp/tools/categories.ts && grep -c "userId: number" src/server/mcp/tools/threads.ts && grep -c "userId: number" src/server/mcp/tools/setups.ts && grep -c "c.set(\"userId\"" src/server/mcp/index.ts
- `createMcpServer` accepts `(db: Db, userId: number)` signature
- MCP auth middleware sets `c.set("userId", result.userId)` for both API key and Bearer token auth
- MCP POST handler passes userId to `createMcpServer(db, userId)`
- Transport map stores userId alongside transport
- All 4 tool registration functions accept `(db: Db, userId: number)`
- All tool handlers pass userId to service function calls
- `getCollectionSummary` accepts and uses userId
- No MCP tool calls a service function without userId
MCP server creation receives userId, all tool registrations pass userId to service calls, MCP auth middleware resolves userId from API key or Bearer token
After all tasks complete:
1. `grep -r 'c.get("userId")' src/server/routes/ | wc -l` shows userId extraction in all route files
2. `grep -r 'c.get("userId")' src/server/mcp/ | wc -l` shows userId in MCP middleware
3. `grep "createMcpServer(db, userId)" src/server/mcp/index.ts` confirms MCP userId threading
4. No service call anywhere in routes/ or mcp/ is missing the userId argument
<success_criteria>
- All route handlers extract userId from context and pass to services
- Settings routes use composite PK for per-user settings
- MCP server creation includes userId
- MCP tool registrations pass userId to all service calls
- MCP auth middleware resolves userId from API key and Bearer token
- Complete chain: middleware sets userId -> routes/MCP extract it -> services filter by it </success_criteria>