2026-10-06 16:43:50 +01:00
2026-10-05 22:36:27 +01:00
2026-10-06 16:43:50 +01:00
2026-10-06 16:43:50 +01:00
2026-10-06 02:12:18 +01:00
2026-10-06 16:39:23 +01:00
2026-10-06 02:12:18 +01:00
2026-10-06 10:12:21 +01:00
2026-10-06 01:44:50 +01:00
2026-10-06 13:09:12 +01:00
2026-10-06 13:09:12 +01:00
2026-10-05 22:36:27 +01:00
2026-10-06 15:44:25 +01:00
2026-10-06 01:44:50 +01:00

Contínuos CRR

Small self-hosted rota application for a non-profit bar/club.

What it does

  • One Night assignment for every calendar day.
  • Independent rotating Saturday Lunch and Sunday Lunch schedules.
  • Admins can override any generated assignment.
  • Members can request that another person (or anyone available) takes an assignment.
  • Optional manager approval before a change becomes final.
  • Optional email and phone on each user; username is used for login.
  • SMTP email notifications and a notification log.
  • Daily reminder command suitable for cron.
  • SQLite by default, packaged for Docker.

Start locally and fill data

Requirements: Python 3.12+, make, and Docker Compose 2.24+ for checks. Run commands from the project root.

make dev-setup   # creates .env if missing, .venv, and installs dependencies
make seed-demo   # optional: populate example people and schedules before opening the app
make dev        # auto-reload on http://localhost:8000

No virtualenv activation is needed. make setup preserves an existing .env and database. Run make to see the main commands; use make dev PORT=8080 for another port.

Open http://localhost:8000. With the example configuration, the administrator login is admin / change-me-now. Existing installations use their existing credentials.

To fill your own data, skip seed-demo, sign in as administrator, and:

  1. Open People (/admin/users) and add members. Contact details are optional; credentials are optional as a pair.
  2. Open Schedules (/admin/schedules) and configure each schedule: Night's repeating day-of-month pattern, Saturday's rotation, and Sunday's independent rotation.
  3. View the rota (/) and use each schedule's configuration page to edit assignments.
  4. Sign in as a member to request a change, as another member to accept it, then as admin to approve it.

On either lunch schedule page, Apply rotation updates the displayed month's automatic shifts from the current rotation order, keeping manual assignments and completed swaps. Every row has Regenerate to explicitly auto-assign that date again, including manual assignments and completed swaps. Generate after date creates missing shifts and updates automatic assignments strictly after the chosen date through an inclusive end date, across as many months as you select; it preserves manual assignments and completed swaps. If the rotation has no active members, automatic shifts become unassigned. Ownership changes cancel any open/pending change request for that shift.

Rotation uses the previous assigned person in that same schedule as its reference, then selects the next active person in the order, wrapping to the start. This continues across months and respects manual assignments and completed swaps as references. For example, with Alice → Bruno → Carla, a previous shift assigned to Bruno means the next automatic shift goes to Carla. If no usable previous assignee exists, the schedule's anchor date supplies the starting position.

Demo seeding creates alice, bruno, carla, diogo, each with password demo-password for newly created accounts, both lunch rotations, and all 31 days of the repeating Night pattern. It generates this month and next so there are future shifts available for testing changes. Re-running it preserves existing accounts, configured schedules, and assignments, including intentionally unassigned shifts. If you already opened a month before seeding, its existing rows stay as they were; use the next month or edit them in the admin UI.

Important: the initial admin is only created when the database has no users. Changing INITIAL_ADMIN_PASSWORD later does not reset that account; reset it from the People screen.

Develop in Docker

Requirements: Docker with Compose 2.24+ and make; host Python is optional.

make seed-demo RUNTIME=docker   # optional; initializes and seeds the persistent database
make dev-up                    # builds and starts the app with auto-reload

Open http://localhost:8000 with the same logins as local development. App code, templates, static files, and migration files are bind-mounted; Python changes reload automatically. Refresh the browser after template or CSS edits. Dependency changes need an image rebuild: stop with Ctrl-C and run make dev-up again.

make dev-up PORT=8080   # use another host port
make dev-logs          # follow app logs from another terminal
make dev-down          # remove containers; database files are kept

docker-compose.dev.yml contains the development app and isolated checks service. Database commands support RUNTIME=docker and work even when the app is stopped.

Move to Docker deployment

The default host and Docker configurations both use data/bar_rota.db. Stop the local app (or development container), then start the deployment stack. On the same machine, your populated database is reused without an import or conversion.

make setup
# Set SECRET_KEY and INITIAL_ADMIN_PASSWORD for deployment in .env.
# Set BASE_URL to the deployed URL; PORT defaults to 8000.
docker compose up -d --build
docker compose ps              # includes the HTTP healthcheck status

docker-compose.yml explicitly builds the production image, with persistent data and a restart policy. It runs without auto-reload. Rebuild after code changes with docker compose up -d --build.

To move to another machine, stop the app, copy the entire data/ directory and your .env configuration to the new project checkout, then run the deployment command there. Keep these private files out of Git and Docker images. Stopping the app before copying also avoids copying an active SQLite database mid-write.

Behind a reverse proxy, set BASE_URL to the public address (used in e-mail links), ROOT_PATH to the prefix the app is served under when it is not at the domain root (for example /crr for https://host/crr/), and SESSION_HTTPS_ONLY=true when the proxy terminates TLS. Uvicorn runs with --proxy-headers --forwarded-allow-ips '*', so X-Forwarded-Proto/X-Forwarded-Host set the scheme and host used in generated URLs and cookies; the proxy must forward them and keep the prefix it advertises.

On every start the application writes a copy of the SQLite file to backups/ beside the database (override with BACKUP_DIR), named rota-<timestamp>.db and never overwriting an older copy. Nothing is deleted, so the folder grows by one file per start; prune it yourself if that becomes too much.

DATA_DIR changes the Docker host directory mounted at /app/data. Keep DATABASE_URL=sqlite:///./data/bar_rota.db in Docker: this path is relative to /app inside the container. For seamless switching between host and Docker, use the default DATA_DIR=./data; a custom directory requires a matching host DATABASE_URL when running locally. The port for Make development commands is selected with PORT=...; direct Compose commands use .env's PORT. Update BASE_URL when changing the URL used in notification links.

Email

Set SMTP_HOST, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD, SMTP_FROM and SMTP_STARTTLS in .env. If SMTP is not configured, email attempts are safely recorded as skipped in Email log.

To send reminders from cron:

python -m app.cli send-reminders

Example cron entry at 09:00 every day:

0 9 * * * cd /path/to/bar-rota && docker compose exec -T bar-rota python -m app.cli send-reminders

Database

The schema is managed with Alembic. The app runs alembic upgrade head on start, so starting it is enough to apply pending migrations.

make migrate                      # apply pending migrations
make revision m="add phone field" # generate a revision after editing app/models.py
make db-shell                     # inspect/edit data with SQLAlchemy

# The same commands without host Python:
make migrate RUNTIME=docker
make revision RUNTIME=docker m="add phone field"
make db-shell RUNTIME=docker

A console session exposes db, select, models, and all model classes:

people = db.scalars(select(User).order_by(User.name)).all()
[(person.id, person.name) for person in people]
db.add(User(name="New member", is_active=True))  # rota-only person
db.commit()                                    # explicitly save edits
# db.rollback()                                # discard uncommitted edits

Exit with Ctrl-D. Use the admin UI for normal rota edits and swaps so workflow validation and notifications run. For schema changes, edit app/models.py, generate and review an Alembic revision, then run make migrate and make check. Docker-generated revisions appear directly in migrations/versions/ through the bind mount. Migrate while the app is stopped, then restart it.

The SQLite file can also be opened with a SQLite database editor. With the default configuration it is data/bar_rota.db; use Alembic for schema edits so code and database stay in sync.

A database created before Alembic existed has no version stamp; it is adopted at the baseline revision 0001_initial and upgraded from there. tests/test_migrations.py fails if the models and the migrations drift apart.

Tests

Checks run in a container, so the host Python environment does not matter:

make check
# or
docker compose -f docker-compose.dev.yml --profile check run --rm checks

make check runs python -m compileall -q app && pytest -ra against the working tree.

Keep make dev or make dev-up running in one terminal and run make check in another. You can keep adding people and editing the rota while checks run: the checks container does not load .env or mount your data/ directory. Its database is disposable, and individual tests use temporary/in-memory databases. Stopping or rebuilding the development app does not delete your populated database.

Project documentation

  • AGENTS.md — instructions and guardrails for coding agents/LLMs.
  • docs/DOMAIN.md — scheduling rules and terminology.
  • docs/ARCHITECTURE.md — implementation overview.
  • docs/ROADMAP.md — intentionally deferred work.
S
Description
No description provided
Readme
1.1 MiB
Languages
Python 74.3%
HTML 21.1%
CSS 3.8%
Dockerfile 0.3%
Mako 0.3%
Other 0.2%