Files
2026-08-01 07:27:31 +00:00

229 lines
9.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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/<id>` | 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 automatically by the `meal-cron` container via `POST /api/cron-tick` every 60 seconds
- 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