267 lines
6.7 KiB
Markdown
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._
|