System architecture
High-level overview
graph TD
Browser["Browser (HTMX)"]
CDN["CDN / Ingress"]
Server["ocg-server\n(axum, Rust)"]
Redirector["ocg-redirector\n(axum, Rust)"]
DB[("PostgreSQL")]
S3["S3-compatible\nimage storage\n(optional)"]
Stripe["Stripe"]
Zoom["Zoom / Google Meet"]
YouTube["YouTube"]
YouCom["You.com\n(event + job discovery)"]
MCP["mcp/\n(Node.js JSON-RPC)"]
Browser -->|HTTPS| CDN
CDN --> Server
CDN --> Redirector
Server --> DB
Server --> S3
Server --> Stripe
Server --> Zoom
Server --> YouTube
Server --> YouCom
MCP --> Server
Components
ocg-server
The main application server. It is a single binary built from ocg-server/src/main.rs that handles HTTP requests, background services, and all business logic.
Routing (ocg-server/src/router.rs)
The axum Router is assembled in router::setup(). Routes are divided into:
- Protected routes (wrapped with
login_required!fromaxum-login) - Dashboard routes, further split into alliance, group, and user sub-routers assembled in
router/dashboard.rs - API routes under
/api/v1/assembled inrouter/api.rs - Public routes (explore, jobs, landscape, wiki, profiles, etc.)
Conditional routes (Zoom webhooks, payment webhooks, email/GitHub/LinkedIn login) are added at runtime based on configuration.
State (ocg-server/src/router.rs — State struct)
The State struct is passed to every handler via axum's FromRef extraction. It holds:
db: DynDB— trait-object database handle (ocg-server/src/db.rs)image_storage: DynImageStorage— trait-object storage handlenotifications_manager: DynNotificationsManagerpayments_manager: DynPaymentsManagermeetings_cfg,payments_cfg,server_cfg— loaded configurationmanual_event_discovery,manual_job_discovery— optional background runners
Handlers (ocg-server/src/handlers/)
One subdirectory per domain: alliance, auth, event, group, images, meetings, payments, site, dashboard.
Services (ocg-server/src/services/)
Long-running background tasks started in main.rs:
| Module | Role |
|---|---|
event_discovery.rs |
Scheduled You.com search for upcoming events |
job_discovery.rs |
Scheduled You.com search for job postings |
meetings.rs |
Meeting lifecycle, host pool, auto-end |
notifications.rs |
Outbound email notifications |
payments.rs |
Stripe payment processing |
recording_publishing.rs |
Google Drive → YouTube publishing |
images.rs |
Image upload and storage |
Database layer (ocg-server/src/db/)
Each domain has its own module (e.g., db/alliance.rs, db/event.rs). All modules implement methods on PgDB which satisfies the DynDB trait object. Queries use deadpool-postgres prepared statements.
Configuration (ocg-server/src/config.rs)
Configuration is loaded via Figment in Config::new():
- Compiled-in defaults (e.g.,
server.addr = "127.0.0.1:9000",log.format = "json") - Optional YAML file (path passed with
-c) OCG_*environment variables with__as nested-key separator
Templates (ocg-server/src/templates/, ocg-server/templates/)
MiniJinja templates are embedded in the binary. The template engine is initialised in templates.rs. HTMX is used for partial-page updates; the server detects hx-request and x-ocg-fetch headers to serve fragment or full-page responses.
Validation (ocg-server/src/validation.rs)
Input structs are annotated with garde rules and validated before being passed to the database layer.
ocg-redirector
A minimal axum server defined in ocg-redirector/. It reads a YAML config and redirects a set of short paths to their canonical URLs. It shares the Cargo workspace but has no dependency on ocg-server.
mcp/
A Node.js JSON-RPC server (mcp/server.mjs) implementing the Model Context Protocol. It exposes operational tools (defined in mcp/tools.json) that call the ocg-server HTTP API. It is intended for use by AI assistants and automation agents.
Database migrations
SQL migrations live in database/migrations/schema/ and are applied by tern via database/migrations/migrate.sh. The justfile provides just db-migrate and related tasks. The current schema is established by 0001_initial.sql; subsequent files are numbered sequentially.
Request flow
sequenceDiagram
participant B as Browser
participant S as ocg-server
participant DB as PostgreSQL
B->>S: GET /{alliance}/group/{slug}
S->>S: refresh_stale_clients middleware
S->>S: login_required check (public route — pass)
S->>DB: db.get_group_by_slug()
DB-->>S: Group row
S->>S: render MiniJinja template
S-->>B: HTML (full page or HTMX fragment)
Deployment
The application is deployed to Kubernetes using the Helm chart in charts/goup/. Static assets are embedded in the binary via rust-embed, so no separate static file server is needed.