cja/
lib.rs

1#![doc(html_favicon_url = "https://cja.app/favicon.svg")]
2//! # CJA — Cron, Jobs and Axum
3//!
4//! > **ALPHA**: CJA is under active development. APIs may change between releases.
5//!
6//! A Rust web framework that combines background job processing, cron scheduling,
7//! and an HTTP server built on [Axum](https://docs.rs/axum).
8//!
9//! ## Overview
10//!
11//! CJA provides three integrated subsystems:
12//!
13//! - **[`jobs`]** — PostgreSQL-backed background job queue with automatic retries,
14//!   exponential backoff, dead letter queue, and graceful shutdown
15//! - **[`cron`]** — Scheduled task execution with both interval-based and cron expression
16//!   scheduling, timezone support
17//! - **[`server`]** — HTTP server on Axum with encrypted cookie management, database-backed
18//!   sessions, and zero-downtime reload via systemfd
19//!
20//! All three share a single [`app_state::AppState`] and coordinate shutdown via
21//! [`tokio_util::sync::CancellationToken`].
22//!
23//! ## Getting Started
24//!
25//! Add CJA to your project:
26//!
27//! ```bash
28//! cargo add cja
29//! ```
30//!
31//! ### 1. Implement `AppState`
32//!
33//! ```rust
34//! use cja::app_state::AppState;
35//! use cja::server::cookies::CookieKey;
36//!
37//! #[derive(Clone)]
38//! struct MyAppState {
39//!     db: sqlx::PgPool,
40//!     cookie_key: CookieKey,
41//! }
42//!
43//! impl AppState for MyAppState {
44//!     fn version(&self) -> &str { "1.0.0" }
45//!     fn db(&self) -> &sqlx::PgPool { &self.db }
46//!     fn cookie_key(&self) -> &CookieKey { &self.cookie_key }
47//! }
48//! ```
49//!
50//! [`app_state::AppState`] is cloned per request — wrap expensive resources in `Arc`.
51//!
52//! ### 2. Set Up the Database
53//!
54//! CJA requires `PostgreSQL`. Set `DATABASE_URL` and run migrations on startup:
55//!
56//! ```rust,ignore
57//! let pool = sqlx::postgres::PgPoolOptions::new()
58//!     .max_connections(5)
59//!     .connect(&std::env::var("DATABASE_URL")?)
60//!     .await?;
61//!
62//! // Run CJA's built-in migrations (jobs, crons, sessions, dead_letter_jobs)
63//! cja::db::run_migrations(&pool).await?;
64//! ```
65//!
66//! ### 3. Wire Up `main()`
67//!
68//! ```rust,ignore
69//! use cja::setup::{setup_sentry, setup_tracing};
70//! use cja::tasks::NamedTask;
71//! use cja::jobs::CancellationToken;
72//!
73//! fn main() -> cja::Result<()> {
74//!     let _sentry_guard = setup_sentry();
75//!     tokio::runtime::Builder::new_multi_thread()
76//!         .enable_all()
77//!         .build()?
78//!         .block_on(run())
79//! }
80//!
81//! async fn run() -> cja::Result<()> {
82//!     // Keep the Eyes handle alive for graceful shutdown
83//!     let _eyes_handle = setup_tracing("my-app")?;
84//!
85//!     let app_state = MyAppState { /* ... */ };
86//!     let shutdown_token = CancellationToken::new();
87//!
88//!     let mut tasks = vec![];
89//!
90//!     // HTTP server
91//!     tasks.push(NamedTask::spawn("server",
92//!         cja::server::run_server(routes(app_state.clone()))
93//!     ));
94//!
95//!     // Job worker
96//!     tasks.push(NamedTask::spawn("jobs",
97//!         cja::jobs::worker::job_worker(
98//!             app_state.clone(),
99//!             Jobs,  // your job registry (see jobs module docs)
100//!             std::time::Duration::from_secs(60),
101//!             cja::jobs::DEFAULT_MAX_RETRIES,
102//!             shutdown_token.clone(),
103//!             cja::jobs::DEFAULT_LOCK_TIMEOUT,
104//!         )
105//!     ));
106//!
107//!     // Signal handler
108//!     let token = shutdown_token.clone();
109//!     tasks.push(NamedTask::spawn("signals", async move {
110//!         tokio::signal::ctrl_c().await?;
111//!         token.cancel();
112//!         Ok(())
113//!     }));
114//!
115//!     cja::tasks::wait_for_first_error(tasks).await
116//! }
117//! ```
118//!
119//! See `crates/cja.app/` in the repository for a complete working example.
120//!
121//! ## Configuration
122//!
123//! | Variable | Required | Default | Description |
124//! |----------|----------|---------|-------------|
125//! | `DATABASE_URL` | Yes | — | `PostgreSQL` connection string |
126//! | `PORT` | No | `3000` | HTTP server port |
127//! | `COOKIE_KEY` | No | Auto-generated | Base64-encoded 64-byte cookie encryption key. **Must be set in production** for session persistence across restarts. |
128//! | `SERVER_DISABLED` | No | `false` | Set to `"true"` to skip starting the HTTP server |
129//! | `JOBS_DISABLED` | No | `false` | Set to `"true"` to skip starting the job worker |
130//! | `CRON_DISABLED` | No | `false` | Set to `"true"` to skip starting the cron worker |
131//! | `RUST_LOG` | No | `info` | Tracing filter directive |
132//! | `JSON_LOGS` | No | — | If set, outputs JSON logs instead of tree format |
133//! | `SENTRY_DSN` | No | — | Enables Sentry error tracking |
134//! | `HONEYCOMB_API_KEY` | No | — | Enables OpenTelemetry export to Honeycomb |
135//! | `EYES_ORG_ID` | No | — | Eyes tracing org ID (both `ORG_ID` and `APP_ID` required) |
136//! | `EYES_APP_ID` | No | — | Eyes tracing app ID |
137//! | `EYES_URL` | No | `https://eyes.coreyja.com` | Eyes server URL |
138//!
139//! ## Feature Flags
140//!
141//! ```toml
142//! [features]
143//! default = ["cron", "jobs"]
144//! cron = ["jobs"]           # cron depends on jobs
145//! jobs = []
146//! testing = []              # enables mock OAuth for tests
147//! ```
148//!
149//! Disabling `jobs` at compile time also disables `cron`.
150//!
151//! ## Re-exports
152//!
153//! CJA re-exports key dependencies so you don't need to add them directly:
154//!
155//! ```rust
156//! use cja::sqlx;
157//! use cja::uuid;
158//! use cja::color_eyre;
159//! use cja::maud;
160//! use cja::chrono;
161//! use cja::chrono_tz;
162//! ```
163
164pub use sqlx;
165pub use uuid;
166
167#[cfg(feature = "cron")]
168pub mod cron;
169#[cfg(feature = "jobs")]
170pub mod jobs;
171pub mod server;
172#[cfg(feature = "testing")]
173pub mod testing;
174
175pub mod app_state;
176pub mod setup;
177
178pub use color_eyre;
179pub use color_eyre::Result;
180
181pub use maud;
182
183pub mod db;
184pub mod tasks;
185
186pub use chrono;
187pub use chrono_tz;