docs(quick-260406-j44): comprehensive dev seed script for bikepacking gear data

This commit is contained in:
2026-04-06 13:54:05 +02:00
parent 6836790e55
commit 4b8dec6252
2 changed files with 210 additions and 0 deletions

View File

@@ -0,0 +1,204 @@
---
phase: quick
plan: 260406-j44
type: execute
wave: 1
depends_on: []
files_modified:
- src/db/dev-seed.ts
- src/db/dev-seed-data.ts
- package.json
autonomous: true
requirements: []
must_haves:
truths:
- "Running `bun run db:seed:dev` populates the database with realistic bikepacking gear data"
- "Re-running the script on a populated DB is safe (idempotent — skips if data exists)"
- "All foreign key relationships are valid (items reference real categories, setup_items reference real items, etc.)"
artifacts:
- path: "src/db/dev-seed-data.ts"
provides: "All seed data as typed constants"
- path: "src/db/dev-seed.ts"
provides: "Idempotent seed runner"
- path: "package.json"
provides: "db:seed:dev script entry"
key_links:
- from: "src/db/dev-seed.ts"
to: "src/db/schema.ts"
via: "drizzle insert operations"
pattern: "db\\.insert\\(schema\\."
- from: "src/db/dev-seed.ts"
to: "src/db/dev-seed-data.ts"
via: "import data constants"
pattern: "import.*from.*dev-seed-data"
---
<objective>
Create a comprehensive development seed script that populates PostgreSQL with realistic bikepacking/outdoor gear data for local development.
Purpose: Enable developers to quickly stand up a fully-populated dev environment with realistic data spanning all entity types — global items, tags, user collections, threads, setups, and settings.
Output: Two new files (`dev-seed-data.ts` for data, `dev-seed.ts` for the runner) and a `db:seed:dev` npm script.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@CLAUDE.md
@src/db/schema.ts
@src/db/seed-global-items.ts
@src/db/global-items-seed.json
@tests/helpers/db.ts
@src/db/index.ts
<interfaces>
<!-- Key schema exports the seed script will use -->
From src/db/schema.ts:
- users: { id (serial PK), logtoSub (text, unique), displayName (text), avatarUrl (text), bio (text) }
- categories: { id (serial PK), name (text), icon (text), userId (int FK users) } — unique(userId, name)
- items: { id (serial PK), name, weightGrams, priceCents, categoryId (FK), userId (FK), notes, productUrl, imageFilename, imageSourceUrl, quantity (default 1), globalItemId (FK nullable), purchasePriceCents }
- threads: { id (serial PK), name, status (default "active"), resolvedCandidateId, categoryId (FK), userId (FK) }
- threadCandidates: { id (serial PK), threadId (FK cascade), name, weightGrams, priceCents, categoryId (FK), notes, productUrl, imageFilename, imageSourceUrl, status (default "researching"), pros, cons, sortOrder, globalItemId (FK nullable) }
- setups: { id (serial PK), name, userId (FK), isPublic (default false) }
- setupItems: { id (serial PK), setupId (FK cascade), itemId (FK cascade), classification (default "base") }
- globalItems: { id (serial PK), brand, model, category, weightGrams, priceCents, imageUrl, description }
- tags: { id (serial PK), name (unique) }
- globalItemTags: { globalItemId (FK cascade), tagId (FK cascade) } — composite PK
- settings: { userId (FK), key (text), value (text) } — composite PK(userId, key)
From src/db/index.ts:
- db: drizzle instance connected to PostgreSQL via DATABASE_URL
From src/db/seed-global-items.ts (idempotent pattern to follow):
- Check `existing.length > 0` before inserting
- Accept optional `db` parameter with production db as default
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Create seed data constants</name>
<files>src/db/dev-seed-data.ts</files>
<action>
Create `src/db/dev-seed-data.ts` exporting typed constant arrays for all seed data. Keep data and logic separate for maintainability.
**Categories** (8-10 with icons):
- Bags (briefcase), Shelter (tent), Sleep System (moon), Cooking (flame), Lighting (flashlight), Tools & Repair (wrench), Clothing (shirt), Water (droplets), Electronics (battery), Navigation (compass)
**Global Items** (35-45 items spanning categories). Use real bikepacking gear brands/models with realistic weights and prices. Examples per category:
- Bags: Revelate Designs Terrapin, Apidura Expedition Handlebar Pack, Ortlieb Frame-Pack, Rockgeist BarJam, Oveja Negra Superwedgie
- Shelter: Zpacks Duplex, Tarptent Stratospire, Durston X-Mid 1, Big Agnes Copper Spur HV UL1
- Sleep: Enlightened Equipment Enigma 20, Therm-a-Rest NeoAir XLite, Nemo Tensor Insulated, Sea to Summit Aeros Pillow
- Cooking: BRS-3000T, Soto Windmaster, Toaks 750ml, Snow Peak Ti-Mini Solo
- Lighting: Nitecore NU25, Lezyne Lite Drive, Fenix HL60R
- Tools: Park Tool IB-3, Lezyne CNC Chain Breaker, Gorilla Tape mini roll
- Clothing: Patagonia R1 Air, Frogg Toggs UL2 Rain Suit, Buff Merino Wool
- Water: Sawyer Squeeze, Katadyn BeFree, HydraPak Seeker 2L
- Electronics: Anker 10000 PD, Garmin inReach Mini 2
- Navigation: Wahoo ELEMNT BOLT, Caltopo printed maps holder
Each global item: `{ brand, model, category, weightGrams, priceCents, description }` — use integers for priceCents (e.g., 67900 for $679.00).
**Tags** (reuse existing SEED_TAGS from seed-global-items.ts plus a few more):
- Existing tags are already seeded by `seedTags()`, so do NOT re-seed them.
**Tag assignments** — array of `{ globalItemIndex, tagNames }` mapping global items to tags by index position and tag name. Example: index 0 (Terrapin) gets ["saddlebag", "waterproof", "bikepacking"]. Assign 2-4 tags per global item.
**User items** (15-20 items for the dev user's collection):
- ~10 items linked to global items via `globalItemId` (reference items). Use the index into the global items array. Include `purchasePriceCents` for some (actual price paid, sometimes different from MSRP).
- ~5-7 standalone items with no globalItemId (custom/unique gear the user added manually)
- Spread across categories. Include notes on some, quantity > 1 on a couple.
**Threads** (3 threads):
1. Active thread "Handlebar Bag Upgrade" in Bags category with 3 candidates (some catalog-linked via globalItemId index), statuses: researching/shortlisted/researching
2. Active thread "Navigation Computer" in Electronics with 2 candidates
3. Resolved thread "Camp Stove" in Cooking with 2 candidates, one marked "arrived"
**Setups** (2 setups):
1. "Weekend Overnighter" — 6-8 items from the user's collection, mix of base/worn/consumable classifications, isPublic: true
2. "Ultra-Light Day Ride" — 3-4 items, isPublic: false
**Settings**: weightUnit "g", currency "EUR"
Export everything as named constants with proper TypeScript types (using `as const` where appropriate for literal inference).
</action>
<verify>
<automated>bunx tsc --noEmit src/db/dev-seed-data.ts 2>&1 | head -20</automated>
</verify>
<done>All seed data constants exported with proper types, no TypeScript errors</done>
</task>
<task type="auto">
<name>Task 2: Create seed runner and wire npm script</name>
<files>src/db/dev-seed.ts, package.json</files>
<action>
Create `src/db/dev-seed.ts` — the idempotent seed runner.
**Structure:**
1. Import `db` from `./index.ts`, all schema tables from `./schema.ts`, all data from `./dev-seed-data.ts`, and `seedGlobalItems` from `./seed-global-items.ts`
2. Main `seedDevData()` async function:
**Idempotency check:** Query `users` table for a user with `logtoSub = "dev-user-seed"`. If found, log "Dev seed data already exists, skipping." and return early. This single check gates the entire script since all other data depends on this user.
**Insertion order** (respects FK constraints):
1. Call `seedGlobalItems(db)` first — reuse existing function to populate global_items and tags
2. Insert dev user: `{ logtoSub: "dev-user-seed", displayName: "Dev User", bio: "Bikepacking enthusiast" }` — capture returned user for userId FK
3. Insert categories (with userId from step 2) — capture returned rows for categoryId mapping
4. Look up tag IDs by name from tags table (they were seeded in step 1) — build `tagNameToId` map
5. Insert global item tag assignments using `globalItemTags` table — use the global item IDs from step 1 and tag IDs from step 4. Note: `seedGlobalItems` does not return IDs, so query `globalItems` table to get IDs by brand+model matching after seeding.
6. Insert user items (with userId, categoryId, globalItemId references) — capture returned rows for setup_items
7. Insert threads (with userId, categoryId) — capture returned IDs
8. Insert thread candidates (with threadId, categoryId, globalItemId) — for the resolved thread, set `resolvedCandidateId` on the thread after inserting candidates
9. Insert setups (with userId) — capture returned IDs
10. Insert setup_items (with setupId, itemId references from step 6)
11. Insert settings (with userId)
12. Log summary: "Dev seed complete: X global items, X tags, X user items, X threads, X setups"
**Script entry point:** At bottom of file:
```typescript
seedDevData()
.then(() => process.exit(0))
.catch((err) => { console.error("Seed failed:", err); process.exit(1); });
```
**package.json:** Add `"db:seed:dev": "bun run src/db/dev-seed.ts"` to scripts section.
Use `db.insert(...).values(...).returning()` to capture IDs needed for FK references. Use `db.insert(...).values(...)` (no returning) for leaf tables.
Wrap everything in a try/catch. On error, log the error and re-throw. Do NOT use transactions (PostgreSQL serial IDs and the idempotency check make this safe enough for dev seeding).
</action>
<verify>
<automated>bunx tsc --noEmit src/db/dev-seed.ts 2>&1 | head -20</automated>
</verify>
<done>
- `bun run db:seed:dev` executes without error on a fresh (migrated) database
- Re-running logs "already exists, skipping" and exits cleanly
- Database contains: 1 dev user, 8+ categories, 35+ global items with tag assignments, 15+ user items (some catalog-linked), 3 threads with candidates, 2 setups with items, settings
</done>
</task>
</tasks>
<verification>
1. `bunx tsc --noEmit src/db/dev-seed-data.ts src/db/dev-seed.ts` — no type errors
2. `bun run db:seed:dev` — completes without error (requires running PostgreSQL)
3. Re-run `bun run db:seed:dev` — logs skip message, exits 0
</verification>
<success_criteria>
- Script populates all entity types: users, categories, global items, tags, global item tag links, items (reference + standalone), threads, candidates (some catalog-linked), setups, setup_items, settings
- Idempotent: safe to run multiple times
- Data is realistic bikepacking gear with accurate weights/prices
- Foreign key relationships are all valid
- `bun run db:seed:dev` is the only command needed
</success_criteria>
<output>
After completion, create `.planning/quick/260406-j44-comprehensive-dev-seed-script-for-bikepa/260406-j44-SUMMARY.md`
</output>