1#[cfg(feature = "enterprise")]
9mod audit;
10mod auth;
11pub mod caching;
12mod docs;
13mod graphql;
14mod handlers_simple;
15mod handlers_swe;
16#[cfg(feature = "enterprise")]
17mod retention;
18pub mod sdk;
19#[cfg(feature = "enterprise")]
20mod sso;
21mod recording;
22mod state;
23mod sync;
24mod webhooks;
25mod websocket;
26
27pub use recording::{
28 configure_recording_routes, create_recording_state,
29};
30#[cfg(feature = "enterprise")]
31pub use audit::{
32 configure_audit_routes, AuditAction, AuditCategory, AuditEvent, AuditEventBuilder, AuditService,
33};
34pub use auth::configure_auth_routes;
35#[cfg(feature = "enterprise")]
36pub use retention::{configure_retention_routes, RetentionPolicy, RetentionService};
37#[cfg(feature = "enterprise")]
38pub use sso::{configure_sso_routes, SamlIdpConfig, SsoService};
39pub use state::AppState;
40pub use sync::{configure_sync_routes, create_sync_state};
41pub use websocket::{configure_websocket_routes, WebSocketState};
42
43use actix_cors::Cors;
44use actix_web::{middleware, web, App, HttpServer};
45use anyhow::Result;
46use std::path::PathBuf;
47
48use crate::database::ChatDatabase;
49
50#[derive(Debug, Clone)]
52pub struct ServerConfig {
53 pub host: String,
54 pub port: u16,
55 pub database_path: String,
56 pub cors_origins: Vec<String>,
57}
58
59impl Default for ServerConfig {
60 fn default() -> Self {
61 Self {
62 host: "0.0.0.0".to_string(), port: 8787,
64 database_path: dirs::data_local_dir()
65 .map(|p| p.join("csm").join("csm.db").to_string_lossy().to_string())
66 .unwrap_or_else(|| "csm.db".to_string()),
67 cors_origins: vec![
68 "http://localhost:5173".to_string(),
69 "http://localhost:3000".to_string(),
70 "http://127.0.0.1:5173".to_string(),
71 "http://127.0.0.1:3000".to_string(),
72 "http://localhost:8081".to_string(), "http://127.0.0.1:8081".to_string(), "http://localhost:19006".to_string(), "http://127.0.0.1:19006".to_string(), ],
77 }
78 }
79}
80
81fn configure_routes(cfg: &mut web::ServiceConfig) {
83 use handlers_simple::*;
84
85 eprintln!("[DEBUG] Configuring routes...");
86
87 cfg.service(
89 web::scope("/api")
90 .route("/health", web::get().to(health_check))
91 .route("/workspaces", web::get().to(list_workspaces))
92 .route("/workspaces/{id}", web::get().to(get_workspace))
93 .route("/sessions", web::get().to(list_sessions))
94 .route("/sessions/search", web::get().to(search_sessions))
95 .route("/sessions/{id}", web::get().to(get_session))
96 .route("/providers", web::get().to(list_providers))
97 .route("/stats", web::get().to(get_stats))
98 .route("/stats/overview", web::get().to(get_stats))
99 .route("/agents", web::get().to(list_agents))
101 .route("/agents", web::post().to(create_agent))
102 .route("/agents/{id}", web::get().to(get_agent))
103 .route("/agents/{id}", web::put().to(update_agent))
104 .route("/agents/{id}", web::delete().to(delete_agent))
105 .route("/swarms", web::get().to(list_swarms))
107 .route("/swarms", web::post().to(create_swarm))
108 .route("/swarms/{id}", web::get().to(get_swarm))
109 .route("/swarms/{id}", web::delete().to(delete_swarm))
110 .route("/settings", web::get().to(get_settings))
112 .route("/settings", web::put().to(update_settings))
113 .route("/settings/accounts", web::get().to(list_accounts))
114 .route("/settings/accounts", web::post().to(create_account))
115 .route("/settings/accounts/{id}", web::delete().to(delete_account))
116 .route("/system/info", web::get().to(get_system_info))
118 .route("/system/health", web::get().to(get_system_health))
119 .route(
120 "/system/providers/health",
121 web::get().to(get_provider_health),
122 )
123 .route("/mcp/tools", web::get().to(list_mcp_tools))
125 .route("/mcp/call", web::post().to(call_mcp_tool))
126 .route("/mcp/batch", web::post().to(call_mcp_tools_batch))
127 .route("/mcp/system-prompt", web::get().to(get_csm_system_prompt))
128 .route("/swe/projects", web::get().to(handlers_swe::list_projects))
130 .route(
131 "/swe/projects",
132 web::post().to(handlers_swe::create_project),
133 )
134 .route(
135 "/swe/projects/{id}",
136 web::get().to(handlers_swe::get_project),
137 )
138 .route(
139 "/swe/projects/{id}",
140 web::delete().to(handlers_swe::delete_project),
141 )
142 .route(
143 "/swe/projects/{id}/open",
144 web::post().to(handlers_swe::open_project),
145 )
146 .route(
147 "/swe/projects/{id}/context",
148 web::get().to(handlers_swe::get_context),
149 )
150 .route(
151 "/swe/projects/{id}/execute",
152 web::post().to(handlers_swe::execute_tool),
153 )
154 .route(
155 "/swe/projects/{project_id}/memory",
156 web::get().to(handlers_swe::list_memory),
157 )
158 .route(
159 "/swe/projects/{project_id}/memory",
160 web::post().to(handlers_swe::create_memory),
161 )
162 .route(
163 "/swe/projects/{project_id}/memory/{id}",
164 web::get().to(handlers_swe::get_memory),
165 )
166 .route(
167 "/swe/projects/{project_id}/memory/{id}",
168 web::put().to(handlers_swe::update_memory),
169 )
170 .route(
171 "/swe/projects/{project_id}/memory/{id}",
172 web::delete().to(handlers_swe::delete_memory),
173 )
174 .route(
175 "/swe/projects/{project_id}/rules",
176 web::get().to(handlers_swe::list_rules),
177 )
178 .route(
179 "/swe/projects/{project_id}/rules",
180 web::post().to(handlers_swe::create_rule),
181 )
182 .route(
183 "/swe/projects/{project_id}/rules/{id}",
184 web::put().to(handlers_swe::update_rule),
185 )
186 .route(
187 "/swe/projects/{project_id}/rules/{id}",
188 web::delete().to(handlers_swe::delete_rule),
189 ),
190 );
191
192 eprintln!("[DEBUG] Added /api routes");
193}
194
195pub async fn start_server(config: ServerConfig) -> Result<()> {
197 let db_path = PathBuf::from(&config.database_path);
199 if let Some(parent) = db_path.parent() {
200 std::fs::create_dir_all(parent)?;
201 }
202
203 let db = ChatDatabase::open(&db_path)?;
205
206 {
208 let conn = rusqlite::Connection::open(&db_path)?;
209 if let Err(e) = handlers_swe::init_swe_tables(&conn) {
210 eprintln!("[WARN] Failed to initialize SWE tables: {}", e);
211 }
212 }
213
214 {
216 let conn = rusqlite::Connection::open(&db_path)?;
217 if let Err(e) = auth::init_auth_tables(&conn) {
218 eprintln!("[WARN] Failed to initialize Auth tables: {}", e);
219 }
220 }
221
222 let state = web::Data::new(AppState::new(db, db_path));
223 let sync_state = web::Data::new(create_sync_state());
224 let ws_state = web::Data::new(WebSocketState::new());
225 let recording_state = web::Data::new(create_recording_state());
226 let cors_origins = config.cors_origins.clone();
227
228 println!("[*] CSM API Server starting...");
229 println!(" Address: http://{}:{}", config.host, config.port);
230 println!(" Database: {}", config.database_path);
231 println!();
232 println!("[*] Mobile app endpoints:");
233 println!(" GET /api/workspaces - List workspaces");
234 println!(" GET /api/sessions - List sessions");
235 println!(" GET /api/sessions/:id - Get session details");
236 println!(" GET /api/stats - Database statistics");
237 println!();
238 println!("[*] SWE Mode endpoints:");
239 println!(" GET /api/swe/projects - List SWE projects");
240 println!(" POST /api/swe/projects - Create SWE project");
241 println!();
242 println!("[*] Recording endpoints:");
243 println!(" POST /recording/events - Send recording events");
244 println!(" POST /recording/snapshot - Store session snapshot");
245 println!(" GET /recording/sessions - List active sessions");
246 println!(" GET /recording/status - Recording status");
247 println!(" GET /recording/ws - WebSocket for real-time recording");
248 println!();
249 println!("[*] Sync endpoints:");
250 println!(" GET /sync/version - Get current sync version");
251 println!(" GET /sync/delta?from=N - Get changes since version N");
252 println!(" POST /sync/event - Push a sync event");
253 println!(" GET /sync/snapshot - Get full data snapshot");
254 println!(" GET /sync/subscribe - SSE stream for real-time updates");
255 println!(" GET /ws - WebSocket for bidirectional updates");
256 println!();
257 println!("Press Ctrl+C to stop the server...");
258 println!();
259
260 eprintln!("[DEBUG] Creating HttpServer...");
261 let server = HttpServer::new(move || {
262 let origins = cors_origins.clone();
263 let cors = Cors::default()
264 .allowed_origin_fn(move |origin, _req_head| {
265 let origin_str = origin.to_str().unwrap_or("");
266 origins.iter().any(|allowed| allowed == origin_str)
267 || origin_str.starts_with("http://localhost:")
268 || origin_str.starts_with("http://127.0.0.1:")
269 || origin_str.starts_with("exp://")
270 })
271 .allowed_methods(vec!["GET", "POST", "PUT", "DELETE", "OPTIONS"])
272 .allowed_headers(vec!["Content-Type", "Authorization", "Accept"])
273 .supports_credentials()
274 .max_age(3600);
275
276 App::new()
277 .app_data(state.clone())
278 .app_data(sync_state.clone())
279 .app_data(ws_state.clone())
280 .app_data(recording_state.clone())
281 .wrap(cors)
282 .wrap(middleware::Logger::default())
283 .configure(configure_routes)
284 .configure(configure_sync_routes)
285 .configure(configure_auth_routes)
286 .configure(configure_recording_routes)
287 .configure(|cfg| configure_websocket_routes(cfg, ws_state.clone()))
288 });
289
290 eprintln!("[DEBUG] Binding to {}:{}...", config.host, config.port);
291 let server = server.bind((config.host.as_str(), config.port))?;
292
293 eprintln!("[DEBUG] Starting server...");
294 server.run().await?;
295
296 eprintln!("[DEBUG] Server stopped.");
297 Ok(())
298}
299
300
301