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("/analytics/heatmap", get(routes::analytics::heatmap))
58 .route(
59 "/analytics/content-breakdown",
60 get(routes::analytics::content_breakdown),
61 )
62 .route("/approval/export", get(routes::approval::export_items))
64 .route("/approval", get(routes::approval::list_items))
65 .route("/approval/stats", get(routes::approval::stats))
66 .route("/approval/approve-all", post(routes::approval::approve_all))
67 .route(
68 "/approval/bulk/approve",
69 post(routes::approval::bulk_approve),
70 )
71 .route("/approval/bulk/reject", post(routes::approval::bulk_reject))
72 .route(
73 "/approval/{id}/history",
74 get(routes::approval::get_edit_history),
75 )
76 .route("/approval/{id}", patch(routes::approval::edit_item))
77 .route(
78 "/approval/{id}/approve",
79 post(routes::approval::approve_item),
80 )
81 .route("/approval/{id}/reject", post(routes::approval::reject_item))
82 .route("/activity/export", get(routes::activity::export_activity))
84 .route("/activity", get(routes::activity::list_activity))
85 .route(
86 "/activity/rate-limits",
87 get(routes::activity::rate_limit_usage),
88 )
89 .route("/replies", get(routes::replies::list_replies))
91 .route(
93 "/content/tweets",
94 get(routes::content::list_tweets).post(routes::content::compose_tweet),
95 )
96 .route(
97 "/content/threads",
98 get(routes::content::list_threads).post(routes::content::compose_thread),
99 )
100 .route("/content/calendar", get(routes::content::calendar))
101 .route("/content/schedule", get(routes::content::schedule))
102 .route("/content/compose", post(routes::content::compose))
103 .route(
104 "/content/scheduled/{id}",
105 patch(routes::content::edit_scheduled).delete(routes::content::cancel_scheduled),
106 )
107 .route(
109 "/tags",
110 get(routes::content::list_account_tags).post(routes::content::create_account_tag),
111 )
112 .route(
114 "/drafts",
115 get(routes::content::list_studio_drafts).post(routes::content::create_studio_draft),
116 )
117 .route(
118 "/drafts/{id}",
119 get(routes::content::get_studio_draft)
120 .patch(routes::content::autosave_draft)
121 .delete(routes::content::delete_draft),
122 )
123 .route(
124 "/drafts/{id}/meta",
125 patch(routes::content::patch_draft_meta),
126 )
127 .route(
128 "/drafts/{id}/schedule",
129 post(routes::content::schedule_studio_draft),
130 )
131 .route(
132 "/drafts/{id}/reschedule",
133 patch(routes::content::reschedule_studio_draft),
134 )
135 .route(
136 "/drafts/{id}/unschedule",
137 post(routes::content::unschedule_studio_draft),
138 )
139 .route(
140 "/drafts/{id}/archive",
141 post(routes::content::archive_studio_draft),
142 )
143 .route(
144 "/drafts/{id}/restore",
145 post(routes::content::restore_studio_draft),
146 )
147 .route(
148 "/drafts/{id}/duplicate",
149 post(routes::content::duplicate_studio_draft),
150 )
151 .route(
152 "/drafts/{id}/revisions",
153 get(routes::content::list_draft_revisions).post(routes::content::create_draft_revision),
154 )
155 .route(
156 "/drafts/{id}/revisions/{rev_id}/restore",
157 post(routes::content::restore_from_revision),
158 )
159 .route(
160 "/drafts/{id}/activity",
161 get(routes::content::list_draft_activity),
162 )
163 .route(
164 "/drafts/{id}/provenance",
165 get(routes::content::get_draft_provenance),
166 )
167 .route("/drafts/{id}/tags", get(routes::content::list_draft_tags))
168 .route(
169 "/drafts/{id}/tags/{tag_id}",
170 post(routes::content::assign_draft_tag).delete(routes::content::unassign_draft_tag),
171 )
172 .route(
174 "/content/drafts",
175 get(routes::content::list_drafts).post(routes::content::create_draft),
176 )
177 .route(
178 "/content/drafts/{id}",
179 patch(routes::content::edit_draft).delete(routes::content::delete_draft),
180 )
181 .route(
182 "/content/drafts/{id}/schedule",
183 post(routes::content::schedule_draft),
184 )
185 .route(
186 "/content/drafts/{id}/publish",
187 post(routes::content::publish_draft),
188 )
189 .route(
190 "/content/drafts/{id}/provenance",
191 get(routes::content::get_draft_provenance),
192 )
193 .route("/ingest", post(routes::ingest::ingest))
195 .route("/sources/status", get(routes::sources::source_status))
197 .route(
198 "/sources/{id}/reindex",
199 post(routes::sources::reindex_source),
200 )
201 .route(
203 "/targets",
204 get(routes::targets::list_targets).post(routes::targets::add_target),
205 )
206 .route(
207 "/targets/{username}/timeline",
208 get(routes::targets::target_timeline),
209 )
210 .route(
211 "/targets/{username}/stats",
212 get(routes::targets::target_stats),
213 )
214 .route(
215 "/targets/{username}",
216 delete(routes::targets::remove_target),
217 )
218 .route("/strategy/current", get(routes::strategy::current))
220 .route("/strategy/history", get(routes::strategy::history))
221 .route("/strategy/refresh", post(routes::strategy::refresh))
222 .route("/strategy/inputs", get(routes::strategy::inputs))
223 .route("/costs/summary", get(routes::costs::summary))
225 .route("/costs/daily", get(routes::costs::daily))
226 .route("/costs/by-model", get(routes::costs::by_model))
227 .route("/costs/by-type", get(routes::costs::by_type))
228 .route("/costs/x-api/summary", get(routes::costs::x_api_summary))
230 .route("/costs/x-api/daily", get(routes::costs::x_api_daily))
231 .route(
232 "/costs/x-api/by-endpoint",
233 get(routes::costs::x_api_by_endpoint),
234 )
235 .route("/assist/tweet", post(routes::assist::assist_tweet))
237 .route("/assist/reply", post(routes::assist::assist_reply))
238 .route("/assist/thread", post(routes::assist::assist_thread))
239 .route("/assist/improve", post(routes::assist::assist_improve))
240 .route(
241 "/assist/highlights",
242 post(routes::assist::assist_highlights),
243 )
244 .route("/assist/hooks", post(routes::assist::hooks::assist_hooks))
245 .route(
246 "/assist/angles",
247 post(routes::assist::angles::assist_angles),
248 )
249 .route("/assist/topics", get(routes::assist::assist_topics))
250 .route(
251 "/assist/optimal-times",
252 get(routes::assist::assist_optimal_times),
253 )
254 .route("/assist/mode", get(routes::assist::get_mode))
255 .route(
257 "/vault/evidence",
258 get(routes::vault::evidence::search_evidence),
259 )
260 .route(
261 "/vault/index-status",
262 get(routes::vault::index_status::get_index_status),
263 )
264 .route("/vault/sources", get(routes::vault::vault_sources))
265 .route("/vault/notes", get(routes::vault::search_notes))
266 .route(
267 "/vault/notes/{id}/neighbors",
268 get(routes::vault::note_neighbors),
269 )
270 .route("/vault/notes/{id}", get(routes::vault::note_detail))
271 .route("/vault/search", get(routes::vault::search_fragments))
272 .route("/vault/resolve-refs", post(routes::vault::resolve_refs))
273 .route(
274 "/vault/send-selection",
275 post(routes::vault::selections::send_selection),
276 )
277 .route(
278 "/vault/selection/{session_id}",
279 get(routes::vault::selections::get_selection),
280 )
281 .route("/discovery/feed", get(routes::discovery::feed))
283 .route("/discovery/keywords", get(routes::discovery::keywords))
284 .route(
285 "/discovery/{tweet_id}/compose-reply",
286 post(routes::discovery::compose_reply),
287 )
288 .route(
289 "/discovery/{tweet_id}/queue-reply",
290 post(routes::discovery::queue_reply),
291 )
292 .route(
294 "/media/upload",
295 post(routes::media::upload).layer(DefaultBodyLimit::max(520 * 1024 * 1024)),
296 )
297 .route("/media/file", get(routes::media::serve_file))
298 .route(
300 "/settings/lan",
301 get(routes::lan::get_status).patch(routes::lan::toggle_lan),
302 )
303 .route(
304 "/settings/lan/reset-passphrase",
305 post(routes::lan::reset_passphrase),
306 )
307 .route("/settings/status", get(routes::settings::config_status))
309 .route("/settings/init", post(routes::settings::init_settings))
310 .route(
311 "/settings/validate",
312 post(routes::settings::validate_settings),
313 )
314 .route("/settings/defaults", get(routes::settings::get_defaults))
315 .route("/settings/test-llm", post(routes::settings::test_llm))
316 .route(
317 "/settings/factory-reset",
318 post(routes::settings::factory_reset),
319 )
320 .route(
321 "/settings/scraper-session",
322 get(routes::scraper_session::get_scraper_session)
323 .post(routes::scraper_session::import_scraper_session)
324 .delete(routes::scraper_session::delete_scraper_session),
325 )
326 .route(
327 "/settings",
328 get(routes::settings::get_settings).patch(routes::settings::patch_settings),
329 )
330 .route(
332 "/connectors/google-drive/link",
333 post(routes::connectors::link_google_drive),
334 )
335 .route(
336 "/connectors/google-drive/callback",
337 get(routes::connectors::callback_google_drive),
338 )
339 .route(
340 "/connectors/google-drive/status",
341 get(routes::connectors::status_google_drive),
342 )
343 .route(
344 "/connectors/google-drive/{id}",
345 delete(routes::connectors::disconnect_google_drive),
346 )
347 .route(
349 "/mcp/policy",
350 get(routes::mcp::get_policy).patch(routes::mcp::patch_policy),
351 )
352 .route("/mcp/policy/templates", get(routes::mcp::list_templates))
353 .route(
354 "/mcp/policy/templates/{name}",
355 post(routes::mcp::apply_template),
356 )
357 .route(
358 "/mcp/telemetry/summary",
359 get(routes::mcp::telemetry_summary),
360 )
361 .route(
362 "/mcp/telemetry/metrics",
363 get(routes::mcp::telemetry_metrics),
364 )
365 .route("/mcp/telemetry/errors", get(routes::mcp::telemetry_errors))
366 .route("/mcp/telemetry/recent", get(routes::mcp::telemetry_recent))
367 .route("/runtime/status", get(routes::runtime::status))
369 .route("/runtime/start", post(routes::runtime::start))
370 .route("/runtime/stop", post(routes::runtime::stop))
371 .route(
373 "/onboarding/x-auth/start",
374 post(routes::onboarding::start_onboarding_auth),
375 )
376 .route(
377 "/onboarding/x-auth/callback",
378 post(routes::onboarding::complete_onboarding_auth),
379 )
380 .route(
381 "/onboarding/x-auth/status",
382 get(routes::onboarding::onboarding_auth_status),
383 )
384 .route(
385 "/onboarding/analyze-profile",
386 post(routes::onboarding::analyze_profile),
387 )
388 .route(
390 "/accounts",
391 get(routes::accounts::list_accounts).post(routes::accounts::create_account),
392 )
393 .route(
394 "/accounts/{id}/roles",
395 get(routes::accounts::list_roles)
396 .post(routes::accounts::set_role)
397 .delete(routes::accounts::remove_role),
398 )
399 .route(
400 "/accounts/{id}/sync-profile",
401 post(routes::accounts::sync_profile),
402 )
403 .route(
405 "/accounts/{id}/x-auth/start",
406 post(routes::x_auth::start_link),
407 )
408 .route(
409 "/accounts/{id}/x-auth/callback",
410 post(routes::x_auth::complete_link),
411 )
412 .route(
413 "/accounts/{id}/x-auth/status",
414 get(routes::x_auth::link_status),
415 )
416 .route(
417 "/accounts/{id}/x-auth/tokens",
418 delete(routes::x_auth::unlink),
419 )
420 .route(
421 "/accounts/{id}",
422 get(routes::accounts::get_account)
423 .patch(routes::accounts::update_account)
424 .delete(routes::accounts::delete_account),
425 )
426 .route("/telemetry/events", post(routes::telemetry::ingest_events))
428 .route("/ws", get(ws::ws_handler))
430 .layer(middleware::from_fn_with_state(
432 state.clone(),
433 auth::auth_middleware,
434 ));
435
436 Router::new()
437 .nest("/api", api)
438 .fallback(dashboard::serve_dashboard)
439 .layer(CorsLayer::permissive())
440 .layer(TraceLayer::new_for_http())
441 .with_state(state)
442}