Files
GearBox/.planning/phases/18-global-items-public-profiles/18-03-PLAN.md

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
18-01
src/server/services/profile.service.ts
src/server/routes/profiles.ts
src/server/routes/auth.ts
src/server/services/setup.service.ts
src/server/routes/setups.ts
src/server/index.ts
tests/services/profile.service.test.ts
tests/routes/profiles.test.ts
true
PROF-01
PROF-02
PROF-03
PROF-04
PROF-05
truths artifacts key_links
PUT /api/auth/profile updates display name, avatar URL, and bio for the authenticated user
GET /api/users/:id/profile returns public profile data (name, avatar, bio, public setups) without auth
PATCH or PUT to setup with isPublic=true makes the setup public
GET /api/setups/:id/public returns setup details without auth (only if isPublic is true)
GET /api/setups/:id/public returns 404 for private setups
Public profile lists only public setups, not private ones
path provides exports
src/server/services/profile.service.ts updateProfile, getPublicProfile
updateProfile
getPublicProfile
path provides min_lines
src/server/routes/profiles.ts GET /api/users/:id/profile route 20
path provides min_lines
tests/services/profile.service.test.ts Profile service tests 40
path provides min_lines
tests/routes/profiles.test.ts Profile and public setup route tests 50
from to via pattern
src/server/routes/profiles.ts src/server/services/profile.service.ts import and call getPublicProfile
from to via pattern
src/server/routes/auth.ts src/server/services/profile.service.ts import updateProfile updateProfile
from to via pattern
src/server/index.ts src/server/routes/profiles.ts app.route registration app.route.*profiles
Build the user profiles and public sharing backend: profile service for CRUD and public profile data, profile update endpoint on auth routes, public profile route, setup isPublic toggle, and public setup view endpoint.

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"), // nullable

setups 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.
  1. updateProfile(db: Db, userId: number, data: UpdateProfile) — Use db.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).

  2. 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.

  3. getPublicSetupWithItems(db: Db, setupId: number) — Similar to existing getSetupWithItems but: no userId param, adds eq(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 createSetup to accept and persist isPublic from data (default false if not provided).
  • Update updateSetup to accept and persist isPublic if provided.
  • Update getAllSetups return fields to include isPublic.
  • Update getSetupWithItems return to include isPublic.

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.

Task 2: Profile routes + public setup route + auth middleware + route tests src/server/routes/profiles.ts, src/server/routes/auth.ts, src/server/routes/setups.ts, src/server/index.ts, tests/routes/profiles.test.ts src/server/routes/auth.ts, src/server/routes/setups.ts, src/server/index.ts, tests/routes/setups.test.ts **profiles.ts route**: Create at `src/server/routes/profiles.ts`.
  1. GET /:id/profile (maps to /api/users/:id/profile) — per D-20. Parse id with parseId. Call getPublicProfile(db, id). Return 404 if null, otherwise JSON. No auth needed.

auth.ts updates — per D-21:

  1. PUT /profile (maps to /api/auth/profile) — Validate body with updateProfileSchema via zValidator. Get userId from context. Call updateProfile(db, userId, body). Return updated profile JSON.

setups.ts updates — per D-22:

  1. Add GET /:id/public endpoint — Parse id with parseId. Call getPublicSetupWithItems(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.

  2. Ensure existing PUT /:id passes isPublic from body through to updateSetup service function.

index.ts updates:

  1. Import profileRoutes from routes/profiles.ts
  2. Register: app.route("/api/users", profileRoutes)
  3. Update auth middleware skip: Add conditions for:
    • c.req.path.match(/^\/api\/users\/\d+\/profile$/) && c.req.method === "GET" — skip auth
    • c.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.
- `bun test tests/services/profile.service.test.ts` — all service tests pass - `bun test tests/routes/profiles.test.ts` — all route tests pass - `bun test` — full suite passes (no regressions from setup service changes)

<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`