Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
ff76ff3
docs(logto): operator setup guide + LOGTO_* env var contract
Apr 29, 2026
e9c70b6
fix(logto-docs): Management API resource is https://default.logto.app…
Apr 30, 2026
c5fb841
feat(auth): JWT validator + Logto claims extraction
Apr 30, 2026
7816985
refactor(auth): apply Phase 1 review fixes
Apr 30, 2026
dac5882
docs: codify database ownership zones + migration rules
Apr 30, 2026
968e7ac
feat(logto): Management API client with cached M2M token
Apr 30, 2026
6f71684
style(auth): drop 'auth:' error prefix to match codebase convention
Apr 30, 2026
3cc11b8
test(logto): live smoke test against real Logto deployment
Apr 30, 2026
784af8b
feat(middleware): RequireJWT validates Logto-issued JWTs on every req…
Apr 30, 2026
052c20a
test(middleware): apply Phase 1.4 review fixes
Apr 30, 2026
1af130d
feat(middleware): RequireSportMatchesJWT cross-checks X-Sport vs JWT …
Apr 30, 2026
babf166
fix(auth,middleware): apply Phase 1 final review findings
Apr 30, 2026
2267dba
feat(health): expose build commit + timestamp via /api/v1/health
Apr 30, 2026
6b7a7b7
fix(deploy): rely on Coolify SOURCE_COMMIT for build-info, drop git COPY
May 1, 2026
418482a
docs(health): document Coolify SOURCE_COMMIT flow into build-info
May 1, 2026
34e24a2
fix(deploy): expand build context to repo root for commit-SHA injection
May 1, 2026
7275b10
docs(health): clarify how Dockerfile resolves the build commit
May 1, 2026
970324e
revert(deploy): drop repo-root build context, accept commit='unknown'
May 1, 2026
72525e5
docs(health): align comment with revert in 970324e
May 1, 2026
a797b0b
plan(logto-phase-2): expand Phase 2 outline to additive-only design
May 1, 2026
ab54799
feat(db): Phase 2 additive schema migration + sqlc bindings
May 1, 2026
6c8190b
docs(health): note Phase 2 migration deploy verification approach
May 1, 2026
68be0c5
fix(migration-00041): remove PLPGSQL DO block goose can't parse
May 1, 2026
1730d3f
fix(db): apply Phase 2 final review findings (I-1, I-2, I-3)
May 1, 2026
ed31e9c
feat(dev): docker-compose.dev.yml + Logto seed script for local dev
May 1, 2026
697f96a
fix(dev): logto runtime config (entrypoint seed, timeout, healthcheck)
May 1, 2026
d516f10
fix(logto): cap page_size at 100 + quote .env value with space
May 1, 2026
a60fec4
feat(auth): scaffold Logto SDK config and AuthProvider
May 2, 2026
4045f35
plan(logto): Phase 3 frontend implementation plan + amendments
May 2, 2026
7090c13
feat(auth): replace cookie-based useAuth with Logto-backed hook
May 2, 2026
206fdc6
feat(auth): apiFetch attaches Bearer + X-Sport, requests org-scoped t…
May 2, 2026
389d026
chore(web): document VITE_LOGTO_* in web/.env.example
May 2, 2026
0401d57
feat(sport): GET /api/v1/sports + SportContext provider
May 2, 2026
a14fcc3
feat(routing): move sport-scoped routes under /$sport segment
May 2, 2026
51e8a40
feat(picker): sport picker landing replaces /login + /register
May 2, 2026
b658607
feat(auth): OIDC callback route at /auth/callback
May 2, 2026
a047a25
feat(profile): GET/PATCH /api/v1/me/profile + form route
May 2, 2026
39a8435
fix(routing): replace stale /login navigations with signIn() calls
May 2, 2026
b1a2653
feat(auth): Logto webhook handler + on-demand user mirror
May 2, 2026
8bf9927
test(e2e): Playwright auth-flow smoke + fixes uncovered during it
May 3, 2026
faec337
docs(logto): document Phase 3 local DB patches needing seeder fixes
May 3, 2026
7341fa9
feat(auth): JWT-session bridge middleware + migrate 19 route groups (…
May 3, 2026
835d3e0
feat(seeder): apply 3 Phase 3.5 patches automatically (C2)
May 3, 2026
a793683
test(e2e): assert dashboard renders with content (Phase 3.5 C1 verify)
May 3, 2026
1ca7a50
fix(auth): address Phase 3 review C3+C4+I1
May 3, 2026
a0a6f42
fix(auth): Phase 3.6 review fixes (mixed-auth + role + production gua…
May 3, 2026
a7fb0c9
fix(auth): apply Phase 3.6 review I-1 + I-2
May 3, 2026
2378c54
feat(landing): public landing + Logto-aware seed + single-sport mode
May 3, 2026
90f8717
fix(routing): lift SportProvider to global, derive slug from URL
May 4, 2026
cbb4358
docs: comprehensive feature inventory at docs/FEATURES.md
May 4, 2026
197f48b
docs(features): correct impersonation restoration path (Logto-native)
May 4, 2026
82993d9
fix(routing): /overlay nav uses trailing slash + SportGuard skips res…
May 4, 2026
105ab88
fix(overlay): normalize null arrays from backend; settings page no lo…
May 4, 2026
a8d06b3
fix(overlay): normalize missing element config keys; settings page no…
May 4, 2026
bb0d8c4
docs: add SMOKE_TEST.md with 18-section local smoke checklist
May 4, 2026
d7383e8
fix(search): mount SearchModal inside RouterProvider + sport-scope links
May 4, 2026
285f659
fix(profile): keep ProfileEdit mounted across Logto SDK isLoading flips
May 4, 2026
0c87dbd
feat(ux): public surface for signed-in users + Google Places on profile
May 4, 2026
d98fadf
feat(ux): smoke-test fixes from sections 7-18
May 5, 2026
d87d5ac
docs(features): record smoke 7-18 known issues + deferred work
May 5, 2026
aedffe1
infra(prod): wire Logto env vars + Dockerfile ENV promotion + prod-bo…
May 5, 2026
76c4b9c
infra(prod): wire Resend SMTP + add docker-compose.bootstrap.yaml
May 6, 2026
095557f
feat(ghost): SMTP via Resend + 'make ghost-theme' packager
May 6, 2026
b47af50
infra(prod): add Logto service to unified compose stack
May 6, 2026
af61dc7
infra(prod): split Postgres into separate db (app) + db_logto services
May 6, 2026
82b98aa
feat(backup): include db_logto in 'make backup-full'
May 6, 2026
154d4b7
fix(prod): drop nested COMMIT default that broke Coolify parser
May 6, 2026
fbf8a1f
fix(coolify): add SERVICE_FQDN_<NAME>_<PORT> for proxy routing
May 6, 2026
5021503
fix(coolify): SERVICE_FQDN_*_<PORT> looks up correct env key
May 7, 2026
681f6c3
feat(deploy): one-shot 'seed' service that runs logto-seed on every d…
May 8, 2026
0277e81
fix(deploy): gate seed service behind 'seed' compose profile
May 8, 2026
7fc5dd6
fix(auth): apply Logto org-role elevation in /api/v1/auth/me too
May 8, 2026
fb81fae
fix(auth): verify sports.logto_org_id at boot to prevent silent token…
May 8, 2026
b68e940
feat(auth): auto-bootstrap Logto on every api boot
May 8, 2026
971b11a
fix(auth): gate /me on sport context to ensure org-scoped token
May 8, 2026
edfeee2
fix(auth): request UserScope.OrganizationRoles so platform_admin appe…
May 8, 2026
5d543ab
TEMP: bypass platform_admin check at all three gates while debugging …
May 8, 2026
cda3187
feat(admin): visible bypass banner + Logto operational runbook
May 11, 2026
8063475
fix(auth): elevate platform_admin via Logto Mgmt API since JWT lacks …
May 11, 2026
ec31ef2
docs: beta punchlist + PR plan from smoke-test triage (#5)
itkujo Jun 14, 2026
ba83191
fix(dev): postgres-init script must be POSIX sh, not bash (#6)
itkujo Jun 14, 2026
d83f288
fix(seed): own quick matches + API keys with admin/TD so they list (#7)
itkujo Jun 14, 2026
be71b0c
fix(layout): gate public bottom tabs on mobile for anonymous users (s…
itkujo Jun 14, 2026
78aade4
fix(courts): block 'stream live' when no stream URL is set (smoke 8.1…
itkujo Jun 14, 2026
ca4123a
fix(players): show VAIR before DUPR on player detail + profile (smoke…
itkujo Jun 14, 2026
2b95e52
fix(seed): use username sign-up identifier when email verify unavaila…
itkujo Jun 14, 2026
542584f
fix(auth): clean post-logout landing instead of raw Logto end-session…
itkujo Jun 14, 2026
3b9bf79
fix(security): remove admin bypass, complete role mapping, chain spor…
itkujo Jun 14, 2026
b95bea6
feat(ref): verbal calls + event log on the referee console (smoke 11.…
itkujo Jun 14, 2026
599a409
feat(admin): restore impersonation via Logto token exchange (PR-17) (…
itkujo Jun 14, 2026
3e6c713
fix(impersonation): emit act claim via Logto JWT customizer (complete…
itkujo Jun 14, 2026
2b151cc
feat(public): read-only division detail + bracket + standings + match…
itkujo Jun 14, 2026
7d954e9
feat(public): spectator division page (bracket/standings/matches) + w…
itkujo Jun 14, 2026
fa0cdc8
feat(public): anon-safe match page + court card drill-in, honest affo…
itkujo Jun 14, 2026
1746dd3
docs: current status + handoff (resume on another machine) (#20)
itkujo Jun 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
109 changes: 94 additions & 15 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,41 +1,120 @@
# Court Command Environment Variables
# Copy to .env for local development.
# In Coolify, set these in the Docker Compose service's environment variables.
#
# Copy this file to .env for local development. The values below are the
# DEV defaults, matching what `make dev-up` brings up via
# docker-compose.dev.yml. See docs/LOCAL_DEV.md for the first-run flow.
#
# In production, Coolify sets these via its env var UI; the values
# below are illustrative only. The `# PRODUCTION` block at the bottom
# of this file documents the prod overrides.

# --- Database ---
# --- Database (local dev: db service in docker-compose.dev.yml) ---
POSTGRES_USER=courtcommand
POSTGRES_PASSWORD=courtcommand
POSTGRES_DB=courtcommand
DATABASE_URL=postgres://courtcommand:courtcommand@db:5432/courtcommand?sslmode=disable
DATABASE_URL=postgres://courtcommand:courtcommand@localhost:5432/courtcommand?sslmode=disable

# --- Redis ---
REDIS_URL=redis://redis:6379/0
# --- Redis (local dev: redis service in docker-compose.dev.yml) ---
REDIS_URL=redis://localhost:6379/0

# --- Backend ---
PORT=8080
APP_ENV=development
CORS_ALLOWED_ORIGINS=http://localhost:5173,http://localhost:3000

# --- Frontend (baked into build) ---
# --- Frontend (Vite dev server) ---
VITE_API_URL=http://localhost:8080
VITE_GOOGLE_MAPS_API_KEY=

# --- Ghost CMS ---
# --- Ghost CMS (not used in local dev unless explicitly started) ---
GHOST_URL=http://localhost:2368

# --- Port Overrides (optional, for local conflicts) ---
# DB_PORT=5432
# REDIS_PORT=6379
# BACKEND_PORT=8080
# FRONTEND_PORT=3000
# GHOST_PORT=2368
# =============================================================================
# Logto (local dev: logto service in docker-compose.dev.yml on ports 3001/3002)
# =============================================================================
#
# First-run flow:
# 1. `make dev-up` # starts postgres + redis + logto containers
# 2. Open http://localhost:3002 in a browser, create the initial admin
# account (Logto's first-time setup wizard), then create:
# - Application -> Machine-to-machine -> name "Court Command Backend"
# - Open it -> Roles -> assign "Logto Management API access"
# - Open it -> Settings -> copy App ID and App Secret into the
# two env vars below.
# 3. `make logto-seed` # provisions everything else automatically
# 4. Capture the seeder's output (it prints the values to paste back here)
# 5. `make dev` # starts the Go backend natively
#
# Subsequent runs: just `make dev-up` + `make dev`.

# Core OIDC endpoint (issuer, JWKS, /oidc/token).
LOGTO_ENDPOINT=http://localhost:3001
# Admin UI (humans only, not used by the backend).
LOGTO_ADMIN_ENDPOINT=http://localhost:3002

# API resource indicator registered in Logto -> API resources.
# Local dev points back at the Go backend's localhost address.
LOGTO_API_RESOURCE=http://localhost:8080/api

# Machine-to-machine app credentials. Operator creates this in the
# Logto admin UI BEFORE the first run of `make logto-seed`.
LOGTO_MANAGEMENT_API_APP_ID=
LOGTO_MANAGEMENT_API_APP_SECRET=

# Resource indicator for the built-in Logto Management API. This is a
# Logto-internal fixed value, NOT a real URL. Same in dev and prod.
LOGTO_MANAGEMENT_API_RESOURCE=https://default.logto.app/api

# HMAC-SHA256 signing key from the webhook the seeder creates.
# Populated by the seeder's output; paste the printed value here.
LOGTO_WEBHOOK_SIGNING_KEY=

# Sport organization IDs (created by the seeder). Populated by the
# seeder's output; paste the printed values here.
LOGTO_PICKLEBALL_ORG_ID=
LOGTO_DEMO_SPORT_ORG_ID=

# Where the seeder will register the webhook URL. Logto runs in a
# Docker container, so it must reach the host-bound backend through
# host.docker.internal (Docker's host-gateway alias).
LOGTO_WEBHOOK_URL=http://host.docker.internal:8080/api/v1/webhooks/logto

# Where the SPA app's redirect URI points (the Vite dev server).
LOGTO_SPA_REDIRECT_URI=http://localhost:5173/auth/callback

# Bootstrap admin (created by the seeder).
# DO NOT USE THESE VALUES IN PRODUCTION.
LOGTO_BOOTSTRAP_EMAIL=admin@courtcommand.local
LOGTO_BOOTSTRAP_PASSWORD=TestPass123!
LOGTO_BOOTSTRAP_NAME="Local Admin"

# Optional: Logto user ID of the bootstrap admin. Used ONLY by the live
# smoke test in api/logto/livesmoke_test.go when LOGTO_LIVE_SMOKE=1.
# Populated by the seeder's output (look for "user_id:" in the summary).
# LOGTO_BOOTSTRAP_USER_ID=

# --- Frontend Logto config (baked into Vite build) ---
VITE_LOGTO_ENDPOINT=http://localhost:3001
# SPA App ID from the seeder's output.
VITE_LOGTO_APP_ID=
VITE_LOGTO_API_RESOURCE=http://localhost:8080/api

# =============================================================================
# PRODUCTION (Coolify) — Override these values in Coolify's env var UI:
# PRODUCTION (Coolify) overrides -- set these in Coolify's env var UI:
# =============================================================================
# POSTGRES_PASSWORD=<strong-random-password>
# DATABASE_URL=postgres://courtcommand:<password>@db:5432/courtcommand?sslmode=disable
# REDIS_URL=redis://redis:6379/0
# APP_ENV=production
# CORS_ALLOWED_ORIGINS=https://courtcommand.app,https://news.courtcommand.app
# VITE_API_URL=https://api.courtcommand.app
# GHOST_URL=https://news.courtcommand.app
# LOGTO_ENDPOINT=https://logto.courtcommand.app
# LOGTO_ADMIN_ENDPOINT=https://logto-admin.courtcommand.app
# LOGTO_API_RESOURCE=https://api.courtcommand.app/api
# LOGTO_WEBHOOK_URL=https://api.courtcommand.app/api/v1/webhooks/logto
# LOGTO_SPA_REDIRECT_URI=https://courtcommand.app/auth/callback
# VITE_LOGTO_ENDPOINT=https://logto.courtcommand.app
# VITE_LOGTO_API_RESOURCE=https://api.courtcommand.app/api
# (Plus the LOGTO_MANAGEMENT_API_APP_ID/SECRET, sport org IDs, and
# webhook signing key captured during the production seed.)
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# .gitignore
.env
.env.prod
.env.production
.env.local
*.exe
*.dll
*.so
Expand All @@ -21,3 +24,18 @@ web/src/routeTree.gen.ts
*.tsbuildinfo
backups/
ghost-theme/cc-ghost-theme.zip

# Playwright (Phase 3 Task 10)
web/test-results/
web/playwright-report/
web/blob-report/

# Local-only build outputs of the seeder (the binary is built into the
# api Docker image; never check in the local one)
api/logto-seed
api/court-command

# Editor backups
*.save
*~
*.swp
124 changes: 114 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,76 @@
# Makefile
.PHONY: dev dev-frontend dev-all up down full full-down build migrate-up migrate-down migrate-create sqlc test test-db seed backup backup-full restore restore-db backup-list backup-before-deploy
.PHONY: dev dev-frontend dev-all dev-up dev-down dev-logs dev-reset up down full full-down build migrate-up migrate-down migrate-create sqlc test test-db seed logto-seed backup backup-full restore restore-db backup-list backup-before-deploy

# ---- Local development (docker-compose.dev.yml) ----
# Brings up postgres + redis + logto with host port bindings; the Go
# backend runs natively on the host for fast iteration. See
# docs/LOCAL_DEV.md for the full first-run walkthrough.

# Start the dev infra (db + redis + logto)
dev-up:
docker compose -f docker-compose.dev.yml up -d
@echo ""
@echo "Dev infra ready:"
@echo " Postgres localhost:5432 (user=courtcommand pass=courtcommand db=courtcommand,logto)"
@echo " Redis localhost:6379"
@echo " Logto OIDC http://localhost:3001"
@echo " Logto admin http://localhost:3002"
@echo ""
@echo "Next: see docs/LOCAL_DEV.md for first-run setup."

# Stop dev infra (preserves volumes / data)
dev-down:
docker compose -f docker-compose.dev.yml down

# Tail logs from the dev infra services
dev-logs:
docker compose -f docker-compose.dev.yml logs -f

# Wipe dev infra including all data (Postgres volume + Logto state)
dev-reset:
docker compose -f docker-compose.dev.yml down -v
@echo "Dev infra wiped. Run 'make dev-up' to start fresh."

# Provision Logto (idempotent): creates apps, resources, scopes, org
# template, organizations, bootstrap admin, webhook. Reads config from
# .env (LOGTO_MANAGEMENT_API_APP_ID/SECRET must be set first -- see
# docs/LOCAL_DEV.md "First-run setup").
logto-seed:
@if [ ! -f .env ]; then echo "ERROR: .env not found. Copy from .env.example first."; exit 1; fi
@cd api && set -a && . ../.env && set +a && go run ./cmd/logto-seed

# Provision a production Logto tenant + sync sports.logto_org_id
# in the production app DB. Run ONCE at launch (re-running is safe;
# every step is idempotent). Env source order:
# 1. .env.prod (preferred -- gitignored, holds prod values)
# 2. .env (fallback for operators with a single env file)
#
# Required vars in the env file:
# LOGTO_ENDPOINT https://logto.courtcommand.app
# LOGTO_API_RESOURCE https://api.courtcommand.app/api
# LOGTO_MANAGEMENT_API_APP_ID (from Logto admin -> Apps -> M2M)
# LOGTO_MANAGEMENT_API_APP_SECRET (same place)
# LOGTO_MANAGEMENT_API_RESOURCE https://default.logto.app/api (Logto-internal, fixed)
# LOGTO_SPA_REDIRECT_URI https://courtcommand.app/auth/callback
# LOGTO_WEBHOOK_URL https://api.courtcommand.app/api/v1/webhooks/logto
# LOGTO_BOOTSTRAP_EMAIL/PASSWORD/NAME for the first admin
# DATABASE_URL points at the prod app DB (for sports.logto_org_id sync)
# APP_ENV must be "production" to skip Demo Sport
#
# Output: prints LOGTO_PICKLEBALL_ORG_ID, LOGTO_WEBHOOK_SIGNING_KEY,
# VITE_LOGTO_APP_ID etc. Paste into Coolify env, restart api+web.
prod-bootstrap:
@ENV_FILE=.env.prod; if [ ! -f $$ENV_FILE ]; then ENV_FILE=.env; fi; \
if [ ! -f $$ENV_FILE ]; then echo "ERROR: neither .env.prod nor .env found"; exit 1; fi; \
echo "Sourcing $$ENV_FILE"; \
cd api && set -a && . ../$$ENV_FILE && set +a && \
if [ "$$APP_ENV" != "production" ]; then \
echo "ERROR: APP_ENV is not 'production' -- refusing to run prod-bootstrap with dev settings"; \
exit 1; \
fi; \
go run ./cmd/logto-seed

# ---- Legacy single-stack (docker-compose.yaml -- prod / Coolify shape) ----

# Start Docker services (db + redis only)
up:
Expand Down Expand Up @@ -60,11 +131,24 @@ test-db: up
test: test-db
cd api && go test ./... -v -count=1

# Seed development data (all entity types — run after migrations)
seed: up
@echo "Seeding development data..."
docker compose exec -T db psql -U courtcommand -d courtcommand < api/db/seed.sql
@echo "Done! Login with admin@courtcommand.com / TestPass123!"
# Seed development domain data (orgs, tournaments, leagues, venues,
# matches, etc.) against the dev stack (docker-compose.dev.yml).
# Preserves the Logto-bootstrap admin row (logto_user_id IS NOT NULL);
# only wipes domain tables and shadow users.
#
# Prereqs:
# 1. make dev-up # postgres + redis + logto running
# 2. make migrate-up # schema is current
# 3. make logto-seed # Logto provisioned; bootstrap admin row exists
# 4. (sign in once via the SPA so the admin is mirrored to local users)
#
# After seeding, the bootstrap admin remains the only signin-capable
# user; all other users are shadow players (status='unclaimed') / staff
# fixtures with logto_user_id=NULL.
seed:
@echo "Seeding development domain data..."
docker compose -f docker-compose.dev.yml exec -T db psql -U courtcommand -d courtcommand < api/db/seed.sql
@echo "Done. Sign in via the SPA with the Logto bootstrap admin to see the seeded fixtures."

# ---- Backup & Restore ----

Expand All @@ -75,12 +159,20 @@ backup:
docker compose exec -T db pg_dump -U courtcommand courtcommand > backups/db-$$TIMESTAMP.sql && \
echo "Database backup: backups/db-$$TIMESTAMP.sql ($$(wc -c < backups/db-$$TIMESTAMP.sql | tr -d ' ') bytes)"

# Full backup: database + uploaded files (for before deploys or major changes)
# Full backup: app database + Logto identity database + uploaded files
# (for before deploys or major changes). Run as 'make backup-full' on
# the production host where the compose stack is running.
backup-full:
@mkdir -p backups
@TIMESTAMP=$$(date +%Y%m%d-%H%M%S); \
docker compose exec -T db pg_dump -U courtcommand courtcommand > backups/db-$$TIMESTAMP.sql && \
echo "Database backup: backups/db-$$TIMESTAMP.sql"; \
echo "App db backup: backups/db-$$TIMESTAMP.sql"; \
if docker compose ps -q db_logto >/dev/null 2>&1 && [ -n "$$(docker compose ps -q db_logto)" ]; then \
docker compose exec -T db_logto pg_dump -U $${LOGTO_DB_USER:-logto} $${LOGTO_DB_NAME:-logto} > backups/db_logto-$$TIMESTAMP.sql && \
echo "Logto db backup: backups/db_logto-$$TIMESTAMP.sql"; \
else \
echo "(db_logto service not running; skipped identity backup)"; \
fi; \
if [ -d api/uploads ] && [ "$$(ls -A api/uploads 2>/dev/null)" ]; then \
tar czf backups/uploads-$$TIMESTAMP.tar.gz -C api uploads && \
echo "Uploads backup: backups/uploads-$$TIMESTAMP.tar.gz"; \
Expand Down Expand Up @@ -125,12 +217,24 @@ restore-uploads:

# List all available backups
backup-list:
@echo "=== Database Backups ==="
@echo "=== App database backups ==="
@ls -lh backups/db-*.sql 2>/dev/null || echo " None"
@echo ""
@echo "=== Upload Backups ==="
@echo "=== Logto identity database backups ==="
@ls -lh backups/db_logto-*.sql 2>/dev/null || echo " None"
@echo ""
@echo "=== Upload backups ==="
@ls -lh backups/uploads-*.tar.gz 2>/dev/null || echo " None"

# Package the Court Command Ghost theme into a zip ready for upload
# at https://news.courtcommand.app/ghost (Admin -> Design -> Change theme).
# Output: ghost-theme/cc-ghost-theme.zip (gitignored).
ghost-theme:
@cd ghost-theme && rm -f cc-ghost-theme.zip && \
zip -r cc-ghost-theme.zip . -x '*.zip' -x '.*' && \
echo "Created ghost-theme/cc-ghost-theme.zip ($$(du -h cc-ghost-theme.zip | cut -f1))"
@echo "Upload via Ghost admin: Settings -> Design -> Change theme -> Upload."

# Include .env if it exists
-include .env
export
33 changes: 28 additions & 5 deletions api/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# api/Dockerfile
# Multi-stage build for the Court Command API
# Multi-stage build for the Court Command API.

# --- Stage 1: Build ---
FROM golang:1.24-alpine AS builder
Expand All @@ -15,11 +15,30 @@ WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

# Copy source code
# Copy source code (build context is ./api)
COPY . .

# Build the binary
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /court-command .
# Build the binary, injecting the build timestamp into the binary so
# /api/v1/health can report when the deployed image was built. The
# commit SHA falls back to "unknown" because Coolify (this version)
# does not expose SOURCE_COMMIT or COOLIFY_GIT_COMMIT_SHA at docker
# build time. The built_at timestamp alone is sufficient to verify a
# fresh deploy landed; pairing it with the local 'git log' tells you
# the exact commit by time.
#
# If a future Coolify version surfaces the SHA at build time, set
# COMMIT in docker-compose.yaml's build.args and the SHA will appear
# in the health response automatically.
ARG COMMIT
RUN RESOLVED_COMMIT="${COMMIT:-unknown}" \
&& BUILT_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
&& echo "Building court-command commit=${RESOLVED_COMMIT} built_at=${BUILT_AT}" \
&& CGO_ENABLED=0 GOOS=linux go build \
-ldflags="-s -w \
-X github.com/court-command/court-command/handler.buildCommit=${RESOLVED_COMMIT} \
-X github.com/court-command/court-command/handler.buildBuiltAt=${BUILT_AT}" \
-o /court-command . \
&& CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /logto-seed ./cmd/logto-seed

# --- Stage 2: Run ---
FROM alpine:3.20
Expand All @@ -31,12 +50,16 @@ RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app

# Copy binary from builder
# Copy binaries from builder. court-command is the API server; logto-seed
# is the one-shot Logto provisioning tool used by docker-compose.bootstrap.yaml
# to set up the Logto tenant + sync sports.logto_org_id on first deploy.
COPY --from=builder /court-command .
COPY --from=builder /logto-seed .

# Copy migrations (embedded in binary via go:embed, but also available for manual goose runs)
COPY --from=builder /app/db/migrations ./db/migrations


# Create uploads directory
RUN mkdir -p uploads && chown appuser:appgroup uploads

Expand Down
Loading