Files
GearBox/.planning/phases/01-foundation-and-collection/01-02-PLAN.md

13 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
01-foundation-and-collection 02 execute 2
01-01
src/server/index.ts
src/server/routes/items.ts
src/server/routes/categories.ts
src/server/routes/totals.ts
src/server/routes/images.ts
src/server/services/item.service.ts
src/server/services/category.service.ts
tests/services/item.service.test.ts
tests/services/category.service.test.ts
tests/services/totals.test.ts
tests/routes/items.test.ts
tests/routes/categories.test.ts
true
COLL-01
COLL-02
COLL-03
COLL-04
truths artifacts key_links
POST /api/items creates an item with name, weight, price, category, notes, and product link
PUT /api/items/:id updates any field on an existing item
DELETE /api/items/:id removes an item and cleans up its image file
POST /api/categories creates a category with name and emoji
PUT /api/categories/:id renames a category or changes its emoji
DELETE /api/categories/:id reassigns its items to Uncategorized then deletes the category
GET /api/totals returns per-category and global weight/cost/count aggregates
POST /api/images accepts a file upload and returns the filename
path provides exports
src/server/services/item.service.ts Item CRUD business logic
getAllItems
getItemById
createItem
updateItem
deleteItem
path provides exports
src/server/services/category.service.ts Category CRUD with reassignment logic
getAllCategories
createCategory
updateCategory
deleteCategory
path provides
src/server/routes/items.ts Hono routes for /api/items
path provides
src/server/routes/categories.ts Hono routes for /api/categories
path provides
src/server/routes/totals.ts Hono route for /api/totals
path provides
src/server/routes/images.ts Hono route for /api/images upload
path provides
tests/services/item.service.test.ts Unit tests for item CRUD
path provides
tests/services/category.service.test.ts Unit tests for category CRUD including reassignment
path provides
tests/services/totals.test.ts Unit tests for totals aggregation
from to via pattern
src/server/routes/items.ts src/server/services/item.service.ts Route handlers call service functions import.*item.service
from to via pattern
src/server/services/item.service.ts src/db/schema.ts Drizzle queries against items table db..*from(items)
from to via pattern
src/server/services/category.service.ts src/db/schema.ts Drizzle queries plus reassignment to Uncategorized on delete update.*items.*categoryId
from to via pattern
src/server/routes/items.ts src/shared/schemas.ts Zod validation via @hono/zod-validator zValidator.*createItemSchema|updateItemSchema
Build the complete backend API: item CRUD, category CRUD with reassignment, computed totals, and image upload. Includes service layer with business logic and comprehensive tests.

Purpose: Provides the data layer and API endpoints that the frontend will consume. All four COLL requirements are addressed by the API. Output: Working Hono API routes with validated inputs, service layer, and passing test suite.

<execution_context> @/home/jean-luc-makiola/.claude/get-shit-done/workflows/execute-plan.md @/home/jean-luc-makiola/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/01-foundation-and-collection/01-RESEARCH.md @.planning/phases/01-foundation-and-collection/01-VALIDATION.md @.planning/phases/01-foundation-and-collection/01-01-SUMMARY.md

From src/db/schema.ts:

export const categories = sqliteTable("categories", {
  id: integer("id").primaryKey({ autoIncrement: true }),
  name: text("name").notNull().unique(),
  emoji: text("emoji").notNull().default("..."),
  createdAt: integer("created_at", { mode: "timestamp" })...
});

export const items = sqliteTable("items", {
  id: integer("id").primaryKey({ autoIncrement: true }),
  name: text("name").notNull(),
  weightGrams: real("weight_grams"),
  priceCents: integer("price_cents"),
  categoryId: integer("category_id").notNull().references(() => categories.id),
  notes: text("notes"),
  productUrl: text("product_url"),
  imageFilename: text("image_filename"),
  createdAt: integer("created_at", { mode: "timestamp" })...,
  updatedAt: integer("updated_at", { mode: "timestamp" })...,
});

From src/shared/schemas.ts:

export const createItemSchema = z.object({ name, weightGrams?, priceCents?, categoryId, notes?, productUrl? });
export const updateItemSchema = createItemSchema.partial().extend({ id });
export const createCategorySchema = z.object({ name, emoji? });
export const updateCategorySchema = z.object({ id, name?, emoji? });

From tests/helpers/db.ts:

export function createTestDb(): DrizzleInstance;
Task 1: Service layer with tests for items, categories, and totals src/server/services/item.service.ts, src/server/services/category.service.ts, tests/services/item.service.test.ts, tests/services/category.service.test.ts, tests/services/totals.test.ts Item service tests: - createItem: creates item with all fields, returns item with id and timestamps - createItem: only name is required, other fields optional - getAllItems: returns all items with category info joined - getItemById: returns single item or null - updateItem: updates specified fields, sets updatedAt - deleteItem: removes item from DB, returns deleted item (for image cleanup) - deleteItem: returns null for non-existent id
Category service tests:
- createCategory: creates with name and emoji
- createCategory: uses default emoji if not provided
- getAllCategories: returns all categories
- updateCategory: renames category
- updateCategory: changes emoji
- deleteCategory: reassigns items to Uncategorized (id=1) then deletes
- deleteCategory: cannot delete Uncategorized (id=1)

Totals tests:
- getCategoryTotals: returns weight sum, cost sum, item count per category
- getCategoryTotals: excludes empty categories (no items)
- getGlobalTotals: returns overall weight, cost, count
- getGlobalTotals: returns zeros when no items exist
Write tests FIRST using createTestDb() from tests/helpers/db.ts. Each test gets a fresh in-memory DB.
Then implement services:

1. `src/server/services/item.service.ts`:
   - Functions accept a db instance parameter (for testability) with default to the production db
   - getAllItems(): SELECT items JOIN categories, returns items with category name and emoji
   - getItemById(id): SELECT single item or null
   - createItem(data: CreateItem): INSERT, return with id and timestamps
   - updateItem(id, data): UPDATE with updatedAt = new Date(), return updated item
   - deleteItem(id): SELECT item first (for image filename), DELETE, return the deleted item data

2. `src/server/services/category.service.ts`:
   - getAllCategories(): SELECT all, ordered by name
   - createCategory(data: CreateCategory): INSERT, return with id
   - updateCategory(id, data): UPDATE name and/or emoji
   - deleteCategory(id): Guard against deleting id=1. UPDATE all items with this categoryId to categoryId=1, then DELETE the category. Use a transaction.

3. Totals functions (can live in item.service.ts or a separate totals module):
   - getCategoryTotals(): Per RESEARCH.md Pattern 4 exactly. SELECT with SUM and COUNT, GROUP BY categoryId, JOIN categories.
   - getGlobalTotals(): SELECT SUM(weightGrams), SUM(priceCents), COUNT(*) from items.
bun test tests/services/ --bail All service tests pass. Item CRUD, category CRUD with Uncategorized reassignment, and computed totals all work correctly against in-memory SQLite. Task 2: Hono API routes with validation, image upload, and integration tests src/server/routes/items.ts, src/server/routes/categories.ts, src/server/routes/totals.ts, src/server/routes/images.ts, src/server/index.ts, tests/routes/items.test.ts, tests/routes/categories.test.ts 1. Create `src/server/routes/items.ts` per RESEARCH.md example: - GET / returns all items (calls getAllItems service) - GET /:id returns single item (404 if not found) - POST / validates body with zValidator("json", createItemSchema), calls createItem, returns 201 - PUT /:id validates body with zValidator("json", updateItemSchema), calls updateItem, returns 200 or 404 - DELETE /:id calls deleteItem, cleans up image file if item had imageFilename (try/catch, don't fail delete if file missing), returns 200 or 404 - Export as itemRoutes
2. Create `src/server/routes/categories.ts`:
   - GET / returns all categories
   - POST / validates with createCategorySchema, returns 201
   - PUT /:id validates with updateCategorySchema, returns 200 or 404
   - DELETE /:id calls deleteCategory, returns 200 or 400 (if trying to delete Uncategorized) or 404
   - Export as categoryRoutes

3. Create `src/server/routes/totals.ts`:
   - GET / returns { categories: CategoryTotals[], global: GlobalTotals }
   - Export as totalRoutes

4. Create `src/server/routes/images.ts`:
   - POST / accepts multipart/form-data with a single file field "image"
   - Validate: file exists, size under 5MB, type is image/jpeg, image/png, or image/webp
   - Generate unique filename: `${Date.now()}-${randomUUID()}.${extension}`
   - Write to uploads/ directory using Bun.write
   - Return 201 with { filename }
   - Export as imageRoutes

5. Update `src/server/index.ts`:
   - Register all routes: app.route("/api/items", itemRoutes), app.route("/api/categories", categoryRoutes), app.route("/api/totals", totalRoutes), app.route("/api/images", imageRoutes)
   - Keep health check and static file serving from Plan 01

6. Create integration tests `tests/routes/items.test.ts`:
   - Test POST /api/items with valid data returns 201
   - Test POST /api/items with missing name returns 400 (Zod validation)
   - Test GET /api/items returns array
   - Test PUT /api/items/:id updates fields
   - Test DELETE /api/items/:id returns success

7. Create integration tests `tests/routes/categories.test.ts`:
   - Test POST /api/categories creates category
   - Test DELETE /api/categories/:id reassigns items
   - Test DELETE /api/categories/1 returns 400 (cannot delete Uncategorized)

NOTE for integration tests: Use Hono's app.request() method for testing without starting a real server. Create a test app instance with an in-memory DB injected.
bun test --bail All API routes respond correctly. Validation rejects invalid input with 400. Item CRUD returns proper status codes. Category delete reassigns items. Totals endpoint returns computed aggregates. Image upload stores files. All integration tests pass. - `bun test` passes all service and route tests - `curl -X POST http://localhost:3000/api/categories -H 'Content-Type: application/json' -d '{"name":"Shelter","emoji":"tent emoji"}'` returns 201 - `curl -X POST http://localhost:3000/api/items -H 'Content-Type: application/json' -d '{"name":"Tent","categoryId":2}'` returns 201 - `curl http://localhost:3000/api/totals` returns category and global totals - `curl -X DELETE http://localhost:3000/api/categories/1` returns 400 (cannot delete Uncategorized)

<success_criteria>

  • All item CRUD operations work via API (create, read, update, delete)
  • All category CRUD operations work via API including reassignment on delete
  • Totals endpoint returns correct per-category and global aggregates
  • Image upload endpoint accepts files and stores them in uploads/
  • Zod validation rejects invalid input with 400 status
  • All tests pass with bun test </success_criteria>
After completion, create `.planning/phases/01-foundation-and-collection/01-02-SUMMARY.md`