Compare commits
5 Commits
2497747e8b
...
afdc915aa2
| Author | SHA1 | Date |
|---|---|---|
|
|
afdc915aa2 | |
|
|
e6d6d3f776 | |
|
|
c885381d3a | |
|
|
ce839e3ce1 | |
|
|
1a4b984d2c |
39
README.md
39
README.md
|
|
@ -61,20 +61,35 @@ npm run dev:frontend
|
||||||
npm test
|
npm test
|
||||||
```
|
```
|
||||||
|
|
||||||
### Production Deployment
|
### Production Setup & Deployment
|
||||||
|
|
||||||
```bash
|
1. **Build and start containers** (from project root):
|
||||||
# Build and start containers
|
|
||||||
docker-compose up -d
|
|
||||||
|
|
||||||
# View logs
|
```bash
|
||||||
docker-compose logs -f
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
# Stop
|
2. **Check running services:**
|
||||||
docker-compose down
|
|
||||||
```
|
|
||||||
|
|
||||||
Access at `http://localhost:3000` (or `https://recipes.paje.ca` in production).
|
```bash
|
||||||
|
docker-compose ps
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **View container logs:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Access the app:**
|
||||||
|
- Frontend: http://localhost:8080 (default)
|
||||||
|
- Backend API: http://localhost:3000
|
||||||
|
|
||||||
|
5. **Stop services:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose down
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -139,8 +154,8 @@ recipe-manager/
|
||||||
- [Architecture Guide](ARCHITECTURE.md) — Technical decisions & patterns
|
- [Architecture Guide](ARCHITECTURE.md) — Technical decisions & patterns
|
||||||
- [Roadmap](ROADMAP.md) — Planned features & milestones
|
- [Roadmap](ROADMAP.md) — Planned features & milestones
|
||||||
- [Agent Instructions](AGENT_INSTRUCTIONS.md) — How AI agents contribute
|
- [Agent Instructions](AGENT_INSTRUCTIONS.md) — How AI agents contribute
|
||||||
- [API Docs](docs/api.md) — Endpoint reference *(coming soon)*
|
- [API Docs](docs/api.md) — Endpoint reference
|
||||||
- [User Guide](docs/user-guide.md) — How to use the app *(coming soon)*
|
- [User Guide](docs/user-guide.md) — How to use the app
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
30
TODO.md
30
TODO.md
|
|
@ -7,36 +7,12 @@
|
||||||
|
|
||||||
## 🎯 Active Tasks
|
## 🎯 Active Tasks
|
||||||
|
|
||||||
### Backend Setup
|
|
||||||
- [x] Initialize Node.js backend: Create src/backend/, package.json with express, better-sqlite3, zod, vitest. Create src/backend/index.ts with "Hello World" server on port 3000. Verify: npm install && npm run dev (server starts). Commit as "feat(backend): initialize Node.js project with Express"
|
|
||||||
- [x] Set up TypeScript: Create tsconfig.json (strict mode, ES2022, Node16 module resolution). Add build script to package.json. Verify: npm run build succeeds. Commit.
|
|
||||||
- [x] Create SQLite schema: Create src/backend/db/schema.sql with recipes, tags, recipe_tags tables per ARCHITECTURE.md. Create src/backend/db/migrate.ts to apply schema using sql.js (see ADR-001). Verify: npm run migrate creates data/recipes.db. Commit.
|
|
||||||
- [x] Implement recipe CRUD API endpoints
|
|
||||||
- [x] Add Zod validation schemas
|
|
||||||
- [x] Write API integration tests
|
|
||||||
|
|
||||||
### Frontend Setup
|
|
||||||
- [x] Initialize React + Vite project
|
|
||||||
- [x] Configure Tailwind CSS
|
|
||||||
- [x] Set up React Router
|
|
||||||
- [x] Create recipe list page
|
|
||||||
- [x] Create recipe detail/edit page
|
|
||||||
- [x] Implement cook mode UI
|
|
||||||
|
|
||||||
### Features
|
|
||||||
- [x] Tag management (create, assign, filter)
|
|
||||||
- [x] Text search (title + ingredients)
|
|
||||||
- [x] Basic error handling + loading states
|
|
||||||
|
|
||||||
### DevOps
|
### DevOps
|
||||||
- [x] Create Dockerfile for backend
|
- [ ] Test local deployment *(Unable to run `docker compose up` in environment: docker is unavailable. Manual test required on host.)*
|
||||||
- [x] Create Dockerfile for frontend (nginx)
|
|
||||||
- [x] Write docker-compose.yml
|
|
||||||
- [ ] Test local deployment
|
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
- [ ] Write API documentation (docs/api.md)
|
- [x] Write API documentation (docs/api.md)
|
||||||
- [ ] Create user guide (docs/user-guide.md)
|
- [x] Create user guide (docs/user-guide.md)
|
||||||
- [ ] Add setup instructions to README
|
- [ ] Add setup instructions to README
|
||||||
- [ ] Document first architecture decisions (ADRs)
|
- [ ] Document first architecture decisions (ADRs)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
# API Documentation — Recipe Manager
|
||||||
|
|
||||||
|
**Base URL:** `/api`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
No authentication required for MVP (local/private use). Consider adding in v1.0.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
### Recipe Endpoints
|
||||||
|
|
||||||
|
#### List Recipes
|
||||||
|
- **GET `/api/recipes`**
|
||||||
|
- Query params:
|
||||||
|
- `search` *(string, optional)*: Filter recipes by title, description, or ingredients substring
|
||||||
|
- `offset` *(integer, optional)*: Pagination offset (default: 0)
|
||||||
|
- `limit` *(integer, optional)*: Pagination limit (default: 20)
|
||||||
|
- Returns: `{ recipes: Recipe[], total: number }`
|
||||||
|
|
||||||
|
#### Get Single Recipe
|
||||||
|
- **GET `/api/recipes/:id`**
|
||||||
|
- Path param: `id` (number)
|
||||||
|
- Returns: `{ recipe: Recipe }`
|
||||||
|
|
||||||
|
#### Create Recipe
|
||||||
|
- **POST `/api/recipes`**
|
||||||
|
- Body:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"title": "Chocolate Cake",
|
||||||
|
"ingredients": ["2 cups flour", "1 cup sugar"],
|
||||||
|
"instructions": ["Mix dry ingredients", "Bake at 350°F for 30 min"],
|
||||||
|
"tags": [1, 2], // optional tag IDs
|
||||||
|
"servings": 8, // optional
|
||||||
|
"sourceUrl": "https://foosite.com/recipe", // optional
|
||||||
|
"notes": "Best with dark chocolate"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Returns: `{ recipe: Recipe }`
|
||||||
|
|
||||||
|
#### Update Recipe
|
||||||
|
- **PUT `/api/recipes/:id`**
|
||||||
|
- Body: Same as POST
|
||||||
|
- Returns: `{ recipe: Recipe }`
|
||||||
|
|
||||||
|
#### Delete Recipe
|
||||||
|
- **DELETE `/api/recipes/:id`**
|
||||||
|
- Returns: `{ success: true }`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Tag Endpoints
|
||||||
|
|
||||||
|
#### List Tags
|
||||||
|
- **GET `/api/tags`**
|
||||||
|
- Returns: `{ tags: Tag[] }`
|
||||||
|
|
||||||
|
#### Create Tag
|
||||||
|
- **POST `/api/tags`**
|
||||||
|
- Body:
|
||||||
|
```json
|
||||||
|
{ "name": "Dessert" }
|
||||||
|
```
|
||||||
|
- Returns: `{ tag: Tag }`
|
||||||
|
|
||||||
|
#### Update Tag
|
||||||
|
- **PUT `/api/tags/:id`**
|
||||||
|
- Body: `{ "name": "Lunch" }`
|
||||||
|
- Returns: `{ tag: Tag }`
|
||||||
|
|
||||||
|
#### Delete Tag
|
||||||
|
- **DELETE `/api/tags/:id`**
|
||||||
|
- Returns: `{ success: true }`
|
||||||
|
|
||||||
|
#### Assign Tag to Recipe
|
||||||
|
- **POST `/api/tags/recipes/:id/tags`
|
||||||
|
- Body: `{ "tagId": 1 }`
|
||||||
|
- Returns: `{ success: true }`
|
||||||
|
|
||||||
|
#### Remove Tag from Recipe
|
||||||
|
- **DELETE `/api/tags/recipes/:id/tags`
|
||||||
|
- Body: `{ "tagId": 1 }`
|
||||||
|
- Returns: `{ success: true }`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Data Models
|
||||||
|
|
||||||
|
### Recipe
|
||||||
|
```ts
|
||||||
|
interface Recipe {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
ingredients: string[];
|
||||||
|
instructions: string[];
|
||||||
|
servings?: number;
|
||||||
|
tags?: Tag[];
|
||||||
|
sourceUrl?: string;
|
||||||
|
notes?: string;
|
||||||
|
createdAt: string; // ISO date
|
||||||
|
updatedAt: string; // ISO date
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tag
|
||||||
|
```ts
|
||||||
|
interface Tag {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
Responses follow the pattern:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"error": "Description of error"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Validation errors will return HTTP 400 with detailed messages.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Versioning
|
||||||
|
|
||||||
|
Current version: MVP v0.1 (2026-03-24)
|
||||||
|
Future versions may introduce breaking API changes (see ROADMAP.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
_Last updated: 2026-03-24_
|
||||||
|
|
@ -0,0 +1,144 @@
|
||||||
|
# Recipe Manager User Guide
|
||||||
|
|
||||||
|
This guide explains how to use Recipe Manager for day-to-day cooking and recipe organization.
|
||||||
|
|
||||||
|
## What You Can Do
|
||||||
|
|
||||||
|
- Add recipes manually
|
||||||
|
- Edit or delete recipes
|
||||||
|
- Organize recipes with tags
|
||||||
|
- Search recipes by text
|
||||||
|
- Use Cook Mode with step and ingredient checklists
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
1. Open Recipe Manager in your browser.
|
||||||
|
2. From the home page (`Recipes`), click **+ New Recipe** to create your first recipe.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Create a Recipe
|
||||||
|
|
||||||
|
1. Click **+ New Recipe**.
|
||||||
|
2. Fill in the required fields:
|
||||||
|
- **Title**
|
||||||
|
- **Ingredients** (one per line)
|
||||||
|
- **Instructions** (one step per line)
|
||||||
|
3. Optionally add:
|
||||||
|
- Description
|
||||||
|
- Source URL
|
||||||
|
- Notes
|
||||||
|
- Servings
|
||||||
|
- Prep time and cook time
|
||||||
|
- Tags
|
||||||
|
4. Click **Save Recipe**.
|
||||||
|
|
||||||
|
### Tips
|
||||||
|
|
||||||
|
- Keep ingredient lines simple (example: `2 cups flour`).
|
||||||
|
- Keep instruction steps short and clear for easier Cook Mode tracking.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Browse and Search Recipes
|
||||||
|
|
||||||
|
On the **Recipes** page you can:
|
||||||
|
|
||||||
|
- Scroll through recipe cards
|
||||||
|
- Use the search box to find recipes by title, description, or ingredient text
|
||||||
|
- Click **Load More** to view additional recipes when available
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## View, Edit, and Delete a Recipe
|
||||||
|
|
||||||
|
1. Click a recipe card to open details.
|
||||||
|
2. Use **Edit** to update any fields.
|
||||||
|
3. Use **Delete** to remove the recipe permanently.
|
||||||
|
|
||||||
|
> Deleting a recipe cannot be undone.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Manage Tags
|
||||||
|
|
||||||
|
Tags help you group recipes (for example: `Dinner`, `Dessert`, `Quick`, `Vegetarian`).
|
||||||
|
|
||||||
|
### Add tags to a recipe
|
||||||
|
|
||||||
|
1. Open a recipe detail page.
|
||||||
|
2. Edit the recipe.
|
||||||
|
3. Select existing tags (or create new ones if available in the tag selector).
|
||||||
|
4. Save changes.
|
||||||
|
|
||||||
|
### Remove tags
|
||||||
|
|
||||||
|
- Edit the recipe and deselect the tag, then save.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cook Mode
|
||||||
|
|
||||||
|
Cook Mode is designed for active cooking:
|
||||||
|
|
||||||
|
1. Open a recipe.
|
||||||
|
2. Click **Cook Mode**.
|
||||||
|
3. Check off ingredients as you prep.
|
||||||
|
4. Check off instruction steps as you complete them.
|
||||||
|
5. Follow progress indicators until complete.
|
||||||
|
|
||||||
|
### Screen Stay Awake
|
||||||
|
|
||||||
|
Cook Mode uses your browser’s Wake Lock support (when available) to keep your screen on while cooking.
|
||||||
|
|
||||||
|
If wake lock is not supported, the app still works normally.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### I can’t save a recipe
|
||||||
|
|
||||||
|
- Make sure title, ingredients, and instructions are not empty.
|
||||||
|
- Check for network/server errors and try again.
|
||||||
|
|
||||||
|
### I don’t see my latest changes
|
||||||
|
|
||||||
|
- Refresh the page.
|
||||||
|
- Verify the save completed successfully (look for confirmation toast).
|
||||||
|
|
||||||
|
### Search is not finding a recipe
|
||||||
|
|
||||||
|
- Try a simpler keyword from title, description, or ingredient lines.
|
||||||
|
- Confirm the recipe was saved.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Keyboard and Mobile Usage
|
||||||
|
|
||||||
|
- The interface works on desktop and mobile browsers.
|
||||||
|
- In Cook Mode, controls are touch-friendly for kitchen use.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Privacy and Data
|
||||||
|
|
||||||
|
Recipe Manager is self-hosted for household use.
|
||||||
|
|
||||||
|
- Your recipe data is stored in your own deployment database.
|
||||||
|
- No external recipe-sharing account is required for MVP use.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MVP Feature Notes
|
||||||
|
|
||||||
|
Current MVP includes core recipe CRUD, search, tags, and cook mode.
|
||||||
|
|
||||||
|
Planned future features include:
|
||||||
|
|
||||||
|
- Recipe web scraping/import
|
||||||
|
- Advanced filtering
|
||||||
|
- Serving-size scaling
|
||||||
|
- Print-friendly layouts
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import initSqlJs, { Database } from 'sql.js';
|
import initSqlJs, { Database } from 'sql.js';
|
||||||
import { readFileSync, existsSync, mkdirSync } from 'fs';
|
import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
|
||||||
import { dirname } from 'path';
|
import { dirname } from 'path';
|
||||||
|
|
||||||
let dbInstance: Database | null = null;
|
let dbInstance: Database | null = null;
|
||||||
|
|
@ -55,7 +55,7 @@ export function saveDatabase(dbPath: string = 'data/recipes.db'): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write to disk
|
// Write to disk
|
||||||
require('fs').writeFileSync(dbPath, buffer);
|
writeFileSync(dbPath, buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue