- TypeScript 84.1%
- CSS 15.7%
- HTML 0.1%
- Dockerfile 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| backend | ||
| frontend | ||
| .gitignore | ||
| compose.yml | ||
| DESIGN.md | ||
| README.md | ||
Bridge Command Crew Hub
Crew management and sharing platform for naval simulation campaign crews. Members share mission logs and fan art within their crew/campaign — access is controlled entirely by OIDC groups synced on login.
Stack
| Layer | Technology |
|---|---|
| Backend | Node.js + Express + TypeScript |
| Frontend | React + TypeScript + Vite |
| Database | PostgreSQL 15 |
| File storage | MinIO (S3-compatible) |
| Auth | OIDC via openid-client (VoidAuth or any standard provider) |
| Sessions | express-session + connect-pg-simple |
| Reverse proxy | lucaslorentz/caddy-docker-proxy (external, label-based) |
Quick start
1. Prerequisites
- Docker + Docker Compose
- An existing
caddy-docker-proxycontainer connected to a Docker network namedcaddy(or changenetworks.caddy.external: trueindocker-compose.ymlto match your setup)
2. Configure environment
cp .env.example .env
# Edit .env — see the variable reference below
3. Start everything
docker compose up -d
On first start the backend will:
- Wait for PostgreSQL to be healthy
- Run all SQL migrations in
backend/migrations/in order - Initialise the OIDC client (discovery against
OIDC_ISSUER) - Create the S3 bucket if it doesn't exist
The seed data (two example crews + three campaigns) is applied by 002_seed.sql.
Environment variables
| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL |
✓ | — | Full PostgreSQL connection string |
POSTGRES_DB |
— | bridge_crew |
Database name (used by the postgres container) |
POSTGRES_USER |
— | bridge |
DB username |
POSTGRES_PASSWORD |
— | bridge_secret |
DB password |
OIDC_ISSUER |
✓ | — | Base URL of your OIDC provider (e.g. https://auth.example.com) |
OIDC_CLIENT_ID |
✓ | — | Client ID registered on the provider |
OIDC_CLIENT_SECRET |
✓ | — | Client secret |
OIDC_REDIRECT_URI |
✓ | — | Full callback URL, e.g. https://hub.example.com/api/auth/callback |
SESSION_SECRET |
✓ | — | Random string ≥ 32 chars (openssl rand -hex 64) |
S3_ENDPOINT |
✓ | http://minio:9000 |
S3/MinIO endpoint |
S3_BUCKET |
— | bridge-crew-hub |
Bucket name (auto-created) |
S3_ACCESS_KEY |
✓ | minioadmin |
S3 access key |
S3_SECRET_KEY |
✓ | minioadmin123 |
S3 secret key |
CADDY_HOST |
— | localhost |
Hostname Caddy routes for (passed to caddy-docker-proxy labels) |
NODE_ENV |
— | development |
development or production |
OIDC provider setup (VoidAuth / any provider)
Groups convention
The app derives access entirely from group membership synced at login. No in-app admin UI is needed.
| Group name | Access granted |
|---|---|
crew:red-squadron |
Member of the Red Squadron crew (all its campaigns) |
campaign:operation-trident |
Member of Operation Trident only |
campaign:battle-of-the-reaches |
Member of Battle of the Reaches only |
- A
crew:*group grants access to all campaigns in that crew. - A
campaign:*group grants access to that campaign only. - Groups are read from the
groupsclaim in the OIDC userinfo response.
What to configure on the provider
- Create a client with:
- Redirect URI:
https://your.domain.com/api/auth/callback - Scopes:
openid email profile groups
- Redirect URI:
- Create groups following the naming convention above and assign users to them.
- Ensure the
groupsclaim is included in the userinfo/ID token response.
Caddy / reverse proxy
This project uses lucaslorentz/caddy-docker-proxy.
The Caddy container is external — connect it to the caddy Docker network, and it will
automatically pick up the routing labels on the app and frontend services:
/api/* → app:3000 (backend)
/* → frontend:5173 (Vite dev server)
For production with Vite's static build, replace the frontend service with a served dist/ directory and update the label accordingly.
To use a different external network name, change networks.caddy.external in docker-compose.yml.
Project layout
.
├── backend/
│ ├── migrations/ # SQL migration files (run in filename order)
│ ├── src/
│ │ ├── config.ts # Zod-validated env config
│ │ ├── app.ts # Express app setup
│ │ ├── index.ts # Entry point (migrations → OIDC init → listen)
│ │ ├── db/ # Pool, migrate, seed, types
│ │ ├── middleware/ # requireAuth, errorHandler
│ │ ├── routes/ # auth, crews, logs, art
│ │ └── services/ # oidc, s3, groups
│ └── Dockerfile
├── frontend/
│ ├── src/
│ │ ├── api/ # Typed API client
│ │ ├── components/ # Navbar, Layout, ArtCard, Lightbox, etc.
│ │ ├── hooks/ # useAuth
│ │ ├── pages/ # One file per route
│ │ └── styles/ # global.css (dark naval theme)
│ └── Dockerfile
├── docker-compose.yml
└── .env.example
Development notes
- Hot reload — both containers mount the source tree as a volume; the backend uses
tsx watchand the frontend uses Vite's HMR. - MinIO console — available at
http://localhost:9001(credentials fromS3_ACCESS_KEY/S3_SECRET_KEY). - Run migrations manually:
docker compose exec app npm run migrate - Run seed manually:
docker compose exec app npm run seed