diff --git a/doc/index.md b/doc/index.md new file mode 100644 index 0000000..d3348ee --- /dev/null +++ b/doc/index.md @@ -0,0 +1,191 @@ +# Meal Tracker + +A lightweight Flask web application for tracking household meal attendance. Members respond **yes** or **no** to lunch and dinner each day, providing an at-a-glance dashboard of who will be eating at home. + +## Features + +- **User accounts** — register, log in, log out with password hashing (Werkzeug) +- **Households** — multi-tenant; users join an existing household or create a new one on registration +- **Daily meal tracking** — lunch and dinner periods auto-created for each day +- **Dashboard** — see all household members and their responses for a given date +- **History** — browse past days; responses to past dates are read-only +- **Admin controls** — household admin can remove members or delete the entire household +- **Self-service** — users can delete their own account (password confirmation required) +- **SQLite** — zero-dependency storage, database file persisted via Docker volume + +## Technology Stack + +| Layer | Choice | +|-------------|---------------------------------| +| Runtime | Python 3.13 | +| Framework | Flask 3.x | +| Database | SQLite (via Python `sqlite3`) | +| Passwords | Werkzeug `generate_password_hash` / `check_password_hash` | +| Frontend | Jinja2 templates, vanilla CSS | +| Deployment | Docker + docker-compose, Caddy reverse proxy network | + +## Project Structure + +``` +. +├── app.py # Flask application entry point +├── auth.py # Authentication blueprint (login, register, logout) +├── meals.py # Meals blueprint (dashboard, responses, history, admin) +├── models.py # Database models and helpers +├── requirements.txt # Python dependencies +├── Dockerfile # Container image definition +├── docker-compose.yml # Container orchestration +├── watch-for-updates.sh # Auto-deploy poller script +├── .gitea/ # Gitea CI/CD workflows +├── templates/ # Jinja2 HTML templates +│ ├── base.html +│ ├── login.html +│ ├── register.html +│ └── dashboard.html +├── static/ +│ └── style.css +├── tests/ # Pytest test suite +│ ├── conftest.py +│ ├── test_auth.py +│ ├── test_meals.py +│ └── test_routes.py +└── doc/ # Documentation + └── index.md +``` + +## Getting Started + +### Prerequisites + +- Docker and docker-compose +- A Caddy reverse proxy network named `caddy` (already present on CozyTren infrastructure) + +### Quick Start + +```bash +# Clone the repository +git clone https://gitea.ct.cozytren.ch/romane/agentbox-test +cd agentbox-test + +# Set your secret key (or it defaults to a dev value) +export SECRET_KEY="your-secret-here" + +# Build and run +docker compose up -d --build +``` + +The app will be available at `http://meal-tracker:5000` (reachable through Caddy if configured). + +### Auto-Deploy + +The `watch-for-updates.sh` script polls the Gitea repository every 20 seconds and automatically rebuilds and redeploys the containers on new commits: + +```bash +# Source credentials and start the watcher +source .env +./watch-for-updates.sh +``` + +## Configuration + +| Environment Variable | Default | Description | +|----------------------|------------------------------|--------------------------------------| +| `SECRET_KEY` | `change-me-in-production` | Flask session signing secret | +| `DB_PATH` | `/data/meals.db` in Docker | Path to the SQLite database file | +| `GITEA_PASSWORD` | _(required for auto-deploy)_ | Gitea password for `agentbox` user | + +## Usage + +### Registration + +1. Navigate to the app +2. Click **Register** +3. Choose a username and password +4. Either **join** an existing household or **create** a new one +5. The first user in a new household becomes the admin + +### Responding to Meals + +1. On the dashboard, click **I'll be there** (green) or **I won't be there** (red) for lunch and dinner +2. Your response appears alongside other household members +3. A timestamp records the last change between yes ↔ no + +### Admin Actions + +- **Remove a user**: click the × button next to a member's name +- **Delete household**: use the dangerous action section (requires password confirmation) +- **Delete your own account**: available to all users, also requires password confirmation + +## API / Routes + +### Authentication + +| Method | Path | Description | +|--------|-------------|---------------------------| +| GET/POST | `/login` | Login page | +| GET/POST | `/register` | Registration page | +| GET | `/logout` | Logout (clears session) | + +### Meals + +| Method | Path | Description | +|--------|--------------|------------------------------------------------| +| GET | `/` | Redirects to dashboard or login | +| GET | `/dashboard` | Main dashboard (`?date=YYYY-MM-DD` to view other dates) | +| POST | `/respond` | Submit a meal response (meal_type, status, date) | +| GET | `/history` | History view (`?date=YYYY-MM-DD`) | + +### Account / Admin + +| Method | Path | Description | +|--------|-------------------------------|-------------------------------------| +| POST | `/delete-account` | Delete own account (needs password) | +| POST | `/admin/remove-user/` | Admin removes a household member | +| POST | `/admin/delete-household` | Admin deletes the entire household | + +## Database Schema + +### `households` +| Column | Type | Notes | +|------------|---------|------------------------| +| id | INTEGER | PRIMARY KEY AUTOINCREMENT | +| name | TEXT | UNIQUE NOT NULL | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | + +### `users` +| Column | Type | Notes | +|---------------|---------|-------------------------------| +| id | INTEGER | PRIMARY KEY AUTOINCREMENT | +| username | TEXT | UNIQUE NOT NULL | +| password_hash | TEXT | NOT NULL | +| is_admin | INTEGER | DEFAULT 0 | +| household_id | INTEGER | REFERENCES households(id) | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | + +### `meal_periods` +| Column | Type | Notes | +|-----------|---------|-------------------------------------------| +| id | INTEGER | PRIMARY KEY AUTOINCREMENT | +| date | TEXT | NOT NULL (ISO format YYYY-MM-DD) | +| meal_type | TEXT | NOT NULL, CHECK(lunch OR dinner) | +| | | UNIQUE(date, meal_type) | + +### `responses` +| Column | Type | Notes | +|------------|---------|---------------------------------------------------------| +| id | INTEGER | PRIMARY KEY AUTOINCREMENT | +| user_id | INTEGER | NOT NULL, REFERENCES users(id) | +| period_id | INTEGER | NOT NULL, REFERENCES meal_periods(id) | +| status | TEXT | DEFAULT 'not_answered', CHECK(yes, no, not_answered) | +| changed_at | TIMESTAMP | Set when flipping between yes↔no | +| created_at | TIMESTAMP | DEFAULT CURRENT_TIMESTAMP | +| | | UNIQUE(user_id, period_id) | + +## Running Tests + +```bash +pip install -r requirements.txt +pytest tests/ -v +``` + +Tests use a temporary in-memory SQLite database (configured per test via `conftest.py`).