10 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 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 18-global-items-public-profiles | 03 | execute | 2 |
|
|
true |
|
|
Purpose: Delivers PROF-01 through PROF-05 server-side. Users can edit their profile, toggle setup visibility, and anyone can view public profiles and setups without auth. Output: profile.service.ts, profiles.ts routes, updated auth.ts + setup service/routes + index.ts, service + route tests
<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/18-global-items-public-profiles/18-CONTEXT.md @.planning/phases/18-global-items-public-profiles/18-RESEARCH.md @.planning/phases/18-global-items-public-profiles/18-01-SUMMARY.md@src/server/services/setup.service.ts @src/server/routes/setups.ts @src/server/routes/auth.ts @src/server/index.ts @src/server/middleware/auth.ts @tests/helpers/db.ts
users table now has: displayName: text("display_name"), // nullable avatarUrl: text("avatar_url"), // nullable bio: text("bio"), // nullablesetups table now has: isPublic: boolean("is_public").notNull().default(false),
export const updateProfileSchema = z.object({ displayName: z.string().max(100).optional(), avatarUrl: z.string().optional(), bio: z.string().max(500).optional(), });
Task 1: Profile service + setup visibility + tests src/server/services/profile.service.ts, src/server/services/setup.service.ts, tests/services/profile.service.test.ts src/server/services/setup.service.ts, src/db/schema.ts, tests/helpers/db.ts, src/shared/schemas.ts - updateProfile(db, userId, { displayName: "Alice" }) updates user and returns updated row - updateProfile(db, userId, { bio: "Bikepacker" }) updates bio only, leaves other fields untouched - updateProfile(db, userId, {}) does nothing harmful, returns user - getPublicProfile(db, userId) returns { id, displayName, avatarUrl, bio, setups: [] } when user has no public setups - getPublicProfile(db, userId) returns only public setups in the setups array (not private ones) - getPublicProfile(db, nonExistentId) returns null - getPublicSetupWithItems(db, setupId) returns setup with items when isPublic is true - getPublicSetupWithItems(db, setupId) returns null when isPublic is false - Updated setup service: createSetup and updateSetup handle isPublic field **profile.service.ts**: Create at `src/server/services/profile.service.ts`. Follow service pattern.-
updateProfile(db: Db, userId: number, data: UpdateProfile)— Usedb.update(users).set(data).where(eq(users.id, userId)).returning(). Return updated user or null if not found. Only set fields that are present in data (Drizzle handles undefined correctly). -
getPublicProfile(db: Db, userId: number)— Select id, displayName, avatarUrl, bio from users. Then select id, name, createdAt from setups where userId matches AND isPublic is true. Return{ ...user, setups: publicSetups }or null. -
getPublicSetupWithItems(db: Db, setupId: number)— Similar to existinggetSetupWithItemsbut: no userId param, addseq(setups.isPublic, true)to where clause. Returns null if setup doesn't exist or is private. Include items via setupItems join (same pattern as existing function). Include weight/cost aggregates.
setup.service.ts updates:
- Update
createSetupto accept and persistisPublicfrom data (default false if not provided). - Update
updateSetupto accept and persistisPublicif provided. - Update
getAllSetupsreturn fields to includeisPublic. - Update
getSetupWithItemsreturn to includeisPublic.
Tests: Write tests FIRST. Use createTestDb(). Create user profile data via direct db.update. Create setups with isPublic true/false to test filtering.
bun test tests/services/profile.service.test.ts
<acceptance_criteria>
- grep -q "updateProfile" src/server/services/profile.service.ts
- grep -q "getPublicProfile" src/server/services/profile.service.ts
- grep -q "getPublicSetupWithItems" src/server/services/profile.service.ts
- grep -q "isPublic" src/server/services/setup.service.ts
- test -f tests/services/profile.service.test.ts
</acceptance_criteria>
Profile service and public setup service functions pass all tests. Setup service handles isPublic in create/update/list/detail.
GET /:id/profile(maps to/api/users/:id/profile) — per D-20. Parse id with parseId. CallgetPublicProfile(db, id). Return 404 if null, otherwise JSON. No auth needed.
auth.ts updates — per D-21:
PUT /profile(maps to/api/auth/profile) — Validate body withupdateProfileSchemavia zValidator. Get userId from context. CallupdateProfile(db, userId, body). Return updated profile JSON.
setups.ts updates — per D-22:
-
Add
GET /:id/publicendpoint — Parse id with parseId. CallgetPublicSetupWithItems(db, id). Return 404 if null (setup not found or is private). Return JSON with setup details and items. This route exists within the existing setup routes file, but the auth middleware skip handles making it public. -
Ensure existing PUT /:id passes isPublic from body through to updateSetup service function.
index.ts updates:
- Import
profileRoutesfrom routes/profiles.ts - Register:
app.route("/api/users", profileRoutes) - Update auth middleware skip: Add conditions for:
c.req.path.match(/^\/api\/users\/\d+\/profile$/) && c.req.method === "GET"— skip authc.req.path.match(/^\/api\/setups\/\d+\/public$/) && c.req.method === "GET"— skip auth
Route tests: Test:
- GET /api/users/:id/profile returns 200 without auth, includes public setups only
- GET /api/users/999/profile returns 404
- PUT /api/auth/profile returns 200 with updated fields (requires auth)
- PUT /api/auth/profile without auth returns 401
- GET /api/setups/:id/public returns 200 for public setup without auth
- GET /api/setups/:id/public returns 404 for private setup
bun test tests/routes/profiles.test.ts
<acceptance_criteria>
- grep -q "profileRoutes|profile" src/server/routes/profiles.ts
- grep -q "profile" src/server/routes/auth.ts
- grep -q "public" src/server/routes/setups.ts
- grep -q "api/users" src/server/index.ts
- grep -q "api/setups.*public|api/users.*profile" src/server/index.ts
- test -f tests/routes/profiles.test.ts </acceptance_criteria> Public profile endpoint returns user info + public setups. Profile update requires auth. Public setup view works without auth and returns 404 for private setups. Auth middleware correctly skips public routes. All route tests pass.
<success_criteria> Profile CRUD works server-side. Public profile shows user info and public setups only. Setup visibility toggle persists. Public setup endpoint serves setup details without auth. Auth middleware correctly routes public/private access. All tests pass. </success_criteria>
After completion, create `.planning/phases/18-global-items-public-profiles/18-03-SUMMARY.md`