docs: map existing codebase
This commit is contained in:
172
.planning/codebase/ARCHITECTURE.md
Normal file
172
.planning/codebase/ARCHITECTURE.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# Architecture
|
||||
|
||||
**Analysis Date:** 2026-03-11
|
||||
|
||||
## Pattern Overview
|
||||
|
||||
**Overall:** Monolithic full-stack with clear backend (Go) and frontend (React) separation, communicating via REST API. The Go binary embeds the compiled React frontend and serves the complete SPA with static assets and API routes.
|
||||
|
||||
**Key Characteristics:**
|
||||
- Monolithic deployment (single Docker container)
|
||||
- Backend-for-Frontend pattern: Go handles routing, SPA serving, and REST API
|
||||
- Session-based authentication via HTTP-only cookies and JWT tokens
|
||||
- Server-side budget totals computation (not stored in DB)
|
||||
- Frontend client-side routing within embedded SPA
|
||||
- User-scoped data isolation at database level
|
||||
|
||||
## Layers
|
||||
|
||||
**Presentation (Frontend):**
|
||||
- Purpose: Render UI, handle user interactions, manage form state and navigation
|
||||
- Location: `frontend/src/`
|
||||
- Contains: React components, pages, hooks, translation files, styling (Tailwind)
|
||||
- Depends on: REST API via `lib/api.ts`, i18n for localization, React Router for navigation
|
||||
- Used by: Users accessing the application through the browser
|
||||
|
||||
**API/Handlers (Backend):**
|
||||
- Purpose: HTTP request routing, validation, and response serialization
|
||||
- Location: `backend/internal/api/`
|
||||
- Contains: `router.go` (chi mux setup, middleware, routes), `handlers.go` (HTTP handler functions)
|
||||
- Depends on: Database queries, authentication, models
|
||||
- Used by: Frontend making API calls, middleware chain
|
||||
|
||||
**Authentication (Backend):**
|
||||
- Purpose: Credential verification, session token generation/validation, user context injection
|
||||
- Location: `backend/internal/auth/`
|
||||
- Contains: Password hashing (bcrypt), JWT token generation/validation, session cookies, auth middleware
|
||||
- Depends on: Models (User), external crypto libraries
|
||||
- Used by: API handlers, router middleware
|
||||
|
||||
**Database Layer (Backend):**
|
||||
- Purpose: Persist and retrieve user data (users, categories, budgets, budget items)
|
||||
- Location: `backend/internal/db/`
|
||||
- Contains: `db.go` (connection pool, migrations runner), `queries.go` (CRUD operations)
|
||||
- Depends on: PostgreSQL 16, pgx driver, models
|
||||
- Used by: API handlers, migration system
|
||||
|
||||
**Models (Backend):**
|
||||
- Purpose: Domain type definitions shared between API and database layers
|
||||
- Location: `backend/internal/models/`
|
||||
- Contains: User, Category, Budget, BudgetItem, BudgetTotals, BudgetDetail structs with JSON tags
|
||||
- Depends on: External types (uuid.UUID, decimal.Decimal)
|
||||
- Used by: API handlers, database queries, JSON serialization
|
||||
|
||||
**Frontend Hooks:**
|
||||
- Purpose: Encapsulate API data fetching and state management
|
||||
- Location: `frontend/src/hooks/`
|
||||
- Contains: `useAuth.ts` (login, register, logout, user state), `useBudgets.ts` (budget list, selection, loading state)
|
||||
- Depends on: React hooks, API client
|
||||
- Used by: Page components and nested component trees
|
||||
|
||||
**Frontend API Client:**
|
||||
- Purpose: Centralize HTTP requests with common configuration, error handling, type definitions
|
||||
- Location: `frontend/src/lib/api.ts`
|
||||
- Contains: Type definitions (User, Category, Budget, BudgetItem, BudgetDetail), request helper, namespace functions for routes (auth, categories, budgets, budgetItems, settings)
|
||||
- Depends on: Fetch API, error handling
|
||||
- Used by: All hooks and components making API calls
|
||||
|
||||
## Data Flow
|
||||
|
||||
**User Registration/Login Flow:**
|
||||
|
||||
1. User submits credentials via LoginPage or RegisterPage
|
||||
2. Frontend calls `auth.register()` or `auth.login()` from `lib/api.ts`
|
||||
3. API request goes to `/api/auth/register` or `/api/auth/login`
|
||||
4. Backend handler (`handlers.go`) validates credentials, hashes password (bcrypt)
|
||||
5. Database layer creates user or validates stored password
|
||||
6. Backend generates JWT token via `auth.GenerateToken()` and sets HTTP-only session cookie
|
||||
7. Frontend receives User object, stores in `useAuth()` state
|
||||
8. Frontend redirects to authenticated view (AppLayout with Router)
|
||||
|
||||
**Budget Data Flow:**
|
||||
|
||||
1. DashboardPage or BudgetSetup component loads via page render
|
||||
2. useBudgets hook calls `budgets.list()` on mount
|
||||
3. API request goes to `/api/budgets`
|
||||
4. Auth middleware validates session cookie and injects userID into context
|
||||
5. Handler retrieves budgets for authenticated user via `queries.ListBudgets(userID)`
|
||||
6. Database joins budget items with categories and computes totals server-side
|
||||
7. Backend responds with Budget list (or BudgetDetail with Items and Totals on `/api/budgets/{id}`)
|
||||
8. Frontend receives data, updates component state
|
||||
9. Components render UI using fetched data
|
||||
|
||||
**State Management:**
|
||||
|
||||
- **Authentication:** Centralized in `useAuth()` hook; persists in React state during session, restored from `/api/auth/me` on page load
|
||||
- **Budget data:** Managed per-page/component via `useBudgets()` hook; re-fetched when creating/updating/deleting
|
||||
- **Form state:** Local component state (useState) for forms like BudgetSetup, VariableExpenses
|
||||
- **i18n:** Global i18n instance in `frontend/src/i18n/index.ts` synced with user preference from backend
|
||||
|
||||
## Key Abstractions
|
||||
|
||||
**JWT Session Token:**
|
||||
- Purpose: Stateless authentication without server-side session storage
|
||||
- Examples: Generated in `backend/internal/auth/auth.go` via `GenerateToken()`, validated in `Middleware()` and `Me()` handler
|
||||
- Pattern: Token contains user ID, expires in 7 days; stored in HTTP-only cookie `session`
|
||||
|
||||
**User-Scoped Data:**
|
||||
- Purpose: All queries filter by authenticated user ID to prevent cross-user data leaks
|
||||
- Examples: Categories, Budgets, BudgetItems all require userID in queries (`queries.ListCategories(userID)`, `queries.ListBudgets(userID)`)
|
||||
- Pattern: userID injected into request context by auth middleware, extracted via `auth.UserIDFromContext()`
|
||||
|
||||
**Category Types:**
|
||||
- Purpose: Categorize budget items (bill, variable_expense, debt, saving, investment, income)
|
||||
- Examples: In `backend/internal/models/models.go` as enum, mirrored in frontend API types, mapped to category_type ENUM in PostgreSQL
|
||||
- Pattern: Controls how budget totals are computed and displayed (separate rows for each type)
|
||||
|
||||
**BudgetDetail with Computed Totals:**
|
||||
- Purpose: Single response containing budget, items, and pre-computed category totals
|
||||
- Examples: Returned by `GetBudgetWithItems()`, includes BudgetTotals with budget/actual for each category type
|
||||
- Pattern: Server-side computation reduces client logic; frontend only displays values
|
||||
|
||||
**API Client Namespaces:**
|
||||
- Purpose: Group related endpoints for discoverability and organization
|
||||
- Examples: `auth.*`, `categories.*`, `budgets.*`, `budgetItems.*`, `settings.*` in `frontend/src/lib/api.ts`
|
||||
- Pattern: Declarative CRUD-style methods (list, create, get, update, delete) mapped to HTTP verbs and paths
|
||||
|
||||
## Entry Points
|
||||
|
||||
**Backend:**
|
||||
- Location: `backend/cmd/server/main.go`
|
||||
- Triggers: `go run ./cmd/server` or binary execution in Docker
|
||||
- Responsibilities: Reads environment variables, connects to PostgreSQL, runs migrations, initializes API router with embedded frontend, starts HTTP server with graceful shutdown
|
||||
|
||||
**Frontend:**
|
||||
- Location: `frontend/src/main.tsx`
|
||||
- Triggers: Vite dev server or loaded index.html in production
|
||||
- Responsibilities: Mounts React app into DOM root element, applies StrictMode for dev warnings
|
||||
|
||||
**Frontend App Root:**
|
||||
- Location: `frontend/src/App.tsx`
|
||||
- Triggers: Mounted by main.tsx
|
||||
- Responsibilities: Initializes BrowserRouter, manages auth state via useAuth(), conditionally renders LoginPage/RegisterPage or authenticated app with Routes
|
||||
|
||||
## Error Handling
|
||||
|
||||
**Strategy:** RESTful status codes with JSON error messages. Frontend catches API errors and displays user-friendly messages.
|
||||
|
||||
**Patterns:**
|
||||
|
||||
- **Backend HTTP errors:** writeError() helper sets HTTP status (400, 401, 404, 409, 500) and JSON body `{"error": "message"}`
|
||||
- **Frontend API errors:** ApiError class wraps HTTP status and message; caught in try/catch blocks
|
||||
- **Auth failures:** Return 401 Unauthorized with "unauthorized" message; frontend redirects to login
|
||||
- **Validation failures:** Return 400 Bad Request with specific field or format error messages
|
||||
- **Unique constraint violations:** Return 409 Conflict for duplicate email, duplicate OIDC subject
|
||||
- **Not found:** Return 404 for missing resources (budget, category, user)
|
||||
- **Server errors:** Return 500 for unexpected failures (password hashing, database errors)
|
||||
|
||||
## Cross-Cutting Concerns
|
||||
|
||||
**Logging:** Backend uses Go's `log` package; logs connection errors, migrations, server startup/shutdown. Frontend uses `console` (no dedicated logger).
|
||||
|
||||
**Validation:** Backend validates on handler entry (required fields, date formats). Frontend validates form state (required inputs, date picker constraints). No shared validation schema.
|
||||
|
||||
**Authentication:** Session cookie with JWT payload. Middleware on protected routes extracts userID from context. Frontend stores user in state and refetches on navigation.
|
||||
|
||||
**Internationalization:** Frontend uses i18n-next with translation files in `frontend/src/i18n/` (de.json, en.json). User preference stored in database (preferred_locale), synced via useAuth on login.
|
||||
|
||||
**CORS:** Enabled for localhost:5173 (Vite dev) and localhost:8080 (Docker production). Credentials included in requests.
|
||||
|
||||
---
|
||||
|
||||
*Architecture analysis: 2026-03-11*
|
||||
182
.planning/codebase/CONCERNS.md
Normal file
182
.planning/codebase/CONCERNS.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# Codebase Concerns
|
||||
|
||||
**Analysis Date:** 2026-03-11
|
||||
|
||||
## Tech Debt
|
||||
|
||||
**Date parsing without validation:**
|
||||
- Issue: In `UpdateBudget` handler, date parsing errors are silently ignored with blank identifiers
|
||||
- Files: `backend/internal/api/handlers.go:322-323`
|
||||
- Impact: Invalid dates passed to the server may silently become zero values, leading to incorrect budget date ranges. The API accepts malformed dates without reporting errors.
|
||||
- Fix approach: Check errors from `time.Parse()` and return HTTP 400 with a descriptive error message before updating the budget.
|
||||
|
||||
**Silent error suppression in API client:**
|
||||
- Issue: In the fetch error handler, JSON parse errors are silently swallowed with `.catch(() => ({}))`
|
||||
- Files: `frontend/src/lib/api.ts:14`
|
||||
- Impact: If the server returns non-JSON error responses (e.g., plain text HTML error pages), the error message is lost and users see a generic error. Network/server issues become indistinguishable from API errors.
|
||||
- Fix approach: Log the raw response or preserve error details. Return a meaningful error message when JSON parsing fails.
|
||||
|
||||
**Hardcoded default database URL with plaintext credentials:**
|
||||
- Issue: Default database URL includes plaintext username and password in code
|
||||
- Files: `backend/cmd/server/main.go:33`
|
||||
- Impact: Credentials appear in logs, version control history, and error messages. The `sslmode=disable` allows unencrypted database connections by default.
|
||||
- Fix approach: Remove the default fallback, require `DATABASE_URL` to be explicitly set in production. Use `sslmode=require` and implement proper secrets management.
|
||||
|
||||
**Placeholder default session secret:**
|
||||
- Issue: Session secret defaults to `"change-me-in-production"` if not set via environment
|
||||
- Files: `backend/cmd/server/main.go:34`
|
||||
- Impact: If `SESSION_SECRET` is not configured in deployment, all sessions use a hardcoded insecure secret, allowing anyone with the code to forge tokens.
|
||||
- Fix approach: Fail startup with a clear error if `SESSION_SECRET` is not set. Make it required for production builds.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
**Hardcoded CORS whitelist for localhost only:**
|
||||
- Risk: CORS only allows `http://localhost:5173` and `http://localhost:8080`, but this is production code that will be deployed. The hardcoded origins require code changes to deploy.
|
||||
- Files: `backend/internal/api/router.go:20-21`
|
||||
- Current mitigation: None (will break in production unless modified)
|
||||
- Recommendations: Make CORS origins configurable via environment variables. Use a secure default (e.g., `AllowedOrigins: []string{}` or read from env). Document required configuration.
|
||||
|
||||
**Insufficient cookie security flags:**
|
||||
- Risk: Session cookies lack the `Secure` flag, which is critical for HTTPS environments
|
||||
- Files: `backend/internal/auth/auth.go:91-98`
|
||||
- Current mitigation: `HttpOnly` and `SameSite=Lax` are set, but `Secure` flag is missing
|
||||
- Recommendations: Add `Secure: true` to `SetSessionCookie()`. Make this configurable for development vs. production.
|
||||
|
||||
**No CSRF protection:**
|
||||
- Risk: API endpoints accept POST/PUT/DELETE from any origin that passes CORS check. No CSRF tokens in use.
|
||||
- Files: `backend/internal/api/router.go` (all mutation endpoints)
|
||||
- Current mitigation: SameSite=Lax provides some protection for browser-based attacks, but is insufficient for API security
|
||||
- Recommendations: Implement CSRF token validation for state-changing operations, or use SameSite=Strict if browser support permits.
|
||||
|
||||
**Unencrypted database connections by default:**
|
||||
- Risk: `sslmode=disable` in default DATABASE_URL allows plaintext communication with PostgreSQL
|
||||
- Files: `backend/cmd/server/main.go:33`
|
||||
- Current mitigation: None
|
||||
- Recommendations: Change default to `sslmode=require`. Document that production must use encrypted connections.
|
||||
|
||||
**No input validation on budget/category names:**
|
||||
- Risk: User-provided text fields (name, notes) have no length limits or content validation
|
||||
- Files: `backend/internal/api/handlers.go` (CreateCategory, CreateBudget, CreateBudgetItem accept raw strings)
|
||||
- Current mitigation: Database has TEXT columns but no constraints
|
||||
- Recommendations: Add max length validation (e.g., 255 chars for names, 5000 for notes) before persisting. Return 422 for validation failures.
|
||||
|
||||
**No rate limiting:**
|
||||
- Risk: No rate limiting on authentication endpoints. Brute force attacks on login/register are not mitigated.
|
||||
- Files: `backend/internal/api/router.go` (auth routes have no middleware)
|
||||
- Current mitigation: None
|
||||
- Recommendations: Implement rate limiting middleware per IP or per email address for auth endpoints.
|
||||
|
||||
## Performance Bottlenecks
|
||||
|
||||
**No pagination on category/budget listing:**
|
||||
- Problem: `ListCategories` and `ListBudgets` fetch all records without limits
|
||||
- Files: `backend/internal/db/queries.go:105-124, 163-181`
|
||||
- Cause: Simple SELECT without LIMIT. With thousands of categories/budgets, this becomes slow.
|
||||
- Improvement path: Add LIMIT/OFFSET parameters, return paginated results. Frontend should request by page.
|
||||
|
||||
**N+1 query problem in budget copy:**
|
||||
- Problem: `CopyBudgetItems` calls `GetBudget()` twice (for verification), then does one bulk INSERT. If called frequently, verification queries add unnecessary overhead.
|
||||
- Files: `backend/internal/db/queries.go:304-319`
|
||||
- Cause: Two separate queries to verify ownership before the copy operation
|
||||
- Improvement path: Combine budget verification with the INSERT in a single transaction. Use CHECK constraints or triggers instead of application-level verification.
|
||||
|
||||
**Totals computed in application code every time:**
|
||||
- Problem: `computeTotals()` is called for every `GetBudgetWithItems()` call, doing arithmetic on all items in memory
|
||||
- Files: `backend/internal/db/queries.go:269-301`
|
||||
- Cause: No caching, no database-side aggregation
|
||||
- Improvement path: Pre-compute totals and store in `budget` table, or use PostgreSQL aggregation functions (GROUP BY, SUM) in the query.
|
||||
|
||||
## Fragile Areas
|
||||
|
||||
**Date parsing without type safety:**
|
||||
- Files: `backend/internal/api/handlers.go:260-269, 322-323`
|
||||
- Why fragile: Dates are parsed as strings and silently fail. Different handlers handle errors differently (one validates, one ignores).
|
||||
- Safe modification: Extract date parsing into a helper function that validates and returns errors. Use typed request structs with custom JSON unmarshalers.
|
||||
- Test coverage: No tests visible for invalid date inputs.
|
||||
|
||||
**Budget item deletion cascades (foreign key constraint):**
|
||||
- Files: `backend/migrations/001_initial.sql` (budget_items.category_id has ON DELETE RESTRICT)
|
||||
- Why fragile: Categories cannot be deleted if they have budget items referencing them. Users see opaque "foreign key violation" errors.
|
||||
- Safe modification: Change to ON DELETE SET NULL if nullability is acceptable, or implement soft deletes. Add validation to prevent orphaned categories.
|
||||
- Test coverage: No tests for category deletion scenarios.
|
||||
|
||||
**Async state management in useBudgets hook:**
|
||||
- Files: `frontend/src/hooks/useBudgets.ts`
|
||||
- Why fragile: `selectBudget()` sets loading=true but fetchList doesn't. Race conditions if budget is selected while list is being fetched. No error boundaries.
|
||||
- Safe modification: Unify loading states, cancel pending requests when switching budgets, handle errors explicitly.
|
||||
- Test coverage: No tests for concurrent operations.
|
||||
|
||||
**JSON error handling in API client:**
|
||||
- Files: `frontend/src/lib/api.ts:14`
|
||||
- Why fragile: `.catch(() => ({}))` returns empty object on any error. Downstream code treats empty object as valid response.
|
||||
- Safe modification: Check `res.ok` before calling `.json()`. Return typed error with details preserved.
|
||||
- Test coverage: No tests for malformed response bodies.
|
||||
|
||||
## Missing Critical Features
|
||||
|
||||
**No input validation framework:**
|
||||
- Problem: Handlers validate data individually with ad-hoc checks (empty email/password)
|
||||
- Blocks: Harder to enforce consistent validation across endpoints. SQL injection is mitigated by parameterized queries, but semantic validation is manual.
|
||||
- Impact: Each new endpoint requires defensive programming. Easy to miss validation cases.
|
||||
|
||||
**No password strength requirements:**
|
||||
- Problem: Registration accepts any non-empty password
|
||||
- Blocks: Users can set weak passwords; no minimum length, complexity checks
|
||||
- Impact: Security of accounts depends entirely on user discipline.
|
||||
|
||||
**OIDC not implemented:**
|
||||
- Problem: OIDC endpoints exist but return "not implemented"
|
||||
- Blocks: Alternative authentication methods unavailable
|
||||
- Impact: Feature is advertised in code but non-functional.
|
||||
|
||||
**No email verification:**
|
||||
- Problem: Users can register with any email address; no verification step
|
||||
- Blocks: Typos create invalid accounts. Email communication impossible.
|
||||
- Impact: Poor UX for password resets or account recovery.
|
||||
|
||||
**No transaction support in budget copy:**
|
||||
- Problem: `CopyBudgetItems` is not atomic. If INSERT fails partway, database is left in inconsistent state.
|
||||
- Blocks: Reliable bulk operations
|
||||
- Impact: Data corruption possible during concurrent operations.
|
||||
|
||||
## Test Coverage Gaps
|
||||
|
||||
**No backend unit tests:**
|
||||
- What's not tested: API handlers, database queries, auth logic, business logic
|
||||
- Files: `backend/internal/api/handlers.go`, `backend/internal/db/queries.go`, `backend/internal/auth/auth.go`
|
||||
- Risk: Regressions in critical paths go undetected. Date parsing bugs, permission checks, decimal arithmetic errors are not caught.
|
||||
- Priority: High
|
||||
|
||||
**No frontend component tests:**
|
||||
- What's not tested: useBudgets hook, useAuth hook, form validation, error handling
|
||||
- Files: `frontend/src/hooks/`, `frontend/src/pages/`
|
||||
- Risk: User interactions fail silently. State management bugs are caught only by manual testing.
|
||||
- Priority: High
|
||||
|
||||
**No integration tests:**
|
||||
- What's not tested: Complete workflows (register → create budget → add items → view totals)
|
||||
- Files: All
|
||||
- Risk: Multiple components may work individually but fail together. Database constraints aren't exercised.
|
||||
- Priority: Medium
|
||||
|
||||
**No E2E tests:**
|
||||
- What's not tested: Full user flows in a real browser
|
||||
- Files: All
|
||||
- Risk: Frontend-specific issues (layout, accessibility, form submission) are missed.
|
||||
- Priority: Medium
|
||||
|
||||
## Dependencies at Risk
|
||||
|
||||
**No version pinning in Go modules:**
|
||||
- Risk: Transitive dependencies can break (jwt library major version bumps, pgx changes)
|
||||
- Impact: Uncontrolled upgrades break API compatibility
|
||||
- Migration plan: Use `go.mod` with explicit versions, run `go mod tidy` in CI. Test with vulnerable versions detected by `govulncheck`.
|
||||
|
||||
**Frontend uses `bun` as package manager:**
|
||||
- Risk: If bun project stalls or has unresolved bugs, switching to npm/pnpm requires rewriting lockfiles
|
||||
- Impact: Vendor lock-in to a newer, less battle-tested tool
|
||||
- Migration plan: Keep package.json syntax compatible. Use `bun add`/`bun remove` instead of editing package.json manually.
|
||||
|
||||
---
|
||||
|
||||
*Concerns audit: 2026-03-11*
|
||||
266
.planning/codebase/CONVENTIONS.md
Normal file
266
.planning/codebase/CONVENTIONS.md
Normal file
@@ -0,0 +1,266 @@
|
||||
# Coding Conventions
|
||||
|
||||
**Analysis Date:** 2026-03-11
|
||||
|
||||
## Naming Patterns
|
||||
|
||||
**Files:**
|
||||
- React components: PascalCase (e.g., `AppLayout.tsx`, `LoginPage.tsx`)
|
||||
- TypeScript utilities/libraries: camelCase (e.g., `useAuth.ts`, `api.ts`, `utils.ts`)
|
||||
- Go packages: lowercase, no underscore (e.g., `auth`, `db`, `models`, `api`)
|
||||
- Go files: descriptive lowercase (e.g., `handlers.go`, `queries.go`, `auth.go`)
|
||||
- Test files: None currently present (testing infrastructure not yet established)
|
||||
|
||||
**Functions:**
|
||||
- React hooks: `use` prefix in camelCase (e.g., `useAuth()`, `useBudgets()`)
|
||||
- Go receiver methods: ExportedName style on receiver (e.g., `(h *Handlers) Register`)
|
||||
- Go internal functions: camelCase for unexported, PascalCase for exported (e.g., `writeJSON()`, `ListCategories()`)
|
||||
- TypeScript functions: camelCase (e.g., `request<T>()`, `cn()`)
|
||||
|
||||
**Variables:**
|
||||
- Local variables: camelCase (e.g., `userID`, `displayName`, `carryover`)
|
||||
- Constants: UPPER_SNAKE_CASE in Go (e.g., `CategoryBill`, `userIDKey`), camelCase in TypeScript (e.g., `API_BASE`)
|
||||
- React state: camelCase for state and setter (e.g., `[user, setUser]`, `[loading, setLoading]`)
|
||||
|
||||
**Types:**
|
||||
- TypeScript interfaces: PascalCase, no `I` prefix (e.g., `User`, `Category`, `Budget`, `BudgetItem`)
|
||||
- TypeScript type aliases: PascalCase (e.g., `CategoryType` = 'bill' | 'variable_expense' | ...)
|
||||
- Go structs: PascalCase (e.g., `User`, `Category`, `Budget`)
|
||||
- Go type enums: PascalCase with prefixed constants (e.g., `CategoryType`, `CategoryBill`, `CategoryIncome`)
|
||||
|
||||
## Code Style
|
||||
|
||||
**Formatting:**
|
||||
- TypeScript: Vite default (2-space indentation implied by no explicit prettier config)
|
||||
- Go: `gofmt` standard (1-tab indentation)
|
||||
- Language-specific linters enforced via `npm run lint` (ESLint) and `go vet`
|
||||
|
||||
**Linting:**
|
||||
- Frontend: ESLint with TypeScript support (`eslint.config.js`)
|
||||
- Extends: `@eslint/js`, `typescript-eslint`, `eslint-plugin-react-hooks`, `eslint-plugin-react-refresh`
|
||||
- Targets: All `.ts` and `.tsx` files
|
||||
- Rules enforced: React hooks rules, React refresh rules
|
||||
- Go: Standard `go vet` (no additional linter configured)
|
||||
|
||||
**TypeScript Compiler Settings:**
|
||||
- `strict: true` - Full strict type checking enabled
|
||||
- `noUnusedLocals: true` - Unused variables are errors
|
||||
- `noUnusedParameters: true` - Unused parameters are errors
|
||||
- `noFallthroughCasesInSwitch: true` - Switch cases must explicitly break or return
|
||||
- `noUncheckedSideEffectImports: true` - Side-effect imports must be explicit
|
||||
- Target: ES2022, Module: ESNext
|
||||
|
||||
## Import Organization
|
||||
|
||||
**TypeScript Order:**
|
||||
1. Third-party React/UI imports (e.g., `from 'react'`, `from 'react-router-dom'`)
|
||||
2. Component/utility imports from `@/` alias
|
||||
3. Type imports (e.g., `type User`)
|
||||
4. CSS/global imports (e.g., `'@/i18n'`)
|
||||
|
||||
Example from `src/components/AppLayout.tsx`:
|
||||
```typescript
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { LayoutDashboard, Tags, Settings, LogOut } from 'lucide-react'
|
||||
import { Sidebar, ... } from '@/components/ui/sidebar'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import type { AuthContext } from '@/hooks/useAuth'
|
||||
```
|
||||
|
||||
**Go Import Order:**
|
||||
1. Standard library imports (e.g., `"context"`, `"encoding/json"`)
|
||||
2. Third-party imports (e.g., `"github.com/go-chi/chi/v5"`)
|
||||
3. Internal imports (e.g., `"simplefinancedash/backend/internal/..."`)
|
||||
|
||||
Example from `backend/internal/api/handlers.go`:
|
||||
```go
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
"simplefinancedash/backend/internal/auth"
|
||||
)
|
||||
```
|
||||
|
||||
**Path Aliases:**
|
||||
- TypeScript: `@/*` maps to `./src/*` (defined in `tsconfig.json`)
|
||||
- Go: Full module paths from module name `simplefinancedash/backend`
|
||||
|
||||
## Error Handling
|
||||
|
||||
**TypeScript Patterns:**
|
||||
- Silent error suppression with fallback (preferred for non-critical operations):
|
||||
```typescript
|
||||
try {
|
||||
const u = await auth.me()
|
||||
setUser(u)
|
||||
} catch {
|
||||
setUser(null) // Fallback without logging
|
||||
}
|
||||
```
|
||||
- Custom error class for API errors:
|
||||
```typescript
|
||||
export class ApiError extends Error {
|
||||
status: number
|
||||
constructor(status: number, message: string) {
|
||||
super(message)
|
||||
this.name = 'ApiError'
|
||||
this.status = status
|
||||
}
|
||||
}
|
||||
```
|
||||
- HTTP response error handling in `request<T>()`:
|
||||
```typescript
|
||||
if (!res.ok) {
|
||||
const body = await res.json().catch(() => ({}))
|
||||
throw new ApiError(res.status, body.error || res.statusText)
|
||||
}
|
||||
```
|
||||
|
||||
**Go Patterns:**
|
||||
- Wrap errors with context using `fmt.Errorf("context: %w", err)` for traceability:
|
||||
```go
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("hashing password: %w", err)
|
||||
}
|
||||
```
|
||||
- Return early on error in handlers:
|
||||
```go
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, "invalid request body")
|
||||
return
|
||||
}
|
||||
```
|
||||
- Silent errors in non-critical flows with zero-value fallback:
|
||||
```go
|
||||
var exists bool
|
||||
err := pool.QueryRow(...).Scan(&exists)
|
||||
if err != nil {
|
||||
exists = false // Assume false on error
|
||||
}
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
**Framework:** No structured logging library. Uses `log` package in Go, `console` in TypeScript.
|
||||
|
||||
**Patterns:**
|
||||
- Go: `log.Printf()` for informational messages (e.g., "Server starting on :8080"), `log.Fatalf()` for fatal errors during startup
|
||||
- Located in `backend/cmd/server/main.go`
|
||||
- TypeScript: No logging in production code. Errors silently caught and handled with UI fallbacks.
|
||||
|
||||
## Comments
|
||||
|
||||
**When to Comment:**
|
||||
- Go: Comments for exported types/functions (standard Go convention)
|
||||
- Comment each exported function above its definition
|
||||
- Used in `backend/internal/auth/auth.go` for public auth functions
|
||||
- TypeScript: Minimal comments; code clarity preferred
|
||||
- JSDoc comments not currently used
|
||||
- Comments only for non-obvious logic or integration details
|
||||
|
||||
**Current Usage:**
|
||||
- Go handler comments: "// Auth Handlers", "// Helpers" as section dividers in `api/handlers.go`
|
||||
- TypeScript code: Inline comments absent (code is self-documenting with clear naming)
|
||||
|
||||
## Function Design
|
||||
|
||||
**Size:**
|
||||
- Go handler functions: 10-50 lines (moderate size with early returns for validation)
|
||||
- Go query functions: Variable (from 5 lines for simple queries to 30+ for complex operations like `GetBudgetWithItems()`)
|
||||
- TypeScript hooks: 20-40 lines per hook (following React patterns with useState/useEffect)
|
||||
|
||||
**Parameters:**
|
||||
- Go: Receiver pattern for struct methods, context as first parameter in database functions
|
||||
- Example: `func (q *Queries) CreateUser(ctx context.Context, email, passwordHash, displayName, locale string) (*models.User, error)`
|
||||
- TypeScript: Props as single typed object, destructured in function signature
|
||||
- Example: `export function AppLayout({ auth, children }: Props)`
|
||||
- React hooks: No parameters passed to hooks; they use internal state
|
||||
|
||||
**Return Values:**
|
||||
- Go: Multiple returns with error as last return (e.g., `(*models.User, error)`)
|
||||
- TypeScript: Single return object containing multiple pieces of state/functions
|
||||
- Example: `useBudgets()` returns `{ list, current, loading, fetchList, selectBudget, setCurrent }`
|
||||
|
||||
## Module Design
|
||||
|
||||
**Exports:**
|
||||
- Go: Exported types/functions start with capital letter (PascalCase)
|
||||
- Non-exported functions use camelCase
|
||||
- Pattern: Define helpers after main exports in same file
|
||||
- TypeScript: Named exports for components/utilities, default exports for page components
|
||||
- Example page export: `export default function App()`
|
||||
- Example utility export: `export class ApiError extends Error`
|
||||
- Example hook export: `export function useAuth()`
|
||||
|
||||
**Barrel Files:**
|
||||
- No barrel files currently used (no `index.ts` re-exports in `src/lib`, `src/hooks`, etc.)
|
||||
- Each utility/hook imported directly from its file
|
||||
- Components from shadcn/ui imported from `@/components/ui/[component-name]`
|
||||
|
||||
## API Client Pattern
|
||||
|
||||
**Location:** `frontend/src/lib/api.ts`
|
||||
|
||||
**Structure:**
|
||||
```typescript
|
||||
// 1. Helper function for all requests
|
||||
async function request<T>(path: string, options?: RequestInit): Promise<T> { ... }
|
||||
|
||||
// 2. Custom error class
|
||||
export class ApiError extends Error { ... }
|
||||
|
||||
// 3. Type definitions
|
||||
export interface User { ... }
|
||||
export interface Category { ... }
|
||||
|
||||
// 4. Namespace objects for endpoint groups
|
||||
export const auth = {
|
||||
register: (...) => request<User>(...),
|
||||
login: (...) => request<User>(...),
|
||||
...
|
||||
}
|
||||
|
||||
export const categories = {
|
||||
list: () => request<Category[]>(...),
|
||||
create: (...) => request<Category>(...),
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
This pattern provides:
|
||||
- Type safety with generic `request<T>`
|
||||
- Automatic error handling and JSON parsing
|
||||
- Organized endpoint grouping by resource
|
||||
- Consistent authentication (cookies via `credentials: 'include'`)
|
||||
|
||||
## React Component Pattern
|
||||
|
||||
**Props Pattern:**
|
||||
- Define interface above component: `interface Props { ... }`
|
||||
- Destructure in function signature: `export function Component({ prop1, prop2 }: Props)`
|
||||
- Type children explicitly: `children: React.ReactNode`
|
||||
|
||||
Example from `AppLayout.tsx`:
|
||||
```typescript
|
||||
interface Props {
|
||||
auth: AuthContext
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export function AppLayout({ auth, children }: Props) { ... }
|
||||
```
|
||||
|
||||
**Hook Usage:**
|
||||
- Always call hooks at top level of component
|
||||
- Use `useCallback` for memoized functions passed to child components
|
||||
- Combine related state with custom hooks (e.g., `useAuth()`, `useBudgets()`)
|
||||
|
||||
---
|
||||
|
||||
*Convention analysis: 2026-03-11*
|
||||
119
.planning/codebase/INTEGRATIONS.md
Normal file
119
.planning/codebase/INTEGRATIONS.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# External Integrations
|
||||
|
||||
## Database: PostgreSQL 16
|
||||
|
||||
**Location**: `backend/internal/db/db.go`, `backend/internal/db/queries.go`
|
||||
|
||||
### Connection
|
||||
|
||||
- **Driver**: `github.com/jackc/pgx/v5` (pgx connection pool)
|
||||
- **Default URL**: `postgres://simplefin:simplefin@localhost:5432/simplefindb?sslmode=disable`
|
||||
- **Environment Variable**: `DATABASE_URL`
|
||||
- **Connection Pool**: `pgxpool.Pool` with automatic management
|
||||
|
||||
### Schema
|
||||
|
||||
**Tables** (from `backend/migrations/001_initial.sql`):
|
||||
|
||||
1. **users** — id (UUID), email, password_hash, oidc_subject, display_name, preferred_locale, timestamps
|
||||
2. **categories** — id (UUID), user_id (FK), name, type (enum), icon, sort_order, timestamps
|
||||
3. **budgets** — id (UUID), user_id (FK), name, start_date, end_date, currency, carryover_amount, timestamps
|
||||
4. **budget_items** — id (UUID), budget_id (FK), category_id (FK), budgeted_amount, actual_amount, notes, timestamps
|
||||
5. **schema_migrations** — version tracking
|
||||
|
||||
**Enum**: `category_type` — bill, variable_expense, debt, saving, investment, income
|
||||
|
||||
**Numeric precision**: `NUMERIC(12, 2)` in PostgreSQL, `shopspring/decimal` in Go
|
||||
|
||||
### Migration Runner
|
||||
|
||||
- Custom implementation in `backend/internal/db/db.go`
|
||||
- Reads `.sql` files from embedded filesystem (`embed.FS`)
|
||||
- Tracks applied versions in `schema_migrations` table
|
||||
- Sequential numbering: `001_initial.sql`, `002_...`, etc.
|
||||
|
||||
## Authentication
|
||||
|
||||
### Local Auth (Active)
|
||||
|
||||
**Location**: `backend/internal/auth/auth.go`
|
||||
|
||||
- **Password hashing**: bcrypt with `DefaultCost`
|
||||
- **Session**: JWT (HS256) in HTTP-only cookie
|
||||
- **Secret**: `SESSION_SECRET` environment variable
|
||||
- **Token TTL**: 7 days
|
||||
- **Cookie**: HttpOnly, SameSite=Lax, MaxAge=7 days
|
||||
|
||||
**Routes**:
|
||||
- `POST /api/auth/register` — Create account
|
||||
- `POST /api/auth/login` — Login
|
||||
- `POST /api/auth/logout` — Clear cookie
|
||||
- `GET /api/auth/me` — Current user
|
||||
|
||||
### OIDC (Planned, Not Implemented)
|
||||
|
||||
**Location**: `backend/internal/api/handlers.go` (stubs returning 501)
|
||||
|
||||
- Routes: `GET /api/auth/oidc`, `GET /api/auth/oidc/callback`
|
||||
- Environment variables defined in `compose.yml`: `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`
|
||||
- DB support ready: `oidc_subject` column, `GetUserByOIDCSubject()`, `UpsertOIDCUser()` queries
|
||||
|
||||
## CORS
|
||||
|
||||
**Location**: `backend/internal/api/router.go`
|
||||
|
||||
```go
|
||||
AllowedOrigins: ["http://localhost:5173", "http://localhost:8080"]
|
||||
AllowedMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
|
||||
AllowedHeaders: ["Content-Type"]
|
||||
AllowCredentials: true
|
||||
```
|
||||
|
||||
## Frontend API Client
|
||||
|
||||
**Location**: `frontend/src/lib/api.ts`
|
||||
|
||||
- Base URL: `/api` (proxied to `http://localhost:8080` via Vite in dev)
|
||||
- Uses native Fetch API with `credentials: 'include'`
|
||||
- Custom `ApiError` class for error handling
|
||||
- Typed endpoints: `auth.*`, `categories.*`, `budgets.*`, `budgetItems.*`, `settings.*`
|
||||
|
||||
## Internationalization (i18n)
|
||||
|
||||
**Location**: `frontend/src/i18n/`
|
||||
|
||||
- **Library**: i18next + react-i18next
|
||||
- **Languages**: English (en, default), German (de)
|
||||
- **Keys**: ~87 per language (auth, navigation, dashboard, budget, categories, settings)
|
||||
- **User preference**: Stored in DB (`preferred_locale` column)
|
||||
|
||||
## Deployment
|
||||
|
||||
### Docker (Multi-stage Build)
|
||||
|
||||
**Location**: `Dockerfile`
|
||||
|
||||
1. **Stage 1** (bun): Build frontend → `frontend/dist`
|
||||
2. **Stage 2** (golang:1.24-alpine): Build Go binary with embedded frontend + migrations
|
||||
3. **Stage 3** (alpine): Runtime with `ca-certificates`, port 8080
|
||||
|
||||
### Docker Compose
|
||||
|
||||
**Location**: `compose.yml`
|
||||
|
||||
- **app**: Go server (:8080), depends on db
|
||||
- **db**: PostgreSQL 16 (:5432), health check, `pgdata` volume
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Purpose | Required |
|
||||
|----------|---------|----------|
|
||||
| `DATABASE_URL` | PostgreSQL connection string | Yes |
|
||||
| `SESSION_SECRET` | JWT signing secret | Yes (production) |
|
||||
| `OIDC_ISSUER` | OIDC provider URL | No (future) |
|
||||
| `OIDC_CLIENT_ID` | OIDC client ID | No (future) |
|
||||
| `OIDC_CLIENT_SECRET` | OIDC client secret | No (future) |
|
||||
|
||||
## External Services
|
||||
|
||||
**None currently integrated.** No third-party APIs, webhooks, payment processors, email providers, or message queues.
|
||||
130
.planning/codebase/STACK.md
Normal file
130
.planning/codebase/STACK.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# Technology Stack
|
||||
|
||||
**Analysis Date:** 2026-03-11
|
||||
|
||||
## Languages
|
||||
|
||||
**Primary:**
|
||||
- Go 1.25.0 - Backend REST API, SPA server, database migrations
|
||||
- TypeScript 5.9.3 - Frontend React application, type-safe client code
|
||||
- SQL - Database schema and migrations (PostgreSQL)
|
||||
|
||||
**Secondary:**
|
||||
- HTML/CSS - Frontend markup and styling (via React/Tailwind)
|
||||
- JavaScript - Runtime for frontend (via TypeScript compilation)
|
||||
|
||||
## Runtime
|
||||
|
||||
**Environment:**
|
||||
- Go 1.25.0 - Backend runtime (production: Alpine-based Docker)
|
||||
- Node.js 19+ (implied via Bun) - Frontend build only
|
||||
- Bun 1.x - JavaScript package manager and runtime
|
||||
|
||||
**Package Manager:**
|
||||
- Bun - JavaScript/TypeScript package manager
|
||||
- Lockfile: `frontend/bun.lock` (present)
|
||||
- Go modules - Go dependency management
|
||||
- Lockfile: `backend/go.mod`, `backend/go.sum` (present)
|
||||
|
||||
## Frameworks
|
||||
|
||||
**Core:**
|
||||
- React 19.2.0 - Frontend UI framework
|
||||
- Chi v5.2.5 - Go HTTP router with middleware support
|
||||
- Vite 7.3.1 - Frontend build tool and dev server
|
||||
|
||||
**UI/Styling:**
|
||||
- Tailwind CSS 4.2.1 - Utility-first CSS framework with Vite plugin
|
||||
- shadcn/ui 4.0.0 - Component library built on Radix UI
|
||||
- Radix UI 1.4.3 - Headless UI component primitives
|
||||
|
||||
**Charting/Data Visualization:**
|
||||
- Recharts 2.15.4 - React charting library for budget visualization
|
||||
|
||||
**Internationalization:**
|
||||
- i18next 25.8.14 - Frontend i18n framework
|
||||
- react-i18next 16.5.6 - React bindings for i18next
|
||||
|
||||
**Routing:**
|
||||
- React Router DOM 7.13.1 - Frontend client-side routing
|
||||
|
||||
## Key Dependencies
|
||||
|
||||
**Critical:**
|
||||
- jackc/pgx/v5 v5.8.0 - PostgreSQL driver and connection pooling (pgxpool)
|
||||
- golang-jwt/jwt/v5 v5.3.1 - JWT token generation and validation for session auth
|
||||
- golang.org/x/crypto v0.48.0 - bcrypt password hashing
|
||||
|
||||
**Infrastructure:**
|
||||
- chi/v5 v5.2.5 - HTTP router with CORS and compression middleware
|
||||
- chi/cors v1.2.2 - CORS middleware for Go
|
||||
- google/uuid v1.6.0 - UUID generation for database IDs
|
||||
- shopspring/decimal v1.4.0 - Precise decimal arithmetic for currency amounts
|
||||
- tailwindcss/vite v4.2.1 - Tailwind CSS Vite integration
|
||||
|
||||
**Frontend Utilities:**
|
||||
- clsx 2.1.1 - Conditional className utility
|
||||
- tailwind-merge 3.5.0 - Merge and deduplicate Tailwind CSS classes
|
||||
- class-variance-authority 0.7.1 - Type-safe component variant system
|
||||
- lucide-react 0.577.0 - Icon library for React
|
||||
- @fontsource-variable/geist 5.2.8 - Geist variable font
|
||||
|
||||
**Dev Tools:**
|
||||
- TypeScript ESLint 8.48.0 - Linting for TypeScript/JavaScript
|
||||
- ESLint 9.39.1 - Base linting framework
|
||||
- @vitejs/plugin-react 5.1.1 - Vite plugin for React (JSX/Fast Refresh)
|
||||
|
||||
## Configuration
|
||||
|
||||
**Environment:**
|
||||
- Configuration via environment variables:
|
||||
- `DATABASE_URL` - PostgreSQL connection string (default: `postgres://simplefin:simplefin@localhost:5432/simplefindb?sslmode=disable`)
|
||||
- `SESSION_SECRET` - Secret for JWT signing (default: `change-me-in-production`)
|
||||
- `PORT` - Server port (default: `8080`)
|
||||
- `OIDC_ISSUER` - OIDC provider issuer URL (optional)
|
||||
- `OIDC_CLIENT_ID` - OIDC client identifier (optional)
|
||||
- `OIDC_CLIENT_SECRET` - OIDC client secret (optional)
|
||||
- Env file: Not detected - uses environment variables at runtime
|
||||
|
||||
**Build:**
|
||||
- Frontend: `frontend/vite.config.ts` - Vite configuration with React and Tailwind plugins
|
||||
- Frontend: `frontend/tsconfig.app.json` - TypeScript compiler options (ES2022, strict mode)
|
||||
- Frontend: `frontend/tsconfig.node.json` - TypeScript config for build tools
|
||||
- Frontend: `frontend/eslint.config.js` - ESLint configuration
|
||||
- Frontend: `frontend/components.json` - shadcn/ui component configuration
|
||||
- Backend: Multi-stage Docker build via `Dockerfile` (Bun frontend → Go backend → Alpine)
|
||||
|
||||
## Platform Requirements
|
||||
|
||||
**Development:**
|
||||
- Docker and Docker Compose (for local PostgreSQL)
|
||||
- Go 1.25.0+
|
||||
- Bun or Node.js 19+ (frontend)
|
||||
- PostgreSQL 16 compatible (for `docker compose up db`)
|
||||
|
||||
**Production:**
|
||||
- Alpine Linux (via Docker image from `Dockerfile`)
|
||||
- PostgreSQL 16 (external database)
|
||||
- Single Docker image deployment: embeds frontend build and migrations
|
||||
|
||||
## Deployment
|
||||
|
||||
**Build Strategy:**
|
||||
- Multi-stage Docker build (file: `Dockerfile`):
|
||||
1. Stage 1 (Bun): Build frontend React app → `frontend/dist`
|
||||
2. Stage 2 (Go): Download Go dependencies, embed frontend dist and migrations, compile binary
|
||||
3. Stage 3 (Alpine): Minimal runtime image with single `/server` binary
|
||||
- Binary embeds:
|
||||
- Frontend static files via `embed.FS` at `cmd/server/frontend_dist/`
|
||||
- SQL migrations via `embed.FS` at `cmd/server/migrations/`
|
||||
|
||||
**Compose Configuration:**
|
||||
- File: `compose.yml`
|
||||
- Services:
|
||||
- `app`: Built from Dockerfile, exposes port 8080
|
||||
- `db`: PostgreSQL 16 Alpine, exposes port 5432, volume: `pgdata`
|
||||
- Health checks: PostgreSQL readiness check before app startup
|
||||
|
||||
---
|
||||
|
||||
*Stack analysis: 2026-03-11*
|
||||
127
.planning/codebase/STRUCTURE.md
Normal file
127
.planning/codebase/STRUCTURE.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# Codebase Structure
|
||||
|
||||
## Directory Layout
|
||||
|
||||
```
|
||||
SimpleFinanceDash/
|
||||
├── backend/ # Go backend application
|
||||
│ ├── cmd/server/ # Application entrypoint
|
||||
│ │ └── main.go # Server init, embeds frontend & migrations
|
||||
│ ├── internal/
|
||||
│ │ ├── api/ # HTTP handlers and routing
|
||||
│ │ │ ├── router.go # Chi router setup, route definitions
|
||||
│ │ │ └── handlers.go # All HTTP request handlers (~480 lines)
|
||||
│ │ ├── auth/ # Authentication logic
|
||||
│ │ │ └── auth.go # JWT/session, bcrypt hashing
|
||||
│ │ ├── db/ # Database layer
|
||||
│ │ │ ├── db.go # Connection pool, migration runner
|
||||
│ │ │ └── queries.go # All database queries (~350 lines, 22 functions)
|
||||
│ │ └── models/ # Domain types
|
||||
│ │ └── models.go # User, Category, Budget, BudgetItem, BudgetTotals
|
||||
│ ├── migrations/ # SQL migration files
|
||||
│ │ └── 001_initial.sql # Initial schema (5 tables, indexes, enums)
|
||||
│ ├── go.mod # Go module (v1.25.0)
|
||||
│ └── go.sum # Dependency lockfile
|
||||
│
|
||||
├── frontend/ # React + Vite + TypeScript
|
||||
│ ├── src/
|
||||
│ │ ├── App.tsx # Root component with routing
|
||||
│ │ ├── main.tsx # React 19 entry point
|
||||
│ │ ├── index.css # Global styles, Tailwind + CSS variables
|
||||
│ │ ├── components/ # UI and feature components
|
||||
│ │ │ ├── ui/ # shadcn/ui components (18 files)
|
||||
│ │ │ │ ├── button.tsx, card.tsx, dialog.tsx, input.tsx
|
||||
│ │ │ │ ├── select.tsx, table.tsx, tabs.tsx, sidebar.tsx
|
||||
│ │ │ │ ├── avatar.tsx, badge.tsx, chart.tsx, separator.tsx
|
||||
│ │ │ │ ├── dropdown-menu.tsx, scroll-area.tsx, sheet.tsx
|
||||
│ │ │ │ ├── skeleton.tsx, spinner.tsx, tooltip.tsx
|
||||
│ │ │ └── [Feature components]
|
||||
│ │ │ ├── AppLayout.tsx # Main layout wrapper
|
||||
│ │ │ ├── BudgetSetup.tsx # Budget creation form
|
||||
│ │ │ ├── BillsTracker.tsx # Bills tracking
|
||||
│ │ │ ├── VariableExpenses.tsx
|
||||
│ │ │ ├── DebtTracker.tsx
|
||||
│ │ │ ├── FinancialOverview.tsx
|
||||
│ │ │ ├── ExpenseBreakdown.tsx
|
||||
│ │ │ └── AvailableBalance.tsx
|
||||
│ │ ├── pages/ # Route-level views
|
||||
│ │ │ ├── DashboardPage.tsx # Main dashboard
|
||||
│ │ │ ├── CategoriesPage.tsx # Category management
|
||||
│ │ │ ├── LoginPage.tsx # Login form
|
||||
│ │ │ ├── RegisterPage.tsx # Registration form
|
||||
│ │ │ └── SettingsPage.tsx # User settings
|
||||
│ │ ├── hooks/ # Custom React hooks
|
||||
│ │ │ ├── useAuth.ts # Auth state management
|
||||
│ │ │ ├── useBudgets.ts # Budget data fetching & selection
|
||||
│ │ │ └── use-mobile.ts # Mobile detection
|
||||
│ │ ├── lib/ # Utilities and API client
|
||||
│ │ │ ├── api.ts # Typed REST client
|
||||
│ │ │ ├── utils.ts # cn() className utility
|
||||
│ │ │ └── format.ts # Currency formatting
|
||||
│ │ └── i18n/ # Internationalization
|
||||
│ │ ├── index.ts # i18next setup
|
||||
│ │ ├── en.json # English translations
|
||||
│ │ └── de.json # German translations
|
||||
│ ├── package.json # Dependencies (bun)
|
||||
│ ├── vite.config.ts # Build config + API proxy
|
||||
│ ├── tsconfig.json # TypeScript config
|
||||
│ ├── eslint.config.js # ESLint config
|
||||
│ ├── components.json # shadcn/ui config
|
||||
│ └── bun.lock # Bun lockfile
|
||||
│
|
||||
├── compose.yml # Docker Compose (PostgreSQL + app)
|
||||
├── Dockerfile # Multi-stage build
|
||||
├── CLAUDE.md # Project guidance
|
||||
├── PRD.md # Product Requirements Document
|
||||
└── README.md # Project overview
|
||||
```
|
||||
|
||||
## Key File Locations
|
||||
|
||||
| Component | Location | Purpose |
|
||||
|-----------|----------|---------|
|
||||
| Server Entrypoint | `backend/cmd/server/main.go` | DB pool init, migrations, embed frontend |
|
||||
| API Routes | `backend/internal/api/router.go` | Chi router, middleware, route definitions |
|
||||
| HTTP Handlers | `backend/internal/api/handlers.go` | Auth, categories, budgets, settings handlers |
|
||||
| Database Layer | `backend/internal/db/db.go` | Connection pool, migration runner |
|
||||
| Queries | `backend/internal/db/queries.go` | 22 database functions using pgx |
|
||||
| Models | `backend/internal/models/models.go` | User, Category, Budget, BudgetItem, BudgetTotals |
|
||||
| Auth | `backend/internal/auth/auth.go` | JWT generation/validation, bcrypt |
|
||||
| Migrations | `backend/migrations/001_initial.sql` | PostgreSQL schema |
|
||||
| Frontend Root | `frontend/src/App.tsx` | React router, auth check, page routes |
|
||||
| API Client | `frontend/src/lib/api.ts` | Typed REST client |
|
||||
| i18n | `frontend/src/i18n/index.ts` | i18next setup (en, de) |
|
||||
| Styling | `frontend/src/index.css` | Tailwind + CSS variables (dark/light) |
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
### Backend (Go)
|
||||
|
||||
- **Packages**: lowercase, single word (`api`, `auth`, `db`, `models`)
|
||||
- **Exported functions**: PascalCase (`GetBudget`, `CreateUser`)
|
||||
- **Private functions**: camelCase
|
||||
- **Constants**: PascalCase (`CategoryBill`, `userIDKey`)
|
||||
- **Files**: snake_case (`handlers.go`, `router.go`, `queries.go`)
|
||||
- **Types**: PascalCase (`User`, `BudgetItem`)
|
||||
|
||||
### Frontend (TypeScript/React)
|
||||
|
||||
- **Components**: PascalCase (`DashboardPage.tsx`, `BudgetSetup.tsx`)
|
||||
- **Pages**: PascalCase with "Page" suffix (`LoginPage.tsx`, `CategoriesPage.tsx`)
|
||||
- **Hooks**: camelCase with "use" prefix (`useAuth.ts`, `useBudgets.ts`)
|
||||
- **Utilities**: camelCase (`utils.ts`, `format.ts`)
|
||||
- **UI components**: kebab-case matching shadcn convention (`button.tsx`, `dropdown-menu.tsx`)
|
||||
- **i18n keys**: nested dot notation (`auth.login`, `dashboard.financialOverview`)
|
||||
|
||||
## Configuration Files
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `backend/go.mod` | Go 1.25.0, 6 main dependencies |
|
||||
| `frontend/package.json` | Node deps, bun package manager |
|
||||
| `frontend/vite.config.ts` | Vite build, React plugin, API proxy to :8080 |
|
||||
| `frontend/tsconfig.json` | Path alias: `@/*` → `./src/*` |
|
||||
| `frontend/components.json` | shadcn/ui config (radix-nova, Lucide icons) |
|
||||
| `frontend/eslint.config.js` | TypeScript + React hooks rules |
|
||||
| `compose.yml` | PostgreSQL 16 + Go app services |
|
||||
| `Dockerfile` | Multi-stage: bun → Go → Alpine |
|
||||
125
.planning/codebase/TESTING.md
Normal file
125
.planning/codebase/TESTING.md
Normal file
@@ -0,0 +1,125 @@
|
||||
# Testing
|
||||
|
||||
## Current State
|
||||
|
||||
**No tests exist yet.** The codebase has zero test files — no Go tests, no frontend component tests, no E2E tests. CLAUDE.md references test commands but the infrastructure is not set up.
|
||||
|
||||
## Planned Frameworks (from CLAUDE.md)
|
||||
|
||||
### Backend (Go)
|
||||
|
||||
- **Framework**: Go standard library `testing` package
|
||||
- **Command**: `cd backend && go test ./...`
|
||||
- **Package-level**: `cd backend && go test ./internal/api/...`
|
||||
- **No test files exist** (`*_test.go`)
|
||||
|
||||
### Frontend
|
||||
|
||||
- **Unit/Component Testing**: Vitest
|
||||
- Command: `cd frontend && bun vitest`
|
||||
- Not yet installed (missing from `package.json` devDependencies)
|
||||
- **E2E Testing**: Playwright
|
||||
- Command: `cd frontend && bun playwright test`
|
||||
- Not yet installed
|
||||
|
||||
## Dependencies Needed
|
||||
|
||||
### Frontend (not yet in package.json)
|
||||
|
||||
```json
|
||||
{
|
||||
"devDependencies": {
|
||||
"vitest": "^x.x.x",
|
||||
"@testing-library/react": "^x.x.x",
|
||||
"@testing-library/jest-dom": "^x.x.x",
|
||||
"@playwright/test": "^x.x.x"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Backend
|
||||
|
||||
No additional dependencies needed — Go's `testing` package is built-in.
|
||||
|
||||
## Configuration Files Needed
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `frontend/vitest.config.ts` | Vitest configuration |
|
||||
| `frontend/src/setupTests.ts` | Test environment setup |
|
||||
| `frontend/playwright.config.ts` | E2E test configuration |
|
||||
|
||||
## Recommended Test Structure
|
||||
|
||||
### Backend
|
||||
|
||||
```
|
||||
backend/
|
||||
internal/
|
||||
api/
|
||||
handlers_test.go # Handler tests (HTTP request/response)
|
||||
router_test.go # Route registration tests
|
||||
auth/
|
||||
auth_test.go # JWT and bcrypt tests
|
||||
db/
|
||||
queries_test.go # Database query tests (integration)
|
||||
```
|
||||
|
||||
### Frontend
|
||||
|
||||
```
|
||||
frontend/
|
||||
src/
|
||||
components/
|
||||
BudgetSetup.test.tsx
|
||||
BillsTracker.test.tsx
|
||||
...
|
||||
pages/
|
||||
DashboardPage.test.tsx
|
||||
LoginPage.test.tsx
|
||||
...
|
||||
hooks/
|
||||
useAuth.test.ts
|
||||
useBudgets.test.ts
|
||||
lib/
|
||||
api.test.ts
|
||||
format.test.ts
|
||||
e2e/
|
||||
auth.spec.ts
|
||||
budget.spec.ts
|
||||
```
|
||||
|
||||
## Testing Patterns (Inferred from Architecture)
|
||||
|
||||
### Backend
|
||||
|
||||
- **HTTP handler testing**: Use `net/http/httptest` with Chi router
|
||||
- **Database testing**: Integration tests against PostgreSQL (pgxpool can be mocked or use test DB)
|
||||
- **Auth testing**: Test JWT generation/validation, bcrypt hashing
|
||||
- **Context-based**: All handlers use `context.Context` for user ID propagation
|
||||
|
||||
### Frontend
|
||||
|
||||
- **Component testing**: Vitest + React Testing Library
|
||||
- **Hook testing**: `renderHook` from Testing Library
|
||||
- **API mocking**: Mock fetch or use MSW for `/api/*` endpoints
|
||||
- **i18n in tests**: Wrap components with i18next provider
|
||||
|
||||
## Priority Test Areas
|
||||
|
||||
### Backend (High Priority)
|
||||
|
||||
1. Auth flow (register, login, JWT validation, password hashing)
|
||||
2. Category CRUD (user isolation)
|
||||
3. Budget operations (carryover calculations, item copying)
|
||||
4. Database query correctness
|
||||
5. Middleware (auth required, CORS)
|
||||
|
||||
### Frontend (High Priority)
|
||||
|
||||
1. Auth flow (login/register forms, state management)
|
||||
2. Budget selection & display
|
||||
3. Budget item CRUD
|
||||
4. Currency formatting
|
||||
5. i18n language switching
|
||||
6. Form validation
|
||||
Reference in New Issue
Block a user