1pub mod api_keys;
30pub mod auth;
31pub mod export;
32pub mod profile;
33pub mod settings;
34pub mod sync;
35
36#[cfg(feature = "portal")]
38pub mod db;
39
40#[cfg(feature = "portal")]
42pub mod auth_db;
43
44#[cfg(feature = "portal")]
46pub mod profile_db;
47
48#[cfg(feature = "portal")]
50pub mod settings_db;
51
52#[cfg(feature = "portal")]
54pub mod api_keys_db;
55
56#[cfg(feature = "portal")]
58pub mod rate_limiting;
59
60#[cfg(feature = "portal")]
62pub mod usage_tracking;
63
64#[cfg(feature = "portal")]
66pub mod export_db;
67
68#[cfg(feature = "portal")]
70pub mod sync_db;
71
72#[cfg(feature = "portal")]
74pub mod sync_ws;
75
76pub mod middleware;
78
79use axum::{
80 routing::{delete, get, post, put},
81 Router,
82};
83
84pub use api_keys::{ApiKey, ApiKeyScope};
85pub use auth::{AuthConfig, AuthService, Claims, TokenPair};
86pub use export::{ExportConfig, ExportFormat, ExportJob, ExportService};
87pub use profile::{Profile, ProfileService, ProfileUpdate};
88pub use settings::{SettingsService, UserSettings};
89pub use sync::{SyncConfig, SyncEvent, SyncService};
90
91#[cfg(feature = "portal")]
92pub use db::{DatabasePool, DbError};
93
94#[cfg(feature = "portal")]
95pub use auth_db::PortalState;
96
97#[cfg(feature = "portal")]
98pub use sync_ws::{SyncWsState, WsState};
99
100#[cfg(feature = "portal")]
101pub use sync_db::{
102 DbDeviceSession, DeviceSessionRepository, SyncConflict, SyncConflictRepository, SyncQueueItem,
103 SyncQueueRepository, SyncState, SyncStateRepository,
104};
105
106pub use middleware::{optional_auth, require_auth, AuthClaims};
107
108pub fn portal_router() -> Router {
110 Router::new()
111 .route("/auth/register", post(auth::handlers::register))
113 .route("/auth/login", post(auth::handlers::login))
114 .route("/auth/logout", post(auth::handlers::logout))
115 .route("/auth/refresh", post(auth::handlers::refresh_token))
116 .route(
117 "/auth/password/reset",
118 post(auth::handlers::request_password_reset),
119 )
120 .route(
121 "/auth/password/reset/:token",
122 post(auth::handlers::reset_password),
123 )
124 .route("/auth/2fa/setup", post(auth::handlers::setup_2fa))
125 .route("/auth/2fa/verify", post(auth::handlers::verify_2fa))
126 .route("/profile", get(profile::handlers::get_profile))
128 .route("/profile", put(profile::handlers::update_profile))
129 .route("/profile/avatar", post(profile::handlers::upload_avatar))
130 .route("/profile/delete", delete(profile::handlers::delete_account))
131 .route("/settings", get(settings::handlers::get_settings))
133 .route("/settings", put(settings::handlers::update_settings))
134 .route("/settings/sync", post(settings::handlers::sync_settings))
135 .route("/api-keys", get(api_keys::handlers::list_keys))
137 .route("/api-keys", post(api_keys::handlers::create_key))
138 .route("/api-keys/:id", delete(api_keys::handlers::revoke_key))
139 .route("/api-keys/:id/rotate", post(api_keys::handlers::rotate_key))
140 .route("/export", post(export::handlers::create_export))
142 .route("/export/:id", get(export::handlers::get_export_status))
143 .route(
144 "/export/:id/download",
145 get(export::handlers::download_export),
146 )
147 .route("/export/schedule", post(export::handlers::schedule_export))
148 .route("/export/history", get(export::handlers::export_history))
149}
150
151#[derive(Debug, Clone)]
153pub struct PortalConfig {
154 pub jwt_secret: String,
156 pub token_expiry_secs: u64,
158 pub refresh_token_expiry_secs: u64,
160 pub require_2fa_for_sensitive: bool,
162 pub max_api_keys_per_user: usize,
164 pub export_storage_path: String,
166 pub export_retention_days: u32,
168}
169
170impl Default for PortalConfig {
171 fn default() -> Self {
172 Self {
173 jwt_secret: std::env::var("JWT_SECRET")
174 .unwrap_or_else(|_| "change-me-in-production".to_string()),
175 token_expiry_secs: 3600, refresh_token_expiry_secs: 604800, require_2fa_for_sensitive: false,
178 max_api_keys_per_user: 10,
179 export_storage_path: "/var/lib/reasonkit/exports".to_string(),
180 export_retention_days: 30,
181 }
182 }
183}
184
185#[cfg(feature = "portal")]
190pub fn portal_router_with_db(state: PortalState) -> Router {
191 use axum::middleware as axum_middleware;
192
193 let public_routes = Router::new()
195 .route("/auth/register", post(auth_db::register))
196 .route("/auth/login", post(auth_db::login))
197 .route("/auth/refresh", post(auth_db::refresh_token))
198 .route(
200 "/auth/password/reset",
201 post(auth::handlers::request_password_reset),
202 )
203 .route(
204 "/auth/password/reset/:token",
205 post(auth::handlers::reset_password),
206 );
207
208 let protected_routes = Router::new()
210 .route("/auth/logout", post(auth_db::logout))
212 .route("/auth/2fa/setup", post(auth::handlers::setup_2fa))
213 .route("/auth/2fa/verify", post(auth::handlers::verify_2fa))
214 .route("/profile", get(profile_db::get_profile))
216 .route("/profile", put(profile_db::update_profile))
217 .route("/profile/avatar", post(profile_db::upload_avatar))
218 .route("/profile/delete", delete(profile_db::delete_account))
219 .route("/settings", get(settings_db::get_settings))
221 .route("/settings", put(settings_db::update_settings))
222 .route("/settings/:key", get(settings_db::get_setting))
223 .route("/settings/:key", put(settings_db::set_setting))
224 .route("/settings/:key", delete(settings_db::delete_setting))
225 .route("/settings/sync", post(settings_db::sync_settings))
226 .route("/api-keys", get(api_keys_db::list_keys))
228 .route("/api-keys", post(api_keys_db::create_key))
229 .route("/api-keys/:id", delete(api_keys_db::revoke_key))
230 .route("/api-keys/:id/rotate", post(api_keys_db::rotate_key))
231 .route("/api-keys/:id/usage", get(usage_tracking::get_usage_stats))
233 .route(
234 "/api-keys/:id/usage/daily",
235 get(usage_tracking::get_daily_usage),
236 )
237 .route(
238 "/api-keys/:id/usage/endpoints",
239 get(usage_tracking::get_endpoint_usage),
240 )
241 .route("/export", post(export_db::create_export))
243 .route("/export/:id", get(export_db::get_export_status))
244 .route("/export/:id/download", get(export_db::download_export))
245 .route("/export/history", get(export_db::export_history))
246 .route_layer(axum_middleware::from_fn(middleware::require_auth));
247
248 Router::new()
250 .merge(public_routes)
251 .merge(protected_routes)
252 .with_state(state)
253}
254
255#[cfg(feature = "portal")]
261pub fn portal_router_with_sync(portal_state: PortalState, ws_state: WsState) -> Router {
262 use axum::middleware as axum_middleware;
263
264 let sync_state = SyncWsState {
265 portal: portal_state.clone(),
266 ws: ws_state,
267 };
268
269 let public_routes = Router::new()
271 .route("/auth/register", post(auth_db::register))
272 .route("/auth/login", post(auth_db::login))
273 .route("/auth/refresh", post(auth_db::refresh_token))
274 .route(
275 "/auth/password/reset",
276 post(auth::handlers::request_password_reset),
277 )
278 .route(
279 "/auth/password/reset/:token",
280 post(auth::handlers::reset_password),
281 )
282 .with_state(portal_state.clone());
283
284 let ws_routes = Router::new()
286 .route("/sync/ws", get(sync_ws::ws_handler))
287 .with_state(sync_state.clone());
288
289 let sync_protected = Router::new()
291 .route("/sync/status", get(sync_ws::get_sync_status))
292 .route("/sync/conflicts", get(sync_ws::get_conflicts))
293 .route(
294 "/sync/conflicts/:id/resolve",
295 post(sync_ws::resolve_conflict),
296 )
297 .route_layer(axum_middleware::from_fn(middleware::require_auth))
298 .with_state(sync_state);
299
300 let protected_routes = Router::new()
302 .route("/auth/logout", post(auth_db::logout))
304 .route("/auth/2fa/setup", post(auth::handlers::setup_2fa))
305 .route("/auth/2fa/verify", post(auth::handlers::verify_2fa))
306 .route("/profile", get(profile_db::get_profile))
308 .route("/profile", put(profile_db::update_profile))
309 .route("/profile/avatar", post(profile_db::upload_avatar))
310 .route("/profile/delete", delete(profile_db::delete_account))
311 .route("/settings", get(settings_db::get_settings))
313 .route("/settings", put(settings_db::update_settings))
314 .route("/settings/:key", get(settings_db::get_setting))
315 .route("/settings/:key", put(settings_db::set_setting))
316 .route("/settings/:key", delete(settings_db::delete_setting))
317 .route("/settings/sync", post(settings_db::sync_settings))
318 .route("/api-keys", get(api_keys_db::list_keys))
320 .route("/api-keys", post(api_keys_db::create_key))
321 .route("/api-keys/:id", delete(api_keys_db::revoke_key))
322 .route("/api-keys/:id/rotate", post(api_keys_db::rotate_key))
323 .route("/api-keys/:id/usage", get(usage_tracking::get_usage_stats))
325 .route(
326 "/api-keys/:id/usage/daily",
327 get(usage_tracking::get_daily_usage),
328 )
329 .route(
330 "/api-keys/:id/usage/endpoints",
331 get(usage_tracking::get_endpoint_usage),
332 )
333 .route("/export", post(export_db::create_export))
335 .route("/export/:id", get(export_db::get_export_status))
336 .route("/export/:id/download", get(export_db::download_export))
337 .route("/export/history", get(export_db::export_history))
338 .route_layer(axum_middleware::from_fn(middleware::require_auth))
339 .with_state(portal_state);
340
341 Router::new()
343 .merge(public_routes)
344 .merge(ws_routes)
345 .merge(sync_protected)
346 .merge(protected_routes)
347}