recipe-manager/ARCHITECTURE.md

267 lines
6.7 KiB
Markdown

# Recipe Manager — Architecture
**Last Updated:** 2026-03-23
**Status:** Initial Design
---
## Tech Stack
### Backend
- **Runtime:** Node.js 22+
- **Language:** TypeScript 5+
- **Framework:** Express.js (REST API)
- **Database:** SQLite 3 (better-sqlite3 for sync operations)
- **Validation:** Zod (schema validation)
- **Testing:** Vitest (unit), Supertest (integration)
### Frontend
- **Framework:** React 18+
- **Build Tool:** Vite
- **Routing:** React Router 6
- **State:** React Context + hooks (Zustand if needed)
- **Forms:** React Hook Form
- **Styling:** Tailwind CSS
- **Testing:** Vitest + Testing Library
### DevOps
- **Containerization:** Docker + Docker Compose
- **Reverse Proxy:** Caddy (auto-HTTPS, simple config)
- **Process Manager:** PM2 (for non-Docker deployments)
- **Backup:** Litestream (continuous SQLite replication)
---
## Architecture Patterns
### 1. Layered Architecture
```
┌─────────────────────────────────────┐
│ Frontend (React) │
│ Components → Hooks → API Client │
└──────────────┬──────────────────────┘
│ HTTP/JSON
┌──────────────┴──────────────────────┐
│ Backend (Express) │
│ Routes → Services → Repositories │
└──────────────┬──────────────────────┘
│ SQL
┌──────────────┴──────────────────────┐
│ Database (SQLite) │
│ recipes, tags, collections │
└─────────────────────────────────────┘
```
### 2. API Design
**RESTful endpoints:**
- `GET /api/recipes` — List recipes (with filters, pagination)
- `GET /api/recipes/:id` — Get single recipe
- `POST /api/recipes` — Create recipe
- `PUT /api/recipes/:id` — Update recipe
- `DELETE /api/recipes/:id` — Delete recipe
- `GET /api/tags` — List tags
- `POST /api/recipes/:id/tags` — Add tag to recipe
**Response format:**
```json
{
"success": true,
"data": { ... },
"error": null
}
```
### 3. Data Model
```sql
CREATE TABLE recipes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT,
ingredients TEXT NOT NULL, -- JSON array
instructions TEXT NOT NULL, -- JSON array of steps
source_url TEXT,
notes TEXT,
servings INTEGER,
prep_time_minutes INTEGER,
cook_time_minutes INTEGER,
created_at INTEGER NOT NULL, -- Unix timestamp
updated_at INTEGER NOT NULL,
last_cooked_at INTEGER
);
CREATE TABLE tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL,
color TEXT -- Hex color for UI
);
CREATE TABLE recipe_tags (
recipe_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
PRIMARY KEY (recipe_id, tag_id),
FOREIGN KEY (recipe_id) REFERENCES recipes(id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
);
CREATE INDEX idx_recipes_title ON recipes(title);
CREATE INDEX idx_recipes_created_at ON recipes(created_at DESC);
CREATE INDEX idx_recipe_tags_recipe ON recipe_tags(recipe_id);
CREATE INDEX idx_recipe_tags_tag ON recipe_tags(tag_id);
```
### 4. Frontend Structure
```
src/
├── components/
│ ├── RecipeCard.tsx
│ ├── RecipeForm.tsx
│ ├── RecipeDetail.tsx
│ ├── CookMode.tsx
│ └── SearchBar.tsx
├── hooks/
│ ├── useRecipes.ts
│ ├── useRecipe.ts
│ └── useTags.ts
├── services/
│ └── api.ts -- API client
├── types/
│ └── recipe.ts -- TypeScript interfaces
├── pages/
│ ├── HomePage.tsx
│ ├── RecipeListPage.tsx
│ ├── RecipeDetailPage.tsx
│ └── CookModePage.tsx
├── App.tsx
└── main.tsx
```
---
## Key Decisions
### Why better-sqlite3 over node-sqlite3?
- Synchronous API (simpler error handling)
- Faster performance (native bindings)
- Better TypeScript support
### Why Tailwind CSS?
- Rapid prototyping
- Agent-friendly (well-documented utility classes)
- No CSS-in-JS overhead
### Why Vite over Create-React-App?
- Faster dev server (ESM-native)
- Simpler config
- Better TypeScript support
### Why Docker Compose?
- Consistent dev/prod environment
- Easy to add services (reverse proxy, backup)
- Paul already uses Docker for fintrove
---
## Development Workflow
### 1. Agent Iteration Loop
```bash
# Agent reads PROJECT.md + ARCHITECTURE.md
# Agent plans next feature
# Agent implements + tests
# Agent commits with conventional commit message
# Agent updates docs/ADR-xxx.md
# Agent reports completion
```
### 2. Testing Strategy
- **Unit tests:** Core business logic (recipe parsing, scaling calculations)
- **Integration tests:** API endpoints
- **E2E tests:** Critical user flows (create recipe → cook mode)
### 3. Commit Conventions
```
feat: add recipe scaling
fix: correct ingredient parsing
docs: update architecture decisions
test: add cook mode E2E tests
chore: bump dependencies
```
---
## Deployment
### Docker Compose Stack
```yaml
services:
recipe-manager:
build: .
ports:
- "3000:3000"
volumes:
- ./data:/app/data # SQLite database
environment:
NODE_ENV: production
DATABASE_PATH: /app/data/recipes.db
caddy:
image: caddy:2
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
depends_on:
- recipe-manager
volumes:
caddy_data:
```
### Backup Strategy
- **Litestream:** Continuous replication to S3/B2
- **Manual exports:** Weekly JSON dumps for portability
- **Git LFS:** Track database snapshots (optional)
---
## Security Considerations
### MVP (Single Household)
- No authentication (behind home network or VPN)
- HTTPS via Caddy (Let's Encrypt)
- Input validation (prevent SQL injection via Zod)
### Future (Multi-User)
- JWT-based auth
- Per-user recipe collections
- Invite-only signup
---
## Performance Goals
- **Page load:** <1s on 3G connection
- **Recipe search:** <100ms for 1000 recipes
- **Cook mode:** Wake lock prevents screen sleep
- **Offline support:** Service worker (PWA) in v1
---
## Open Questions for Agents
1. **Recipe scraping:** Puppeteer vs Playwright vs Cheerio?
2. **Image storage:** Filesystem vs base64 in DB vs S3?
3. **Serving size scaling:** Fractional math library needed?
4. **Export format:** JSON, CSV, or standard (Recipe Schema.org)?
---
_Agents: Add Architecture Decision Records (ADRs) in `docs/` as you make significant choices._