Skip to main content

PostgreSQL Schema

The relational schema lives in the main PostgreSQL database and is managed via sqlx migrations.

Migration file: crates/apollon-db/migrations/20260609000000_initial.up.sql

Entity Relationship

┌─────────────────┐ ┌──────────────────────┐
│ cargo_categories│ │ clients │
│─────────────────│ │──────────────────────│
│ id (PK) │◄──┐ │ id (PK) │
│ name (UNIQUE) │ │ │ name, email, phone │
│ description │ │ │ street, house_number │
│ created_at │ │ │ postal_code, city │
│ updated_at │ │ │ country │
└─────────────────┘ │ │ additional_info │
│ └──────────┬─────────────┘
│ │
│ │ ON DELETE CASCADE
│ ▼
│ ┌──────────────────────┐
│ │ contact_persons │
│ │──────────────────────│
│ │ id (PK) │
│ │ name, email, phone │
│ │ position │
│ │ is_primary │
│ │ client_id (FK) │
│ └──────────────────────┘

│ ┌──────────────────────┐
└──┤ trips │
│──────────────────────│
│ id (PK) │
│ start_time │
│ end_time │
│ start_latitude │
│ start_longitude │
│ end_latitude │
│ end_longitude │
│ cargo │
│ category_id (FK) │──► ON DELETE RESTRICT
│ weight │
│ sender_id (FK) │──► ON DELETE RESTRICT
│ receiver_id (FK) │──► ON DELETE RESTRICT
│ status (enum) │
│ log_gps │
└──────────────────────┘

Enum Types

trip_status

CREATE TYPE trip_status AS ENUM ('ACTIVE', 'COMPLETED', 'CANCELLED');

Tables

clients

Stores sender and receiver organizations for cargo trips.

CREATE TABLE clients (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
email TEXT NOT NULL,
phone TEXT NOT NULL,
street TEXT NOT NULL,
house_number TEXT NOT NULL,
postal_code TEXT NOT NULL,
city TEXT NOT NULL,
country TEXT NOT NULL,
additional_info TEXT
);
ColumnTypeConstraintsDescription
idUUIDPK, auto-generatedUnique identifier
nameTEXTNOT NULLCompany or person name
emailTEXTNOT NULLContact email
phoneTEXTNOT NULLContact phone
streetTEXTNOT NULLStreet name
house_numberTEXTNOT NULLHouse/building number
postal_codeTEXTNOT NULLPostal/ZIP code
cityTEXTNOT NULLCity
countryTEXTNOT NULLCountry
additional_infoTEXTnullableFree-text notes

contact_persons

Contact persons associated with a client. Supports a primary contact designation.

CREATE TABLE contact_persons (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
email TEXT NOT NULL,
phone TEXT NOT NULL,
position TEXT NOT NULL,
is_primary BOOLEAN NOT NULL DEFAULT false,
client_id UUID NOT NULL REFERENCES clients(id) ON DELETE CASCADE
);
ColumnTypeConstraintsDescription
idUUIDPK, auto-generatedUnique identifier
nameTEXTNOT NULLFull name
emailTEXTNOT NULLEmail address
phoneTEXTNOT NULLPhone number
positionTEXTNOT NULLJob title / role
is_primaryBOOLEANNOT NULL, default falsePrimary contact flag
client_idUUIDFK to clients(id), ON DELETE CASCADEParent client

When a client is deleted, all associated contact persons are automatically removed (CASCADE).

cargo_categories

Categories for classifying cargo types. Names must be unique.

CREATE TABLE cargo_categories (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL UNIQUE,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
ColumnTypeConstraintsDescription
idUUIDPK, auto-generatedUnique identifier
nameTEXTNOT NULL, UNIQUECategory name
descriptionTEXTnullableOptional description
created_atTIMESTAMPTZNOT NULL, default now()Creation timestamp
updated_atTIMESTAMPTZNOT NULL, default now()Last update timestamp

trips

Core trip records linking senders, receivers, cargo categories, and GPS tracking.

CREATE TABLE trips (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
start_time TIMESTAMPTZ NOT NULL,
end_time TIMESTAMPTZ,
start_latitude DOUBLE PRECISION NOT NULL,
start_longitude DOUBLE PRECISION NOT NULL,
end_latitude DOUBLE PRECISION,
end_longitude DOUBLE PRECISION,
cargo TEXT NOT NULL,
category_id UUID NOT NULL REFERENCES cargo_categories(id) ON DELETE RESTRICT,
weight DOUBLE PRECISION NOT NULL,
sender_id UUID NOT NULL REFERENCES clients(id) ON DELETE RESTRICT,
receiver_id UUID NOT NULL REFERENCES clients(id) ON DELETE RESTRICT,
status trip_status NOT NULL DEFAULT 'ACTIVE',
log_gps BOOLEAN NOT NULL DEFAULT false
);
ColumnTypeConstraintsDescription
idUUIDPK, auto-generatedUnique identifier
start_timeTIMESTAMPTZNOT NULLTrip start timestamp
end_timeTIMESTAMPTZnullableTrip end timestamp (set on completion)
start_latitudeDOUBLE PRECISIONNOT NULLStarting position latitude
start_longitudeDOUBLE PRECISIONNOT NULLStarting position longitude
end_latitudeDOUBLE PRECISIONnullableEnd position latitude
end_longitudeDOUBLE PRECISIONnullableEnd position longitude
cargoTEXTNOT NULLCargo description
category_idUUIDFK to cargo_categories(id), ON DELETE RESTRICTCargo type
weightDOUBLE PRECISIONNOT NULLCargo weight
sender_idUUIDFK to clients(id), ON DELETE RESTRICTSending client
receiver_idUUIDFK to clients(id), ON DELETE RESTRICTReceiving client
statustrip_statusNOT NULL, default 'ACTIVE'Trip lifecycle state
log_gpsBOOLEANNOT NULL, default falseEnable GPS tracking

Foreign key behavior:

  • category_id -- ON DELETE RESTRICT: Cannot delete a category that has trips
  • sender_id -- ON DELETE RESTRICT: Cannot delete a client used as sender
  • receiver_id -- ON DELETE RESTRICT: Cannot delete a client used as receiver

Indexes

CREATE INDEX idx_trips_status ON trips(status);
CREATE INDEX idx_trips_sender_id ON trips(sender_id);
CREATE INDEX idx_trips_receiver_id ON trips(receiver_id);
CREATE INDEX idx_contact_persons_client_id ON contact_persons(client_id);
IndexTableColumn(s)Purpose
idx_trips_statustripsstatusFilter trips by status (ACTIVE/COMPLETED/CANCELLED)
idx_trips_sender_idtripssender_idLook up trips by sender client
idx_trips_receiver_idtripsreceiver_idLook up trips by receiver client
idx_contact_persons_client_idcontact_personsclient_idLook up contacts by client

Notes

  • All primary keys use gen_random_uuid() (PostgreSQL 13+ built-in, no pgcrypto needed)
  • All timestamps use TIMESTAMPTZ (timezone-aware)
  • GPS location data is not in this database -- it lives in TimescaleDB