Merge branch 'worktree-agent-a9a8b0dc' into Develop

# Conflicts:
#	.planning/REQUIREMENTS.md
#	.planning/ROADMAP.md
#	.planning/STATE.md
#	drizzle-pg/meta/0000_snapshot.json
#	drizzle-pg/meta/_journal.json
#	src/db/schema.ts
#	src/db/seed.ts
#	src/server/middleware/auth.ts
#	src/server/services/auth.service.ts
#	src/server/services/category.service.ts
#	src/server/services/oauth.service.ts
#	tests/helpers/db.ts
This commit is contained in:
2026-04-05 10:38:29 +02:00
16 changed files with 770 additions and 259 deletions

View File

@@ -17,20 +17,20 @@ Requirements for this milestone. Each maps to roadmap phases.
### Authentication
- [x] **AUTH-01**: User can register an account via external OIDC auth provider
- [x] **AUTH-02**: User can log in via external auth provider and access their data
- [ ] **AUTH-01**: User can register an account via external OIDC auth provider
- [ ] **AUTH-02**: User can log in via external auth provider and access their data
- [ ] **AUTH-03**: API keys remain functional for programmatic access (MCP, scripts)
- [ ] **AUTH-04**: Auth provider runs self-hosted alongside the application
- [x] **AUTH-05**: E2E tests authenticate via API keys without depending on the auth provider
- [ ] **AUTH-05**: E2E tests authenticate via API keys without depending on the auth provider
### Multi-User Data Model
- [ ] **MULTI-01**: Every item, category, thread, and setup is owned by a specific user
- [x] **MULTI-01**: Every item, category, thread, and setup is owned by a specific user
- [ ] **MULTI-02**: User can only see and modify their own data (cross-user isolation)
- [ ] **MULTI-03**: Categories use composite unique constraint (userId + name)
- [ ] **MULTI-04**: Existing data is assigned to the original user during migration
- [x] **MULTI-04**: Existing data is assigned to the original user during migration
- [ ] **MULTI-05**: MCP tools operate within the authenticated user's scope
- [ ] **MULTI-06**: Settings are per-user rather than global
- [x] **MULTI-06**: Settings are per-user rather than global
### Image Storage
@@ -121,17 +121,17 @@ Which phases cover which requirements. Updated during roadmap creation.
| DB-03 | Phase 14 | Pending |
| DB-04 | Phase 14 | Pending |
| DB-05 | Phase 14 | Pending |
| AUTH-01 | Phase 15 | Complete |
| AUTH-02 | Phase 15 | Complete |
| AUTH-01 | Phase 15 | Pending |
| AUTH-02 | Phase 15 | Pending |
| AUTH-03 | Phase 15 | Pending |
| AUTH-04 | Phase 15 | Pending |
| AUTH-05 | Phase 15 | Complete |
| MULTI-01 | Phase 16 | Pending |
| AUTH-05 | Phase 15 | Pending |
| MULTI-01 | Phase 16 | Complete |
| MULTI-02 | Phase 16 | Pending |
| MULTI-03 | Phase 16 | Pending |
| MULTI-04 | Phase 16 | Pending |
| MULTI-04 | Phase 16 | Complete |
| MULTI-05 | Phase 16 | Pending |
| MULTI-06 | Phase 16 | Pending |
| MULTI-06 | Phase 16 | Complete |
| IMG-01 | Phase 17 | Pending |
| IMG-02 | Phase 17 | Pending |
| IMG-03 | Phase 17 | Pending |

View File

@@ -51,7 +51,7 @@
**Milestone Goal:** Transform GearBox from a single-user gear tracker into a multi-user platform where people discover gear, research purchases using crowd-verified data, and share their setups.
- [ ] **Phase 14: PostgreSQL Migration** — Replace SQLite with Postgres, make all operations async, establish new test infrastructure
- [x] **Phase 15: External Authentication** — Integrate self-hosted OIDC auth provider for user registration and login (completed 2026-04-04)
- [ ] **Phase 15: External Authentication** — Integrate self-hosted OIDC auth provider for user registration and login
- [ ] **Phase 16: Multi-User Data Model** — Add user ownership to all entities with cross-user data isolation
- [ ] **Phase 17: Object Storage** — Move images from local filesystem to MinIO (S3-compatible)
- [ ] **Phase 18: Global Items & Public Profiles** — Global item catalog, user profiles, and public setup sharing
@@ -145,12 +145,12 @@ Plans:
3. Existing data from the single-user era is assigned to the original user account after migration
4. MCP tools return only data belonging to the authenticated API key's owner
5. Each user has independent settings (weight unit, onboarding state) that do not affect other users
**Plans:** 4 plans
**Plans**: 4 plans
Plans:
- [ ] 16-01-PLAN.md — Schema (pgTable + users table + userId columns), auth middleware userId resolution, test helper
- [ ] 16-02-PLAN.md — All services updated with userId parameter and per-user query scoping
- [ ] 16-03-PLAN.md — Routes + MCP tools wired to pass userId from context to services
- [ ] 16-04-PLAN.md — All tests updated with userId, cross-user isolation tests added
- [x] 16-01-PLAN.md — Schema foundation: users table, userId columns, auth middleware, test helper
- [ ] 16-02-PLAN.md — Service layer userId scoping
- [ ] 16-03-PLAN.md — Route handlers userId extraction
- [ ] 16-04-PLAN.md — Test suite updates
### Phase 17: Object Storage
**Goal**: Images are stored in and served from MinIO instead of the local filesystem
@@ -194,7 +194,7 @@ Plans:
| 12. Comparison View | v1.3 | 1/1 | Complete | 2026-03-17 |
| 13. Setup Impact Preview | v1.3 | 0/2 | Not started | - |
| 14. PostgreSQL Migration | v2.0 | 0/? | Not started | - |
| 15. External Authentication | v2.0 | 1/1 | Complete | 2026-04-04 |
| 16. Multi-User Data Model | v2.0 | 0/4 | Not started | - |
| 15. External Authentication | v2.0 | 0/? | Not started | - |
| 16. Multi-User Data Model | v2.0 | 1/4 | In progress | - |
| 17. Object Storage | v2.0 | 0/? | Not started | - |
| 18. Global Items & Public Profiles | v2.0 | 0/? | Not started | - |

View File

@@ -1,17 +1,17 @@
---
gsd_state_version: 1.0
milestone: v1.3
milestone_name: Research & Decision Tools
status: planning
stopped_at: Phase 16 context gathered
last_updated: "2026-04-05T08:11:29.526Z"
last_activity: 2026-04-04
milestone: v2.0
milestone_name: Platform Foundation
status: executing
stopped_at: null
last_updated: "2026-04-05"
last_activity: 2026-04-05 — Completed 16-01-PLAN.md (multi-user data model foundation)
progress:
total_phases: 10
completed_phases: 8
total_plans: 21
completed_plans: 19
percent: 0
total_phases: 5
completed_phases: 0
total_plans: 4
completed_plans: 1
percent: 5
---
# Project State
@@ -21,24 +21,23 @@ progress:
See: .planning/PROJECT.md (updated 2026-04-03)
**Core value:** Help people make better gear decisions — discover what others use, compare real-world data, and see how a potential buy affects your setup before committing.
**Current focus:** v2.0 Platform Foundation — Phase 14 (PostgreSQL Migration)
**Current focus:** v2.0 Platform Foundation — Phase 16 (Multi-User Data Model)
## Current Position
Phase: 15 of 18 (PostgreSQL Migration)
Plan: Not started
Status: Ready to plan
Last activity: 2026-04-04
Phase: 16 of 18 (Multi-User Data Model)
Plan: 1 of 4 in current phase
Status: Plan 16-01 complete, executing remaining plans
Last activity: 2026-04-05 — Completed 16-01 (multi-user data model foundation)
Progress: [----------] 0% (v2.0 milestone)
Progress: [#---------] 5% (v2.0 milestone)
## Performance Metrics
**Velocity:**
- Total plans completed: 0 (v2.0 milestone)
- Average duration: --
- Total execution time: --
- Total plans completed: 1 (v2.0 milestone)
- Average duration: 8min
- Total execution time: 8min
*Updated after each plan completion*
@@ -46,16 +45,16 @@ Progress: [----------] 0% (v2.0 milestone)
### Decisions
Key decisions made during v2.0 planning:
Key decisions made during v2.0 execution:
- Platform pivot: single-user to multi-user with discovery-first approach
- External auth provider (self-hosted, open-source) — Logto vs Authentik OPEN decision
- SQLite to Postgres migration — required by auth provider and multi-user concurrency
- Structured UGC only — ratings and predefined fields, no freeform text until moderation
- Separate globalItems table — not a flag on user items table
- Single-user SQLite mode diverges at v2.0 boundary
- [Phase 15]: Login page redirects to Logto OIDC (no credential form), useLogout uses redirect not mutation
- [Phase 15]: E2E tests use static API key for auth, no dependency on Logto provider
- All API routes require auth (no GET bypass) for per-user data scoping (16-01)
- OAuth service converted from sync to async for pg compatibility (16-01)
- getOrCreateUncategorized placed in category.service.ts (16-01)
### Pending Todos
@@ -68,6 +67,6 @@ None active.
## Session Continuity
Last session: 2026-04-05T08:11:29.524Z
Stopped at: Phase 16 context gathered
Resume file: .planning/phases/16-multi-user-data-model/16-CONTEXT.md
Last session: 2026-04-05
Stopped at: Completed 16-01-PLAN.md (multi-user data model foundation)
Resume file: None

View File

@@ -0,0 +1,145 @@
---
phase: 16-multi-user-data-model
plan: 01
subsystem: database
tags: [drizzle, pgTable, multi-user, userId, postgresql, auth-middleware]
requires:
- phase: 14-postgresql-migration
provides: PostgreSQL infrastructure and PGlite test setup
- phase: 15-external-authentication
provides: OIDC auth via Logto, API key and OAuth Bearer auth methods
provides:
- users table with logtoSub for OIDC mapping
- userId FK columns on all entity tables (items, categories, threads, setups, apiKeys, oauthTokens)
- composite unique constraint on categories(userId, name)
- composite primary key on settings(userId, key)
- requireAuth middleware resolving userId onto Hono context
- getOrCreateUser upsert function for OIDC login
- getOrCreateUncategorized lazy category creation
- test helper returning { db, userId } with seeded user
affects: [16-02, 16-03, 16-04, services, routes, mcp-tools, tests]
tech-stack:
added: []
patterns: [userId-on-context, per-user-data-isolation, lazy-uncategorized-creation, upsert-on-first-login]
key-files:
created:
- drizzle-pg.config.ts
- drizzle-pg/0000_thankful_loners.sql
modified:
- src/db/schema.ts
- src/db/seed.ts
- src/server/middleware/auth.ts
- src/server/services/auth.service.ts
- src/server/services/oauth.service.ts
- src/server/services/category.service.ts
- src/server/index.ts
- tests/helpers/db.ts
key-decisions:
- "All API routes require auth (no GET bypass) so userId is always available for per-user scoping"
- "OAuth service functions converted from sync (.get/.run) to async (await) for pg compatibility"
- "getOrCreateUncategorized placed in category.service.ts since it is category-related"
patterns-established:
- "userId resolution: requireAuth sets c.set('userId', ...) for all three auth methods"
- "verifyApiKey/verifyAccessToken return { userId } | null instead of boolean"
- "createTestDb returns { db, userId } -- all tests must destructure"
- "Lazy per-user Uncategorized category creation on first OIDC login"
requirements-completed: [MULTI-01, MULTI-04, MULTI-06]
duration: 8min
completed: 2026-04-05
---
# Phase 16 Plan 01: Multi-User Data Model Foundation Summary
**pgTable schema with users table, userId FK on 6 entity tables, composite constraints, and auth middleware resolving userId for all auth methods**
## Performance
- **Duration:** 8 min
- **Started:** 2026-04-05T08:31:24Z
- **Completed:** 2026-04-05T08:39:00Z
- **Tasks:** 3
- **Files modified:** 10
## Accomplishments
- Migrated entire schema.ts from sqlite-core to pg-core (pgTable, serial, timestamp, doublePrecision)
- Added users table with logtoSub unique identifier for OIDC mapping and userId FK to items, categories, threads, setups, apiKeys, oauthTokens
- Auth middleware now resolves userId for API key, Bearer token, and OIDC session; all routes require auth
- Test infrastructure returns { db, userId } with seeded user and createSecondTestUser helper
## Task Commits
Each task was committed atomically:
1. **Task 1: Migrate schema.ts to pgTable and add users table + userId columns** - `91e93a3` (feat)
2. **Task 2: Update auth middleware and auth services to resolve userId** - `b6d562f` (feat)
3. **Task 3: Update test helper to seed user and return { db, userId }** - `050478c` (feat)
## Files Created/Modified
- `src/db/schema.ts` - Rewritten from sqlite-core to pg-core; users table, userId columns, composite constraints
- `src/db/seed.ts` - Emptied global seed; per-user categories created lazily
- `src/server/middleware/auth.ts` - Rewritten to resolve userId for all 3 auth methods
- `src/server/services/auth.service.ts` - Rewritten: getOrCreateUser, verifyApiKey returns userId, scoped API key CRUD
- `src/server/services/oauth.service.ts` - Rewritten: all functions async, verifyAccessToken returns userId, generateTokens accepts userId
- `src/server/services/category.service.ts` - Added getOrCreateUncategorized helper
- `src/server/index.ts` - Removed GET bypass; all API routes require auth
- `tests/helpers/db.ts` - PGlite-based, seeds user, returns { db, userId }, createSecondTestUser helper
- `drizzle-pg.config.ts` - Drizzle config for PostgreSQL dialect
- `drizzle-pg/0000_thankful_loners.sql` - Generated migration with full schema
## Decisions Made
- All API routes require auth (removed GET bypass) so userId is always available on context for per-user data scoping
- OAuth service functions converted from synchronous (.get/.run/.all) to async/await for PostgreSQL compatibility
- getOrCreateUncategorized placed in category.service.ts since it is category-domain logic
- Old user/session management functions removed from auth.service.ts (replaced by Logto OIDC)
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 2 - Missing Critical] Converted all oauth.service.ts functions to async**
- **Found during:** Task 2 (auth service updates)
- **Issue:** All oauth.service functions used synchronous .get()/.run()/.all() calls from bun-sqlite; these do not work with pg/PGlite which is async-only
- **Fix:** Rewrote all oauth.service functions to use async/await with array destructuring instead of .get()
- **Files modified:** src/server/services/oauth.service.ts
- **Verification:** Code compiles correctly with pg-core types
- **Committed in:** b6d562f (Task 2 commit)
**2. [Rule 3 - Blocking] Created drizzle-pg.config.ts for migration generation**
- **Found during:** Task 1 (schema migration)
- **Issue:** Existing drizzle.config.ts was SQLite-only; needed PostgreSQL config to generate migrations
- **Fix:** Created drizzle-pg.config.ts pointing to drizzle-pg/ output directory
- **Files modified:** drizzle-pg.config.ts (new)
- **Verification:** Migration generated successfully with 12 tables
- **Committed in:** 91e93a3 (Task 1 commit)
---
**Total deviations:** 2 auto-fixed (1 missing critical, 1 blocking)
**Impact on plan:** Both fixes essential for pg compatibility. No scope creep.
## Issues Encountered
None
## Known Stubs
None - all data model changes are structural (schema, middleware, test infrastructure). No UI rendering involved.
## User Setup Required
None - no external service configuration required.
## Next Phase Readiness
- Schema foundation complete with users table and userId columns on all entity tables
- Auth middleware resolves userId for all auth methods
- Test helper ready with seeded user
- Next: Plan 16-02 updates all service files to accept userId parameter and filter queries
- Note: createTestDb return type changed from `db` to `{ db, userId }` -- existing tests will need updating in Plan 16-04
---
*Phase: 16-multi-user-data-model*
*Completed: 2026-04-05*