No description
Find a file
Ian Keane 507531fbff Changes for refactor of references view, add unique keys for some tables
References sites table updated so we can filter by site later, added
timestamp to tunes so they can be sorted in certain contexts, enforced
uniqueness on some tables that were causing issues
2026-06-11 10:05:23 -04:00
ain Initial Commit 2026-06-09 15:07:08 -04:00
app Changes for refactor of references view, add unique keys for some tables 2026-06-11 10:05:23 -04:00
docker Initial Commit 2026-06-09 15:07:08 -04:00
migrations Changes for refactor of references view, add unique keys for some tables 2026-06-11 10:05:23 -04:00
nixos Initial Commit 2026-06-09 15:07:08 -04:00
tests Initial Commit 2026-06-09 15:07:08 -04:00
.gitignore Initial Commit 2026-06-09 15:07:08 -04:00
alembic.ini Initial Commit 2026-06-09 15:07:08 -04:00
env.example Initial Commit 2026-06-09 15:07:08 -04:00
Makefile Initial Commit 2026-06-09 15:07:08 -04:00
pyproject.toml Initial Commit 2026-06-09 15:07:08 -04:00
README.md Initial Commit 2026-06-09 15:07:08 -04:00
uv.lock Initial Commit 2026-06-09 15:07:08 -04:00
wsgi.py Initial Commit 2026-06-09 15:07:08 -04:00

repertory-api

Flask + SQLAlchemy REST API for the Repertory tune-tracking database.

Stack

  • Flask 3 HTTP API
  • SQLAlchemy 2 / Flask-SQLAlchemy ORM
  • PostgreSQL database (tunes)
  • Alembic migrations
  • uv dependency management
  • Docker Compose local development
  • NixOS systemd module production deployment

Local development

First time setup

make local

This will:

  1. Copy env.example.env (skipped if .env already exists)
  2. Start Postgres via Docker Compose
  3. Wait for Postgres to be ready
  4. Run Alembic migrations
  5. Start the API container

At the end it prints the API URL and your local API key:

  ✅  Local environment is ready.

  API:      http://localhost:5000
  API key:  dev-secret-key-change-me

The API key is set in .env as API_KEY. Change it there if you want — it only affects your local dev environment. Production keys are managed via SOPS (see below).

Subsequent runs

make local   # idempotent — safe to re-run, skips what's already done
make stop    # stop containers (data preserved)
make reset   # stop containers AND wipe the database volume (fresh slate)

Other useful commands

make test    # run the test suite (SQLite in-memory, no Docker required)
make migrate # run pending Alembic migrations against the local DB
make logs    # tail Docker Compose logs
make shell   # open a psql shell in the running Postgres container

Run make or make help to see all available targets.

Adding a schema migration

After changing a model in app/models.py:

# With the local DB running:
uv run alembic revision --autogenerate -m "describe your change"
make migrate

Commit both the model change and the new file in migrations/versions/.


API Endpoints

Health

Method Path Description
GET /health Health check

Tunes

Method Path Description
GET /tunes/ List all tunes (supports query filters — see below)
GET /tunes/<id> Get a single tune with all sub-records
POST /tunes/ Create a tune (optionally with nested banjo/fiddle/etc.)
PATCH /tunes/<id> Update tune core fields
DELETE /tunes/<id> Delete a tune (cascades to all sub-records)

GET /tunes/ query params (all optional, combinable):

Param Example Notes
search ?search=cluck Substring match on name
key ?key=D Exact key match (case-insensitive)
instrument ?instrument=fiddle Filter by instrument
tuning ?tuning=cross Substring match on tuning
callable ?callable=true Boolean
review ?review=false Boolean
to_learn ?to_learn=true Boolean
difficulty ?difficulty=easy "easy" or "hard"

instrument must be set for tuning, callable, review, to_learn, and difficulty filters to apply.

Instrument sub-records (banjo / fiddle)

Method Path Description
PUT /tunes/<id>/banjo Create or fully replace banjo entry
PUT /tunes/<id>/fiddle Create or fully replace fiddle entry
PATCH /tunes/<id>/banjo Partially update banjo entry
PATCH /tunes/<id>/fiddle Partially update fiddle entry
DELETE /tunes/<id>/banjo Remove banjo entry
DELETE /tunes/<id>/fiddle Remove fiddle entry

Instrument fields: learned_from, date_learned (ISO date string), tuning, callable (bool), review (bool), to_learn (bool), difficulty ("easy"/"hard").

Notes

Method Path Description
GET /tunes/<id>/notes List notes
POST /tunes/<id>/notes Add a note
PATCH /tunes/<id>/notes/<note_id> Update a note
DELETE /tunes/<id>/notes/<note_id> Delete a note

References

Method Path Description
GET /tunes/<id>/references List references
POST /tunes/<id>/references Add a reference
PATCH /tunes/<id>/references/<ref_id> Update a reference
DELETE /tunes/<id>/references/<ref_id> Delete a reference
POST /tunes/<id>/references/<ref_id>/musicians Add a musician tag
DELETE /tunes/<id>/references/<ref_id>/musicians/<name> Remove a musician

Production deployment (NixOS + SOPS + age)

The NixOS module in nixos/module.nix fetches the application source directly from your Forgejo instance at a pinned tag, builds the Python environment, and manages the service under systemd — no manual file copying required.

1. Add secrets to your SOPS file

repertory-api-key: "your-api-key-here"
repertory-db-url: "postgresql://user:pass@host:5432/tunes"

2. Wire up the module in configuration.nix

imports = [ (fetchTarball {
  url    = "https://git.yourdomain.com/yourusername/repertory-api/archive/v1.0.0.tar.gz";
  sha256 = "0000000000000000000000000000000000000000000000000000";
} + "/nixos/module.nix") ];

sops.secrets."repertory-api-key" = {};
sops.secrets."repertory-db-url"  = {};

services.repertory-api = {
  enable  = true;
  domain  = "git.yourdomain.com";
  owner   = "yourusername";
  version = "v1.0.0";
  rev     = "abc123...";  # git commit SHA for the tag
  sha256  = "sha256-..."; # see below
};

3. Deploy

nixos-rebuild switch

On start, the service automatically runs alembic upgrade head before Flask comes up — so migrations are applied on every deploy with no manual step.

Updating to a new version

  1. Push a new tag to Forgejo (e.g. v1.1.0)
  2. Get the new sha256:
    nix-prefetch-url --unpack \
      https://git.yourdomain.com/yourusername/repertory-api/archive/v1.1.0.tar.gz
    
  3. Update version, rev, and sha256 in configuration.nix
  4. nixos-rebuild switch — Nix fetches the new source, rebuilds the Python env, restarts the service, and runs any new migrations

How secrets are handled

The preStart script reads the SOPS-decrypted secret files (written to /run/secrets/ by sops-nix) and assembles them into a mode-600 env file at /run/repertory-api/env on tmpfs. The Flask process receives API_KEY and DATABASE_URL from that file. Nothing is written to disk in plaintext.


Database schema

musicians      (id, name)
sources        (id, name)
tunings        (id, name)
instruments    (id, name)
tunes          (id, name, key, modal, source_id → sources.id)
  ├── tune_by_instrument (id, tune_id, instrument_id, learned_from_id, tuning_id, date_learned, callable, review, to_learn, difficulty)
  │    └── tune_by_instrument_notes (id, tune_by_instrument_id, note)
  ├── tune_notes    (id, tune_id, note)
  └── references (id, tune_id, link, site)
       └── reference_musicians (reference_id, musician_id)  ← join table

To add a new instrument (e.g. harmonica): add a model in app/models.py mirroring Banjo/Fiddle, register the name in INSTRUMENT_MODELS in app/routes_instruments.py, then uv run alembic revision --autogenerate -m "add harmonica" and make migrate.