1pub mod account;
8pub mod auth;
9pub mod dashboard;
10pub mod error;
11pub mod routes;
12pub mod state;
13pub mod ws;
14
15use std::sync::Arc;
16
17use axum::extract::DefaultBodyLimit;
18use axum::middleware;
19use axum::routing::{delete, get, patch, post};
20use axum::Router;
21use tower_http::cors::CorsLayer;
22use tower_http::trace::TraceLayer;
23
24use crate::state::AppState;
25
26pub fn build_router(state: Arc<AppState>) -> Router {
28 let api = Router::new()
29 .route("/health", get(routes::health::health))
30 .route("/health/detailed", get(routes::health::health_detailed))
31 .route("/auth/login", post(auth::routes::login))
33 .route("/auth/logout", post(auth::routes::logout))
34 .route("/auth/status", get(auth::routes::status))
35 .route("/analytics/summary", get(routes::analytics::summary))
37 .route("/analytics/followers", get(routes::analytics::followers))
38 .route(
39 "/analytics/performance",
40 get(routes::analytics::performance),
41 )
42 .route("/analytics/topics", get(routes::analytics::topics))
43 .route(
44 "/analytics/recent-performance",
45 get(routes::analytics::recent_performance),
46 )
47 .route(
48 "/analytics/engagement-rate",
49 get(routes::analytics::engagement_rate),
50 )
51 .route("/analytics/reach", get(routes::analytics::reach))
52 .route(
53 "/analytics/follower-growth",
54 get(routes::analytics::follower_growth),
55 )
56 .route("/analytics/best-times", get(routes::analytics::best_times))
57 .route("/approval/export", get(routes::approval::export_items))
59 .route("/approval", get(routes::approval::list_items))
60 .route("/approval/stats", get(routes::approval::stats))
61 .route("/approval/approve-all", post(routes::approval::approve_all))
62 .route(
63 "/approval/{id}/history",
64 get(routes::approval::get_edit_history),
65 )
66 .route("/approval/{id}", patch(routes::approval::edit_item))
67 .route(
68 "/approval/{id}/approve",
69 post(routes::approval::approve_item),
70 )
71 .route("/approval/{id}/reject", post(routes::approval::reject_item))
72 .route("/activity/export", get(routes::activity::export_activity))
74 .route("/activity", get(routes::activity::list_activity))
75 .route(
76 "/activity/rate-limits",
77 get(routes::activity::rate_limit_usage),
78 )
79 .route("/replies", get(routes::replies::list_replies))
81 .route(
83 "/content/tweets",
84 get(routes::content::list_tweets).post(routes::content::compose_tweet),
85 )
86 .route(
87 "/content/threads",
88 get(routes::content::list_threads).post(routes::content::compose_thread),
89 )
90 .route("/content/calendar", get(routes::content::calendar))
91 .route("/content/schedule", get(routes::content::schedule))
92 .route("/content/compose", post(routes::content::compose))
93 .route(
94 "/content/scheduled/{id}",
95 patch(routes::content::edit_scheduled).delete(routes::content::cancel_scheduled),
96 )
97 .route(
99 "/tags",
100 get(routes::content::list_account_tags).post(routes::content::create_account_tag),
101 )
102 .route(
104 "/drafts",
105 get(routes::content::list_studio_drafts).post(routes::content::create_studio_draft),
106 )
107 .route(
108 "/drafts/{id}",
109 get(routes::content::get_studio_draft)
110 .patch(routes::content::autosave_draft)
111 .delete(routes::content::delete_draft),
112 )
113 .route(
114 "/drafts/{id}/meta",
115 patch(routes::content::patch_draft_meta),
116 )
117 .route(
118 "/drafts/{id}/schedule",
119 post(routes::content::schedule_studio_draft),
120 )
121 .route(
122 "/drafts/{id}/reschedule",
123 patch(routes::content::reschedule_studio_draft),
124 )
125 .route(
126 "/drafts/{id}/unschedule",
127 post(routes::content::unschedule_studio_draft),
128 )
129 .route(
130 "/drafts/{id}/archive",
131 post(routes::content::archive_studio_draft),
132 )
133 .route(
134 "/drafts/{id}/restore",
135 post(routes::content::restore_studio_draft),
136 )
137 .route(
138 "/drafts/{id}/duplicate",
139 post(routes::content::duplicate_studio_draft),
140 )
141 .route(
142 "/drafts/{id}/revisions",
143 get(routes::content::list_draft_revisions).post(routes::content::create_draft_revision),
144 )
145 .route(
146 "/drafts/{id}/revisions/{rev_id}/restore",
147 post(routes::content::restore_from_revision),
148 )
149 .route(
150 "/drafts/{id}/activity",
151 get(routes::content::list_draft_activity),
152 )
153 .route(
154 "/drafts/{id}/provenance",
155 get(routes::content::get_draft_provenance),
156 )
157 .route("/drafts/{id}/tags", get(routes::content::list_draft_tags))
158 .route(
159 "/drafts/{id}/tags/{tag_id}",
160 post(routes::content::assign_draft_tag).delete(routes::content::unassign_draft_tag),
161 )
162 .route(
164 "/content/drafts",
165 get(routes::content::list_drafts).post(routes::content::create_draft),
166 )
167 .route(
168 "/content/drafts/{id}",
169 patch(routes::content::edit_draft).delete(routes::content::delete_draft),
170 )
171 .route(
172 "/content/drafts/{id}/schedule",
173 post(routes::content::schedule_draft),
174 )
175 .route(
176 "/content/drafts/{id}/publish",
177 post(routes::content::publish_draft),
178 )
179 .route(
180 "/content/drafts/{id}/provenance",
181 get(routes::content::get_draft_provenance),
182 )
183 .route("/ingest", post(routes::ingest::ingest))
185 .route("/sources/status", get(routes::sources::source_status))
187 .route(
188 "/sources/{id}/reindex",
189 post(routes::sources::reindex_source),
190 )
191 .route(
193 "/targets",
194 get(routes::targets::list_targets).post(routes::targets::add_target),
195 )
196 .route(
197 "/targets/{username}/timeline",
198 get(routes::targets::target_timeline),
199 )
200 .route(
201 "/targets/{username}/stats",
202 get(routes::targets::target_stats),
203 )
204 .route(
205 "/targets/{username}",
206 delete(routes::targets::remove_target),
207 )
208 .route("/strategy/current", get(routes::strategy::current))
210 .route("/strategy/history", get(routes::strategy::history))
211 .route("/strategy/refresh", post(routes::strategy::refresh))
212 .route("/strategy/inputs", get(routes::strategy::inputs))
213 .route("/costs/summary", get(routes::costs::summary))
215 .route("/costs/daily", get(routes::costs::daily))
216 .route("/costs/by-model", get(routes::costs::by_model))
217 .route("/costs/by-type", get(routes::costs::by_type))
218 .route("/costs/x-api/summary", get(routes::costs::x_api_summary))
220 .route("/costs/x-api/daily", get(routes::costs::x_api_daily))
221 .route(
222 "/costs/x-api/by-endpoint",
223 get(routes::costs::x_api_by_endpoint),
224 )
225 .route("/assist/tweet", post(routes::assist::assist_tweet))
227 .route("/assist/reply", post(routes::assist::assist_reply))
228 .route("/assist/thread", post(routes::assist::assist_thread))
229 .route("/assist/improve", post(routes::assist::assist_improve))
230 .route(
231 "/assist/highlights",
232 post(routes::assist::assist_highlights),
233 )
234 .route("/assist/hooks", post(routes::assist::hooks::assist_hooks))
235 .route(
236 "/assist/angles",
237 post(routes::assist::angles::assist_angles),
238 )
239 .route("/assist/topics", get(routes::assist::assist_topics))
240 .route(
241 "/assist/optimal-times",
242 get(routes::assist::assist_optimal_times),
243 )
244 .route("/assist/mode", get(routes::assist::get_mode))
245 .route("/vault/sources", get(routes::vault::vault_sources))
247 .route("/vault/notes", get(routes::vault::search_notes))
248 .route(
249 "/vault/notes/{id}/neighbors",
250 get(routes::vault::note_neighbors),
251 )
252 .route("/vault/notes/{id}", get(routes::vault::note_detail))
253 .route("/vault/search", get(routes::vault::search_fragments))
254 .route("/vault/resolve-refs", post(routes::vault::resolve_refs))
255 .route(
256 "/vault/send-selection",
257 post(routes::vault::selections::send_selection),
258 )
259 .route(
260 "/vault/selection/{session_id}",
261 get(routes::vault::selections::get_selection),
262 )
263 .route("/discovery/feed", get(routes::discovery::feed))
265 .route("/discovery/keywords", get(routes::discovery::keywords))
266 .route(
267 "/discovery/{tweet_id}/compose-reply",
268 post(routes::discovery::compose_reply),
269 )
270 .route(
271 "/discovery/{tweet_id}/queue-reply",
272 post(routes::discovery::queue_reply),
273 )
274 .route(
276 "/media/upload",
277 post(routes::media::upload).layer(DefaultBodyLimit::max(520 * 1024 * 1024)),
278 )
279 .route("/media/file", get(routes::media::serve_file))
280 .route(
282 "/settings/lan",
283 get(routes::lan::get_status).patch(routes::lan::toggle_lan),
284 )
285 .route(
286 "/settings/lan/reset-passphrase",
287 post(routes::lan::reset_passphrase),
288 )
289 .route("/settings/status", get(routes::settings::config_status))
291 .route("/settings/init", post(routes::settings::init_settings))
292 .route(
293 "/settings/validate",
294 post(routes::settings::validate_settings),
295 )
296 .route("/settings/defaults", get(routes::settings::get_defaults))
297 .route("/settings/test-llm", post(routes::settings::test_llm))
298 .route(
299 "/settings/factory-reset",
300 post(routes::settings::factory_reset),
301 )
302 .route(
303 "/settings/scraper-session",
304 get(routes::scraper_session::get_scraper_session)
305 .post(routes::scraper_session::import_scraper_session)
306 .delete(routes::scraper_session::delete_scraper_session),
307 )
308 .route(
309 "/settings",
310 get(routes::settings::get_settings).patch(routes::settings::patch_settings),
311 )
312 .route(
314 "/connectors/google-drive/link",
315 post(routes::connectors::link_google_drive),
316 )
317 .route(
318 "/connectors/google-drive/callback",
319 get(routes::connectors::callback_google_drive),
320 )
321 .route(
322 "/connectors/google-drive/status",
323 get(routes::connectors::status_google_drive),
324 )
325 .route(
326 "/connectors/google-drive/{id}",
327 delete(routes::connectors::disconnect_google_drive),
328 )
329 .route(
331 "/mcp/policy",
332 get(routes::mcp::get_policy).patch(routes::mcp::patch_policy),
333 )
334 .route("/mcp/policy/templates", get(routes::mcp::list_templates))
335 .route(
336 "/mcp/policy/templates/{name}",
337 post(routes::mcp::apply_template),
338 )
339 .route(
340 "/mcp/telemetry/summary",
341 get(routes::mcp::telemetry_summary),
342 )
343 .route(
344 "/mcp/telemetry/metrics",
345 get(routes::mcp::telemetry_metrics),
346 )
347 .route("/mcp/telemetry/errors", get(routes::mcp::telemetry_errors))
348 .route("/mcp/telemetry/recent", get(routes::mcp::telemetry_recent))
349 .route("/runtime/status", get(routes::runtime::status))
351 .route("/runtime/start", post(routes::runtime::start))
352 .route("/runtime/stop", post(routes::runtime::stop))
353 .route(
355 "/onboarding/x-auth/start",
356 post(routes::onboarding::start_onboarding_auth),
357 )
358 .route(
359 "/onboarding/x-auth/callback",
360 post(routes::onboarding::complete_onboarding_auth),
361 )
362 .route(
363 "/onboarding/x-auth/status",
364 get(routes::onboarding::onboarding_auth_status),
365 )
366 .route(
367 "/onboarding/analyze-profile",
368 post(routes::onboarding::analyze_profile),
369 )
370 .route(
372 "/accounts",
373 get(routes::accounts::list_accounts).post(routes::accounts::create_account),
374 )
375 .route(
376 "/accounts/{id}/roles",
377 get(routes::accounts::list_roles)
378 .post(routes::accounts::set_role)
379 .delete(routes::accounts::remove_role),
380 )
381 .route(
382 "/accounts/{id}/sync-profile",
383 post(routes::accounts::sync_profile),
384 )
385 .route(
387 "/accounts/{id}/x-auth/start",
388 post(routes::x_auth::start_link),
389 )
390 .route(
391 "/accounts/{id}/x-auth/callback",
392 post(routes::x_auth::complete_link),
393 )
394 .route(
395 "/accounts/{id}/x-auth/status",
396 get(routes::x_auth::link_status),
397 )
398 .route(
399 "/accounts/{id}/x-auth/tokens",
400 delete(routes::x_auth::unlink),
401 )
402 .route(
403 "/accounts/{id}",
404 get(routes::accounts::get_account)
405 .patch(routes::accounts::update_account)
406 .delete(routes::accounts::delete_account),
407 )
408 .route("/telemetry/events", post(routes::telemetry::ingest_events))
410 .route("/ws", get(ws::ws_handler))
412 .layer(middleware::from_fn_with_state(
414 state.clone(),
415 auth::auth_middleware,
416 ));
417
418 Router::new()
419 .nest("/api", api)
420 .fallback(dashboard::serve_dashboard)
421 .layer(CorsLayer::permissive())
422 .layer(TraceLayer::new_for_http())
423 .with_state(state)
424}