main
Root fixes: - .chip now has explicit color: var(--text) in base theme, not just dark — buttons no longer use browser default - All inputs/selects/textareas get background: var(--surface) and color: var(--text) in both light and dark themes - Removed color-scheme: dark on body (prevented browser from forcing dark form controls regardless of data-theme) - Dark mode input override only changes background shade now This fixes: unreadable chip text and always-dark inputs.
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
# 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:
# 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
- Navigate to the app
- Click Register
- Choose a username and password
- Either join an existing household or create a new one
- The first user in a new household becomes the admin
Responding to Meals
- On the dashboard, click I'll be there (green) or I won't be there (red) for lunch and dinner
- Your response appears alongside other household members
- 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 |
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
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 to receive push notifications with action buttons, allowing them to respond to meals directly from their phone without opening the web UI.
Setup
- Install the ntfy app on your phone and subscribe to a topic (e.g.
your-name-meals) - (Optional) Create an access token for authenticated publishing
- 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)
- Ntfy Topic URL: Your topic (e.g.
- Click Send Test to verify it works
How It Works
- Reminders are sent automatically by the
meal-croncontainer viaPOST /api/cron-tickevery 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
Description
A lightweight Flask meal attendance tracker with household scoping, ntfy push notifications, and i18n (en/fr/de). 100% vibecoded.
400 KiB
Languages
Python
68.4%
HTML
12.5%
CSS
7.1%
Java
7%
Shell
4.7%
Other
0.3%