Files
GearBox/.planning/phases/16-multi-user-data-model/16-CONTEXT.md

5.6 KiB

Phase 16: Multi-User Data Model - Context

Gathered: 2026-04-05 Status: Ready for planning

## Phase Boundary

Add user ownership to all user-created entities (items, categories, threads, setups, settings) and enforce complete cross-user data isolation. Every query must be scoped to the authenticated user. MCP tools operate within the authenticated user's scope.

## Implementation Decisions

User Identity Storage

  • D-01: Create a thin local users table: id (serial integer PK), logtoSub (text, unique, not null), createdAt (timestamp). Auto-created on first OIDC login via upsert.
  • D-02: All entity tables reference users.id (integer FK) — not the Logto sub string directly. Integer FKs are more efficient for joins across 6+ tables.
  • D-03: The requireAuth middleware resolves the authenticated identity to a local users.id and sets it on the Hono context (e.g., c.set("userId", userId)).

Schema Changes

  • D-04: Add userId (integer, NOT NULL, FK → users.id) column to: items, categories, threads, setups, settings, apiKeys.
  • D-05: categories: Drop global unique constraint on name. Add composite unique constraint on (userId, name). Each user gets their own "Uncategorized" default category.
  • D-06: settings: Change primary key from key alone to composite (userId, key). Each user has their own settings (weightUnit, etc.).
  • D-07: apiKeys: Add userId column so middleware can resolve which user's data an API key grants access to.
  • D-08: threadCandidates and setupItems: No userId needed — they inherit ownership through their parent thread/setup FK.

Service Layer Changes

  • D-09: Every service function that reads or writes user-owned data gains a userId parameter. All queries include where(eq(table.userId, userId)) for isolation.
  • D-10: requireAuth middleware sets userId on context. Routes extract userId from context and pass to services.

Data Migration

  • D-11: Migration script adds userId column with a temporary default, then updates all existing rows to user ID 1 (the first registered user), then removes the default and sets NOT NULL.
  • D-12: Create "Uncategorized" category per-user on first login (or lazily when needed).

MCP Tool Scoping

  • D-13: MCP tools resolve userId from the authenticated token (API key → userId lookup, or Bearer token → userId). All tool operations are scoped to that user.

Claude's Discretion

  • Exact migration SQL approach (single migration vs multi-step)
  • Whether to use Drizzle's .where() chaining or a helper function for userId scoping
  • Default category creation strategy (eager on first login vs lazy on first item creation)
  • Whether thread resolution should check that the target category belongs to the same user
  • Order of service file changes (all at once vs table-by-table)

<canonical_refs>

Canonical References

Downstream agents MUST read these before planning or implementing.

Database Schema

  • src/db/schema.ts — Current schema (no userId columns yet)
  • drizzle-pg/ — PostgreSQL migration directory

Services (all need userId parameter)

  • src/server/services/item.service.ts — Item CRUD
  • src/server/services/category.service.ts — Category CRUD + unique constraint
  • src/server/services/thread.service.ts — Thread + candidate CRUD + resolution
  • src/server/services/setup.service.ts — Setup CRUD + item sync
  • src/server/services/totals.service.ts — Aggregate queries (weight/cost totals)
  • src/server/services/csv.service.ts — CSV import/export
  • src/server/services/auth.service.ts — API key management (needs userId)

Routes (all need userId from context)

  • src/server/routes/*.ts — All route files pass userId to services

Middleware

  • src/server/middleware/auth.ts — requireAuth resolves userId onto context
  • src/server/services/auth.service.ts — API key → userId lookup

MCP

  • src/server/mcp/index.ts — MCP tool handlers need userId scoping

Tests

  • tests/services/*.test.ts — All service tests need userId in calls
  • tests/routes/*.test.ts — Route tests need userId in context
  • tests/mcp/tools.test.ts — MCP tests need userId scoping

Requirements

  • .planning/REQUIREMENTS.md — MULTI-01 through MULTI-06

</canonical_refs>

<code_context>

Existing Code Insights

Reusable Assets

  • Service DI pattern (db as first param) — extend with userId as second param
  • requireAuth middleware — extend to resolve and set userId on context
  • Drizzle eq() where clauses — same pattern, add userId condition
  • createTestDb() helper — extend to seed a test user

Established Patterns

  • Service DI: functionName(db, ...) — becomes functionName(db, userId, ...)
  • Route context: c.get("db") — add c.get("userId")
  • Async Postgres: All services already use await with Drizzle (from Phase 14)
  • Test isolation: PGlite per-test — add user seed to createTestDb()

Integration Points

  • src/server/middleware/auth.ts — Primary point for userId resolution
  • src/server/index.ts — Where middleware is applied
  • src/db/schema.ts — All table definitions need userId column
  • tests/helpers/db.ts — Test DB helper needs user seed

</code_context>

## Specific Ideas

No specific requirements — open to standard approaches for multi-tenant data isolation with Drizzle ORM.

## Deferred Ideas

None — discussion stayed within phase scope


Phase: 16-multi-user-data-model Context gathered: 2026-04-05