# 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 ├── i18n.py # Internationalization (en/fr/de) ├── ntfy.py # Ntfy notification 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 ### Ntfy Callback (public) | Method | Path | Description | |--------|------|-------------| | POST | `/api/ntfy-callback` | Receive ntfy action callbacks. Auth via per-user `callback_token`. Body: `{"token":"...", "meal_type":"lunch|dinner", "date":"YYYY-MM-DD", "status":"yes|no"}`. Returns 200 on success, 403 for bad token, 400 for invalid/past data. | ### Ntfy Settings (authenticated) | Method | Path | Description | |--------|------|-------------| | POST | `/ntfy-settings` | Save ntfy topic URL and access token | | POST | `/ntfy-test` | Send a test notification to verify configuration | ### 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`). ## Ntfy Notifications Users can configure [ntfy](https://ntfy.sh) to receive push notifications with action buttons, allowing them to respond to meals directly from their phone without opening the web UI. ### Setup 1. Install the ntfy app on your phone and subscribe to a topic (e.g. `your-name-meals`) 2. (Optional) Create an access token for authenticated publishing 3. In the web UI, open **Account Settings** and enter: - **Ntfy Topic URL**: Your topic (e.g. `https://ntfy.sh/your-name-meals`) - **Access Token**: Your ntfy access token (if using auth) 4. Click **Send Test** to verify it works ### How It Works - Reminders are sent via the CLI command: `flask send-reminders` - The notification contains **Home** and **Out** buttons - Pressing a button sends an HTTP callback to the app, recording your response instantly - Tapping the notification itself opens the dashboard ### Scheduling Reminders Add a cron job on the server to send reminders automatically: ```bash # Lunch reminder at 9:00 AM 0 9 * * * cd /path/to/app && docker compose exec meal-tracker flask send-reminders --meal lunch --base-url https://meals.ct.cozytren.ch # Dinner reminder at 5:00 PM 0 17 * * * cd /path/to/app && docker compose exec meal-tracker flask send-reminders --meal dinner --base-url https://meals.ct.cozytren.ch ``` Or run manually: ```bash docker compose exec meal-tracker flask send-reminders --date 2026-08-01 --meal both ```