This document summarizes the high‑level schema and design decisions for the PostgreSQL backend. It mirrors the system‑wide API key and UID design and avoids implementation details.
Core entities
Logbook
Internal primary key: id (sequential, used for joins)
External identifier: uid (immutable, opaque; used in client/server protocols)
Flexible additional_data JSONB for extra fields, with a guard against duplicating core fields
Deletion policy
No soft deletes in the server‑side schema.
Deleting a logbook cascades hard deletes to its QSOs and API keys (non‑recoverable).
Deleting a QSO is non‑recoverable.
At most one active (non‑revoked) API key per logbook is enforced by a partial unique index; rotate by creating a new key before revoking the old one, or revoke then create.
High‑level flows
Registration
Client registers a logbook; server creates uid and issues an API key (prefix.secretHex)
Client stores uid and the full API key locally
Authentication and authorisation
Requests include Authorization: ApiKey <prefix>.<secretHex> and uid
Server resolves uid to logbook_id, finds an active key by (logbook_id, key_prefix), and validates digest
Rotation and revocation
Keys can be revoked or rotated; policy can allow multiple active keys or restrict to one
Integrity rules (application‑enforced)
Logging station callsign must match the logbook’s callsign on writes
Contacted station callsign (qso.call) is unconstrained relative to the logbook’s callsign
Notes
Sequential IDs remain internal; uid is the stable external reference
Prefix is independent random for zero leakage; it is not derived from secretHex
SQL models are generated from the migrations and can be re‑generated after schema changes
ApplyInitialSchemaSimple is a fallback initializer that applies the initial schema
without using golang-migrate. It is intended for development/debugging where the
standard migrator may be blocked by environment constraints.
BootstrapExec executes the initial up migration directly for first-time initialization
when the schema is entirely missing. This avoids potential issues with external
migration locking on brand new databases.