1use std::marker::PhantomData;
11use std::path::PathBuf;
12use std::sync::{Arc, Mutex};
13use std::{env, fs};
14
15use actix_web::web::ServiceConfig;
16use arc_core::aggregate::Aggregate;
17use arc_core::command_bus::SnapshotPolicy;
18use arc_core::projection::Projector;
19use tracing::{debug, error, info};
20
21pub mod helpers {
22 pub mod access_log;
23 pub mod audit_context;
24 pub mod config;
25 pub mod csrf;
26 pub mod database;
27 pub mod es_stack;
28 pub mod general;
29 pub mod jwt;
30 pub mod rate_limit;
31 pub mod session;
32 pub mod template;
33}
34
35pub mod http {
36 pub mod middlewares {
37 pub mod auth_middleware;
38 pub mod idle_timeout_middleware;
39 pub mod jwt_middleware;
40 pub mod rate_limit_middleware;
41 }
42 pub mod errors;
43}
44
45pub mod commands;
46pub mod websocket;
47
48#[derive(Debug)]
50pub struct AppState {
51 pub app_name: Mutex<String>,
52}
53
54pub struct ProjectorReg {
58 pub projector: Box<dyn Projector>,
59 pub view: String,
60}
61
62impl ProjectorReg {
63 pub fn new(projector: impl Projector + 'static, view: impl Into<String>) -> Self {
64 Self {
65 projector: Box::new(projector),
66 view: view.into(),
67 }
68 }
69}
70
71pub struct ArcApp;
75
76impl ArcApp {
77 pub fn builder<A: Aggregate + 'static>() -> ArcAppBuilder<A> {
78 ArcAppBuilder {
79 projectors: Vec::new(),
80 routes: None,
81 snapshot_policy: None,
82 _marker: PhantomData,
83 }
84 }
85}
86
87type RoutesFn = dyn Fn(&mut ServiceConfig) + Send + Sync + 'static;
88
89pub struct ArcAppBuilder<A: Aggregate + 'static> {
93 projectors: Vec<ProjectorReg>,
94 routes: Option<Arc<RoutesFn>>,
95 snapshot_policy: Option<SnapshotPolicy>,
96 _marker: PhantomData<A>,
97}
98
99impl<A: Aggregate + 'static> ArcAppBuilder<A> {
100 pub fn register_aggregate(mut self, projectors: Vec<ProjectorReg>) -> Self {
103 self.projectors = projectors;
104 self
105 }
106
107 pub fn register_projector(
110 mut self,
111 projector: impl Projector + 'static,
112 view: impl Into<String>,
113 ) -> Self {
114 self.projectors.push(ProjectorReg::new(projector, view));
115 self
116 }
117
118 pub fn register_routes<F>(mut self, f: F) -> Self
122 where
123 F: Fn(&mut ServiceConfig) + Send + Sync + 'static,
124 {
125 self.routes = Some(Arc::new(f));
126 self
127 }
128
129 pub fn snapshot_policy(mut self, policy: Option<SnapshotPolicy>) -> Self {
131 self.snapshot_policy = policy;
132 self
133 }
134
135 pub async fn serve(self, app_url: String, app_port: u16) -> std::io::Result<()> {
138 let routes = self
139 .routes
140 .expect("ArcAppBuilder::serve requires register_routes(..)");
141 commands::serve::run::<A>(
142 app_url,
143 app_port,
144 self.projectors,
145 self.snapshot_policy,
146 routes,
147 )
148 .await
149 }
150}
151
152pub fn check_database_health() {
156 info!("Checking database health");
157 let driver = helpers::config::DatabaseDriver::from_env();
158
159 if !driver.is_file_backed() {
160 debug!(
161 driver = driver.as_str(),
162 "Database driver uses a connection string; skipping filesystem check"
163 );
164 return;
165 }
166
167 let database: String = helpers::config::database_url();
168 if !fs::exists(PathBuf::from(&database)).unwrap() {
169 error!("Database file not found at: {}", database);
170 error!("Please run `cargo run migrate` to create the database");
171 std::process::exit(1);
172 }
173 debug!("Database file found at: {}", database);
174}
175
176pub fn check_app_health() {
178 info!("Checking app health");
179 if !fs::exists(PathBuf::from(".env")).unwrap() {
180 info!("Creating .env file from .env.example");
181 fs::copy(PathBuf::from(".env.example"), PathBuf::from(".env"))
182 .expect("Failed to copy .env.example to .env");
183 }
184}
185
186pub fn validate_environment() {
188 let required_vars = ["APP_URL", "SECRET_KEY", "DATABASE_URL"];
189 let mut missing = Vec::new();
190 for var in required_vars {
191 if env::var(var).is_err() {
192 missing.push(var);
193 }
194 }
195 if !missing.is_empty() {
196 error!(
197 "Missing required environment variables: {}. Check your .env file.",
198 missing.join(", ")
199 );
200 std::process::exit(1);
201 }
202 debug!("All required environment variables present");
203}