3.6 KiB
3.6 KiB
Flask Multi-User Rewrite of Raccoon Timekeeper
Date: 2025-12-10 Project: Raccoon-TimeKeeper Status: resolved Tags: flask, sqlalchemy, flask-login, auth, docker, gunicorn, timezone, migration
Context
Raccoon Timekeeper started life as a Google Apps Script / Google Sheets time tracker. Jess wanted it self-hosted as a Python app with proper security: an admin created on first run (resettable from the CLI), admins able to add users, and strict per-user isolation so each person only sees their own timesheets. After the rewrite, testing surfaced two bugs: data not persisting across container restarts, and entries landing in the wrong week of the weekly summary.
Key Decisions
- Flask + SQLAlchemy + Flask-Login rather than staying on Apps Script — self-hosting, real user accounts and role-based access weren't practical on Sheets.
- First-run setup page that redirects to admin creation when no admin exists — avoids shipping default credentials.
- CLI recovery (
flask reset-admin) instead of a web "forgot password" flow — no mail server dependency, and recovery requires shell access, which is the right trust boundary. - User isolation enforced server-side — every API query filters on
current_user.id; UI hiding alone was rejected as insufficient. - Admin self-protection — admins cannot deactivate, demote or delete their own account, preventing a lockout with zero admins.
- Kept the original grayscale raccoon theme — functional rewrite only, not a redesign.
- Local-date handling in JS instead of
toISOString()— the UTC conversion shifted dates across the day boundary (AU timezone) and pushed entries into the neighbouring week.
Technical Details
- Stack: Flask, Flask-SQLAlchemy, Flask-Login, Werkzeug password hashing; SQLite by default via
DATABASE_URL(defaultsqlite:///timekeeper.db);SECRET_KEYfrom env. - Deployed with Docker (
Dockerfile,docker-compose.yml) running under gunicorn. - CLI commands:
flask init-db,flask reset-admin,flask create-user [--admin],flask list-users. - Flexible time input accepted:
1:30,1.5,90m,1h 30m. - Persistence bug root cause:
init_db()only ran insideif __name__ == '__main__':, which gunicorn never executes. Fix: run DB initialisation at app import time regardless of how the app is launched. - Week-shift bug root cause:
Date.prototype.toISOString()returns UTC. Fix: format dates from localgetFullYear()/getMonth()/getDate()throughout the front-end JS.
Code Changes
app.py— full rewrite: models, auth, admin routes, user-scoped API, CLI commands, import-time DB init.requirements.txt— added Flask-Login / SQLAlchemy dependencies.templates/— addedsetup,login, admin user management, task/settings management and change-password templates;index.htmldate handling fixed.static/— CSS and JavaScript utilities.README.md— security model, install and CLI documentation..env.example,Dockerfile,docker-compose.yml— container deployment.
Open Questions / Next Steps
- Confirm the Docker volume mapping keeps the SQLite file outside the container layer (the import-time init fixed table creation, but data persistence also depends on the volume).
- No password-reset path for regular users other than an admin resetting it — acceptable for now.
- Repo has been idle since Dec 2025; confirm whether it's still in use before investing further.
Artifacts
- Final fix commit:
e50d48e— Fix timezone issue in date handling - use local dates instead of UTC - README security docs:
3a3e8e4 - Repo: https://gitea.hideawaygaming.com.au/jessikitty/Raccoon-TimeKeeper