Files
SimpleFinanceDash/.planning/phases/05-template-data-model-and-api/05-CONTEXT.md

5.7 KiB

Phase 5: Template Data Model and API - Context

Gathered: 2026-03-12 Status: Ready for planning

## Phase Boundary

Backend infrastructure for the template system — new DB tables (templates, template_items), migration to add item_tier to budget_items, and REST endpoints for template CRUD and budget-from-template generation. No frontend changes in this phase. One-off items are excluded from templates at the data layer.

## Implementation Decisions

Item type storage

  • New PostgreSQL ENUM item_tier with values: fixed, variable, one_off
  • Added as column on budget_items table via migration 002
  • Existing budget items default to 'fixed' (they were created via copy-from-previous, treating all as recurring)
  • Column named item_tier (not item_type) to avoid collision with existing category_type field on BudgetItem
  • Settable via existing POST /api/budgets/{id}/items and PUT /api/budgets/{id}/items/{itemId} endpoints — optional field, defaults to 'one_off' for new items

Template ownership

  • One template per user — templates table with UNIQUE constraint on user_id
  • Lazy creation: template auto-created when user first adds a template item (POST /api/template/items)
  • GET /api/template returns { "id": null, "items": [] } when no template exists
  • Template auto-named "My Template" on creation; user can rename via PUT /api/template

Template item data model

  • template_items table: id, template_id (FK), category_id (FK), item_tier, budgeted_amount (NULLABLE), sort_order, created_at, updated_at
  • Fixed items: category + budgeted_amount set
  • Variable items: category + budgeted_amount NULL
  • CHECK constraint on template_items: item_tier IN ('fixed', 'variable') — one_off cannot exist in templates (DB-level enforcement)

Generation endpoint

  • POST /api/budgets/generate with body { "month": "2026-04", "currency": "EUR" }
  • Single atomic transaction: creates budget + all template-derived items together
  • Fixed items generated with their template amounts; variable items generated with budgeted_amount = 0
  • Returns 201 Created with full BudgetDetail response (same shape as GET /api/budgets/{id})
  • Returns 409 Conflict with { "error": "budget already exists", "budget_id": "..." } if budget for that month exists
  • If user has no template: creates empty budget with zero items (no error)
  • Budget auto-named from month using user's preferred_locale (localized month name + year, e.g. "April 2026")
  • Start/end dates computed from month parameter (first and last day of month)

API routes

  • Singular /api/template (one template per user, no ID needed):
    • GET /api/template — get template with items (joined category name, type, icon)
    • PUT /api/template — update template name
    • POST /api/template/items — add item (auto-creates template if needed)
    • PUT /api/template/items/{itemId} — update item
    • DELETE /api/template/items/{itemId} — remove item
    • PUT /api/template/items/reorder — batch update sort_order
  • Generation: POST /api/budgets/generate (under budgets, since the output is a budget)

API response shape

  • item_tier always included in budget item JSON responses (additive, non-breaking)
  • Template GET response includes joined category details: category_name, category_type, category_icon
  • Generation endpoint returns standard BudgetDetail shape

Claude's Discretion

  • Go struct design for Template and TemplateItem models
  • Exact migration SQL structure and index choices
  • Handler implementation patterns (error messages, validation order)
  • Query function signatures and SQL query structure
  • Month name localization approach (map vs library)
## Specific Ideas
  • Migration defaults existing items to 'fixed' since the old copy-from-previous treated everything as recurring
  • Lazy template creation removes friction — user doesn't need to "set up" a template before using the app
  • 409 Conflict on duplicate month matches existing error patterns (409 for unique constraint violations)
  • Template response joins category details so Phase 6 frontend doesn't need extra API calls

<code_context>

Existing Code Insights

Reusable Assets

  • models.go: BudgetItem struct — add ItemTier field, BudgetDetail response shape reused by generation endpoint
  • queries.go: 22 existing query functions — CopyBudgetItems as reference for template-to-budget generation pattern
  • handlers.go: Handlers struct with queries dependency injection — add template handlers following same pattern
  • router.go: Chi router with auth middleware group — add /api/template route group and /api/budgets/generate endpoint

Established Patterns

  • PostgreSQL ENUM types (category_type) — same pattern for item_tier
  • User-scoped data isolation: all queries filter by userID from auth context
  • JSON serialization with json:"field_name" tags on structs
  • Error responses: writeError(w, status, message) helper
  • UUID primary keys with uuid_generate_v4() default
  • Decimal amounts via shopspring/decimal and NUMERIC(12,2) in PostgreSQL

Integration Points

  • 001_initial.sql existing schema — new migration 002 adds item_tier column and template tables
  • handlers.go CreateBudgetItem/UpdateBudgetItem — add item_tier to request parsing and query calls
  • queries.go GetBudgetWithItems — add item_tier to SELECT and Scan
  • router.go protected routes group — add template routes and generate endpoint
  • models.go — add ItemTier type, Template struct, TemplateItem struct

</code_context>

## Deferred Ideas

None — discussion stayed within phase scope


Phase: 05-template-data-model-and-api Context gathered: 2026-03-12