Merge branch 'worktree-agent-a7e6e4b2' into Develop

# Conflicts:
#	.planning/REQUIREMENTS.md
#	.planning/ROADMAP.md
#	.planning/STATE.md
#	drizzle/meta/_journal.json
#	src/db/schema.ts
#	src/db/seed.ts
#	src/shared/schemas.ts
#	src/shared/types.ts
This commit is contained in:
2026-04-05 13:13:26 +02:00
18 changed files with 1863 additions and 232 deletions

View File

@@ -26,32 +26,32 @@ Requirements for this milestone. Each maps to roadmap phases.
### Multi-User Data Model
- [ ] **MULTI-01**: Every item, category, thread, and setup is owned by a specific user
- [x] **MULTI-02**: User can only see and modify their own data (cross-user isolation)
- [ ] **MULTI-02**: User can only see and modify their own data (cross-user isolation)
- [ ] **MULTI-03**: Categories use composite unique constraint (userId + name)
- [x] **MULTI-04**: Existing data is assigned to the original user during migration
- [x] **MULTI-05**: MCP tools operate within the authenticated user's scope
- [ ] **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
### Image Storage
- [x] **IMG-01**: Images are stored in MinIO (S3-compatible) instead of local filesystem
- [x] **IMG-02**: Existing uploaded images are migrated to MinIO
- [x] **IMG-03**: Image upload and retrieval work through the new storage layer
- [x] **IMG-04**: Docker Compose provides MinIO for local development
- [ ] **IMG-01**: Images are stored in MinIO (S3-compatible) instead of local filesystem
- [ ] **IMG-02**: Existing uploaded images are migrated to MinIO
- [ ] **IMG-03**: Image upload and retrieval work through the new storage layer
- [ ] **IMG-04**: Docker Compose provides MinIO for local development
### Global Item Database
- [x] **GLOB-01**: A global item catalog exists with brand, model, category, manufacturer specs, and image
- [x] **GLOB-02**: Global catalog is seeded with initial items from manufacturer data
- [ ] **GLOB-03**: User can search the global catalog by name or brand
- [ ] **GLOB-04**: User can link a personal collection item to a global catalog entry
- [ ] **GLOB-05**: Global item pages show basic info and owner count
- [x] **GLOB-03**: User can search the global catalog by name or brand
- [x] **GLOB-04**: User can link a personal collection item to a global catalog entry
- [x] **GLOB-05**: Global item pages show basic info and owner count
### User Profiles & Sharing
- [x] **PROF-01**: User has a profile with display name, avatar, and bio
- [ ] **PROF-01**: User has a profile with display name, avatar, and bio
- [ ] **PROF-02**: User can view their own public profile page
- [x] **PROF-03**: User can set a setup as public or private
- [ ] **PROF-03**: User can set a setup as public or private
- [ ] **PROF-04**: Public setups are viewable by anyone without authentication
- [ ] **PROF-05**: Public profile page lists the user's public setups
@@ -127,23 +127,23 @@ Which phases cover which requirements. Updated during roadmap creation.
| AUTH-04 | Phase 15 | Pending |
| AUTH-05 | Phase 15 | Pending |
| MULTI-01 | Phase 16 | Pending |
| MULTI-02 | Phase 16 | Complete |
| MULTI-02 | Phase 16 | Pending |
| MULTI-03 | Phase 16 | Pending |
| MULTI-04 | Phase 16 | Complete |
| MULTI-05 | Phase 16 | Complete |
| MULTI-04 | Phase 16 | Pending |
| MULTI-05 | Phase 16 | Pending |
| MULTI-06 | Phase 16 | Pending |
| IMG-01 | Phase 17 | Complete |
| IMG-02 | Phase 17 | Complete |
| IMG-03 | Phase 17 | Complete |
| IMG-04 | Phase 17 | Complete |
| GLOB-01 | Phase 18 | Complete |
| GLOB-02 | Phase 18 | Complete |
| GLOB-03 | Phase 18 | Pending |
| GLOB-04 | Phase 18 | Pending |
| GLOB-05 | Phase 18 | Pending |
| PROF-01 | Phase 18 | Complete |
| IMG-01 | Phase 17 | Pending |
| IMG-02 | Phase 17 | Pending |
| IMG-03 | Phase 17 | Pending |
| IMG-04 | Phase 17 | Pending |
| GLOB-01 | Phase 18 | Complete (18-02) |
| GLOB-02 | Phase 18 | Complete (18-02) |
| GLOB-03 | Phase 18 | Complete (18-02) |
| GLOB-04 | Phase 18 | Complete (18-02) |
| GLOB-05 | Phase 18 | Complete (18-02) |
| PROF-01 | Phase 18 | Pending |
| PROF-02 | Phase 18 | Pending |
| PROF-03 | Phase 18 | Complete |
| PROF-03 | Phase 18 | Pending |
| PROF-04 | Phase 18 | Pending |
| PROF-05 | Phase 18 | Pending |

View File

@@ -52,8 +52,8 @@
- [ ] **Phase 14: PostgreSQL Migration** — Replace SQLite with Postgres, make all operations async, establish new test infrastructure
- [ ] **Phase 15: External Authentication** — Integrate self-hosted OIDC auth provider for user registration and login
- [x] **Phase 16: Multi-User Data Model** — Add user ownership to all entities with cross-user data isolation (completed 2026-04-05)
- [x] **Phase 17: Object Storage** — Move images from local filesystem to MinIO (S3-compatible) (completed 2026-04-05)
- [ ] **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
## Phase Details
@@ -145,12 +145,7 @@ 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:
- [x] 16-01-PLAN.md — Schema foundation: users table, userId columns, auth middleware, test helper
- [x] 16-02-PLAN.md — Service layer userId scoping
- [x] 16-03-PLAN.md — Route handlers userId extraction
- [ ] 16-04-PLAN.md — Test suite updates
**Plans**: TBD
### Phase 17: Object Storage
**Goal**: Images are stored in and served from MinIO instead of the local filesystem
@@ -161,11 +156,7 @@ Plans:
2. All previously uploaded images are accessible after migration to MinIO (no broken images)
3. Image URLs work correctly in all views (collection, planning, setups, comparison table)
4. Docker Compose includes MinIO for local development with no manual bucket setup required
**Plans:** 3/3 plans complete
Plans:
- [x] 17-01-PLAN.md — Storage service abstraction + Docker Compose MinIO infrastructure
- [x] 17-02-PLAN.md — Server-side image handling refactoring (routes, services, MCP tools)
- [x] 17-03-PLAN.md — Client component updates + image migration script
**Plans**: TBD
### Phase 18: Global Items & Public Profiles
**Goal**: Users can discover gear through a global catalog and share their setups publicly via profile pages
@@ -177,13 +168,7 @@ Plans:
3. A global item page shows basic info and how many users own it
4. User can edit their profile (display name, avatar, bio) and view their own public profile page
5. User can toggle a setup between public and private; public setups are viewable by anyone without logging in and appear on the owner's public profile
**Plans:** 1/5 plans executed
Plans:
- [x] 18-01-PLAN.md — Schema foundation: globalItems, itemGlobalLinks, user profile columns, setup isPublic, Zod schemas, types, seed data
- [ ] 18-02-PLAN.md — Global item backend: service (search, owner count, link/unlink), routes, seed script, tests
- [ ] 18-03-PLAN.md — Profile and sharing backend: profile service, public profile/setup routes, auth middleware updates, tests
- [ ] 18-04-PLAN.md — Global item client: catalog browse/search page, detail page, link-to-global-item UI
- [ ] 18-05-PLAN.md — Profile and sharing client: profile edit in settings, public profile page, setup visibility toggle
**Plans**: TBD
**UI hint**: yes
## Progress
@@ -205,6 +190,6 @@ Plans:
| 13. Setup Impact Preview | v1.3 | 0/2 | Not started | - |
| 14. PostgreSQL Migration | v2.0 | 0/? | Not started | - |
| 15. External Authentication | v2.0 | 0/? | Not started | - |
| 16. Multi-User Data Model | v2.0 | 2/4 | Complete | 2026-04-05 |
| 17. Object Storage | v2.0 | 3/3 | Complete | 2026-04-05 |
| 18. Global Items & Public Profiles | v2.0 | 1/5 | In Progress| |
| 16. Multi-User Data Model | v2.0 | 0/? | Not started | - |
| 17. Object Storage | v2.0 | 0/? | Not started | - |
| 18. Global Items & Public Profiles | v2.0 | 2/5 | In progress | - |

View File

@@ -1,16 +1,16 @@
---
gsd_state_version: 1.0
milestone: v1.3
milestone_name: Research & Decision Tools
milestone: v2.0
milestone_name: Platform Foundation
status: planning
stopped_at: Phase 18 context gathered
last_updated: "2026-04-05T10:34:23.345Z"
last_activity: 2026-04-05
stopped_at: null
last_updated: "2026-04-03"
last_activity: 2026-04-03 — v2.0 roadmap created (Phases 14-18)
progress:
total_phases: 12
completed_phases: 10
total_plans: 28
completed_plans: 26
total_phases: 5
completed_phases: 0
total_plans: 0
completed_plans: 0
percent: 0
---
@@ -21,28 +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 18 (Global Items & Public Profiles)
**Current focus:** v2.0 Platform Foundation — Phase 14 (PostgreSQL Migration)
## Current Position
Phase: 18 of 18 (Global Items & Public Profiles)
Plan: 1 of 5 in current phase
Plan: 2 of 5 in current phase
Status: Executing
Last activity: 2026-04-05 — Completed 18-01 schema foundations
Last activity: 2026-04-05 — Completed 18-02 global items service and routes
Progress: [==========] 100% (phases) | Plan 1/5 in Phase 18
Progress: [##--------] 20% (v2.0 milestone)
## Performance Metrics
**Velocity:**
- Total plans completed: 1 (v2.0 milestone)
- Average duration: 3min
- Total execution time: 3min
| Phase | Plan | Duration | Tasks | Files |
|-------|------|----------|-------|-------|
| 18 | 01 | 3min | 2 | 5 |
- Total plans completed: 0 (v2.0 milestone)
- Average duration: --
- Total execution time: --
*Updated after each plan completion*
@@ -51,19 +46,12 @@ Progress: [==========] 100% (phases) | Plan 1/5 in Phase 18
### Decisions
Key decisions made during v2.0 planning:
- 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 17]: Private S3 bucket with presigned URLs (1h default), MinIO pinned to quay.io RELEASE.2025-09-07
- [Phase 17]: Image URL enrichment at route level, not service level, keeping services storage-agnostic
- [Phase 17]: Use createObjectURL for immediate upload preview, presigned URLs for existing images
- [Phase 18]: Global items category as text field (not FK) for hobby-agnostic flexibility
- [Phase 18]: itemGlobalLinks unique on itemId (one global item per user item)
- [Phase 18]: Seed data uses real bikepacking product names and approximate specs
### Pending Todos
@@ -76,6 +64,6 @@ None active.
## Session Continuity
Last session: 2026-04-05T10:59:44Z
Stopped at: Completed 18-01-PLAN.md (schema foundations)
Resume file: .planning/phases/18-global-items-public-profiles/18-01-SUMMARY.md
Last session: 2026-04-05
Stopped at: Completed 18-02-PLAN.md (global items service and routes)
Resume file: None

View File

@@ -0,0 +1,136 @@
---
phase: 18-global-items-public-profiles
plan: 02
subsystem: server
tags: [global-items, service, routes, seed, search, linking]
requires:
- phase: 18-01
provides: "globalItems/itemGlobalLinks tables, Zod schemas, seed JSON"
provides:
- "Global item search service with case-insensitive LIKE and wildcard escaping"
- "Global item detail with owner count aggregation"
- "Item-to-global link/unlink service functions"
- "GET /api/global-items and GET /api/global-items/:id public routes"
- "POST /api/items/:id/link and DELETE /api/items/:id/link auth-protected routes"
- "Idempotent seed script integrated into startup"
affects: [18-03, 18-04, 18-05]
tech-stack:
added: []
patterns: ["LIKE search with wildcard escaping for SQLite", "Owner count via junction table aggregation"]
key-files:
created:
- "src/server/services/global-item.service.ts"
- "src/server/routes/global-items.ts"
- "src/db/seed-global-items.ts"
- "tests/services/global-item.service.test.ts"
- "tests/routes/global-items.test.ts"
modified:
- "src/server/routes/items.ts"
- "src/server/index.ts"
- "src/db/seed.ts"
- "src/db/schema.ts"
- "src/shared/schemas.ts"
- "src/shared/types.ts"
- "src/db/global-items-seed.json"
key-decisions:
- "Used SQLite LIKE (case-insensitive for ASCII) instead of Postgres ILIKE since codebase is still SQLite"
- "Auth middleware already skips GET requests globally, no additional skip needed for /api/global-items"
- "Link/unlink endpoints placed on items routes (/api/items/:id/link) since they act on user items"
patterns-established:
- "Junction table count aggregation for owner counts"
- "Wildcard character escaping in search queries"
requirements-completed: [GLOB-01, GLOB-02, GLOB-03, GLOB-04, GLOB-05]
duration: 4min
completed: 2026-04-05
---
# Phase 18 Plan 02: Global Items Service and Routes Summary
**Global item catalog backend with LIKE search, owner count aggregation, item linking, idempotent seeding, and full test coverage**
## Performance
- **Duration:** 4 min
- **Started:** 2026-04-05T11:03:16Z
- **Completed:** 2026-04-05T11:07:46Z
- **Tasks:** 2
- **Files created:** 5
- **Files modified:** 6
## Accomplishments
- Built global-item.service.ts with 4 service functions following existing DI pattern
- Implemented case-insensitive search with wildcard escaping (%, _) for safe user input
- Added owner count aggregation via junction table count query
- Created public GET routes for global item catalog (search + detail)
- Added authenticated POST/DELETE link/unlink endpoints on item routes
- Wrote idempotent seed script that imports 18-item bikepacking catalog on startup
- Full TDD: 12 service tests + 10 route tests, all passing
- Full suite: 278 tests, 0 failures
## Task Commits
Each task was committed atomically:
1. **Task 1: Global item service + seed script + tests (TDD)**
- RED: `3a6876f` - Failing tests for service and seed
- GREEN: `60dd9f4` - Implementation passing all tests
2. **Task 2: Global item routes + link/unlink + route tests** - `d97d5d9`
## Files Created/Modified
- `src/server/services/global-item.service.ts` - searchGlobalItems, getGlobalItemWithOwnerCount, linkItemToGlobal, unlinkItemFromGlobal
- `src/server/routes/global-items.ts` - GET / (search), GET /:id (detail with ownerCount)
- `src/db/seed-global-items.ts` - Idempotent seed function importing from JSON
- `src/db/seed.ts` - Added seedGlobalItems call to seedDefaults
- `src/server/routes/items.ts` - Added POST /:id/link and DELETE /:id/link
- `src/server/index.ts` - Registered /api/global-items route
- `src/db/schema.ts` - Added globalItems and itemGlobalLinks SQLite tables
- `src/shared/schemas.ts` - Added searchGlobalItemsSchema and linkItemSchema
- `src/shared/types.ts` - Added GlobalItem, ItemGlobalLink, SearchGlobalItems, LinkItem types
- `src/db/global-items-seed.json` - 18 bikepacking gear items across 7 categories
- `tests/services/global-item.service.test.ts` - 12 service tests
- `tests/routes/global-items.test.ts` - 10 route tests
## Decisions Made
- Used SQLite LIKE instead of Postgres ILIKE since the codebase is still on SQLite; SQLite LIKE is already case-insensitive for ASCII characters
- Auth middleware already has a global GET skip rule, so no additional middleware change was needed for public global item access
- Link/unlink endpoints placed on /api/items/:id/link (item-centric) rather than on global-items routes
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 3 - Blocking] Applied 18-01 schema prerequisites to SQLite codebase**
- **Found during:** Pre-task setup
- **Issue:** Plan 18-01 was executed by a parallel agent on a Postgres-migrated schema, but this worktree is still on SQLite
- **Fix:** Added globalItems/itemGlobalLinks as sqliteTable definitions, Zod schemas, types, seed JSON, and migration directly in this branch
- **Files modified:** src/db/schema.ts, src/shared/schemas.ts, src/shared/types.ts, src/db/global-items-seed.json, drizzle migration
**2. [Rule 1 - Bug] Used LIKE instead of ILIKE for SQLite compatibility**
- **Found during:** Task 1
- **Issue:** Plan specified ilike (Postgres-only), but codebase uses SQLite where LIKE is already case-insensitive for ASCII
- **Fix:** Used drizzle-orm `like` operator which maps to SQLite LIKE
- **Files modified:** src/server/services/global-item.service.ts
## Known Stubs
None - all endpoints return real data from the database.
## Next Phase Readiness
- Global item catalog fully queryable via API
- Link/unlink API ready for client integration in Plan 18-03
- Seed data available for development and testing
---
*Phase: 18-global-items-public-profiles*
*Completed: 2026-04-05*