5.7 KiB
5.7 KiB
Phase 5: Template Data Model and API - Context
Gathered: 2026-03-12 Status: Ready for planning
## Phase BoundaryBackend 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 DecisionsItem type storage
- New PostgreSQL ENUM
item_tierwith values:fixed,variable,one_off - Added as column on
budget_itemstable via migration 002 - Existing budget items default to
'fixed'(they were created via copy-from-previous, treating all as recurring) - Column named
item_tier(notitem_type) to avoid collision with existingcategory_typefield on BudgetItem - Settable via existing
POST /api/budgets/{id}/itemsandPUT /api/budgets/{id}/items/{itemId}endpoints — optional field, defaults to'one_off'for new items
Template ownership
- One template per user —
templatestable with UNIQUE constraint onuser_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_itemstable: 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/generatewith 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 namePOST /api/template/items— add item (auto-creates template if needed)PUT /api/template/items/{itemId}— update itemDELETE /api/template/items/{itemId}— remove itemPUT /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_tieralways 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)
- 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 endpointqueries.go: 22 existing query functions — CopyBudgetItems as reference for template-to-budget generation patternhandlers.go: Handlers struct with queries dependency injection — add template handlers following same patternrouter.go: Chi router with auth middleware group — add/api/templateroute group and/api/budgets/generateendpoint
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/decimalandNUMERIC(12,2)in PostgreSQL
Integration Points
001_initial.sqlexisting schema — new migration 002 adds item_tier column and template tableshandlers.goCreateBudgetItem/UpdateBudgetItem — add item_tier to request parsing and query callsqueries.goGetBudgetWithItems — add item_tier to SELECT and Scanrouter.goprotected routes group — add template routes and generate endpointmodels.go— add ItemTier type, Template struct, TemplateItem struct
</code_context>
## Deferred IdeasNone — discussion stayed within phase scope
Phase: 05-template-data-model-and-api Context gathered: 2026-03-12