Files
Raccoon-TimeKeeper/.claude-context/sessions/2025-12-10-flask-multiuser-rewrite.md
T

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 (default sqlite:///timekeeper.db); SECRET_KEY from 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 inside if __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 local getFullYear()/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/ — added setup, login, admin user management, task/settings management and change-password templates; index.html date 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