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("/approval/export", get(routes::approval::export_items))
49 .route("/approval", get(routes::approval::list_items))
50 .route("/approval/stats", get(routes::approval::stats))
51 .route("/approval/approve-all", post(routes::approval::approve_all))
52 .route(
53 "/approval/{id}/history",
54 get(routes::approval::get_edit_history),
55 )
56 .route("/approval/{id}", patch(routes::approval::edit_item))
57 .route(
58 "/approval/{id}/approve",
59 post(routes::approval::approve_item),
60 )
61 .route("/approval/{id}/reject", post(routes::approval::reject_item))
62 .route("/activity/export", get(routes::activity::export_activity))
64 .route("/activity", get(routes::activity::list_activity))
65 .route(
66 "/activity/rate-limits",
67 get(routes::activity::rate_limit_usage),
68 )
69 .route("/replies", get(routes::replies::list_replies))
71 .route(
73 "/content/tweets",
74 get(routes::content::list_tweets).post(routes::content::compose_tweet),
75 )
76 .route(
77 "/content/threads",
78 get(routes::content::list_threads).post(routes::content::compose_thread),
79 )
80 .route("/content/calendar", get(routes::content::calendar))
81 .route("/content/schedule", get(routes::content::schedule))
82 .route("/content/compose", post(routes::content::compose))
83 .route(
84 "/content/scheduled/{id}",
85 patch(routes::content::edit_scheduled).delete(routes::content::cancel_scheduled),
86 )
87 .route(
89 "/tags",
90 get(routes::content::list_account_tags).post(routes::content::create_account_tag),
91 )
92 .route(
94 "/drafts",
95 get(routes::content::list_studio_drafts).post(routes::content::create_studio_draft),
96 )
97 .route(
98 "/drafts/{id}",
99 get(routes::content::get_studio_draft)
100 .patch(routes::content::autosave_draft)
101 .delete(routes::content::delete_draft),
102 )
103 .route(
104 "/drafts/{id}/meta",
105 patch(routes::content::patch_draft_meta),
106 )
107 .route(
108 "/drafts/{id}/schedule",
109 post(routes::content::schedule_studio_draft),
110 )
111 .route(
112 "/drafts/{id}/unschedule",
113 post(routes::content::unschedule_studio_draft),
114 )
115 .route(
116 "/drafts/{id}/archive",
117 post(routes::content::archive_studio_draft),
118 )
119 .route(
120 "/drafts/{id}/restore",
121 post(routes::content::restore_studio_draft),
122 )
123 .route(
124 "/drafts/{id}/duplicate",
125 post(routes::content::duplicate_studio_draft),
126 )
127 .route(
128 "/drafts/{id}/revisions",
129 get(routes::content::list_draft_revisions).post(routes::content::create_draft_revision),
130 )
131 .route(
132 "/drafts/{id}/revisions/{rev_id}/restore",
133 post(routes::content::restore_from_revision),
134 )
135 .route(
136 "/drafts/{id}/activity",
137 get(routes::content::list_draft_activity),
138 )
139 .route("/drafts/{id}/tags", get(routes::content::list_draft_tags))
140 .route(
141 "/drafts/{id}/tags/{tag_id}",
142 post(routes::content::assign_draft_tag).delete(routes::content::unassign_draft_tag),
143 )
144 .route(
146 "/content/drafts",
147 get(routes::content::list_drafts).post(routes::content::create_draft),
148 )
149 .route(
150 "/content/drafts/{id}",
151 patch(routes::content::edit_draft).delete(routes::content::delete_draft),
152 )
153 .route(
154 "/content/drafts/{id}/schedule",
155 post(routes::content::schedule_draft),
156 )
157 .route(
158 "/content/drafts/{id}/publish",
159 post(routes::content::publish_draft),
160 )
161 .route("/ingest", post(routes::ingest::ingest))
163 .route("/sources/status", get(routes::sources::source_status))
165 .route(
166 "/sources/{id}/reindex",
167 post(routes::sources::reindex_source),
168 )
169 .route(
171 "/targets",
172 get(routes::targets::list_targets).post(routes::targets::add_target),
173 )
174 .route(
175 "/targets/{username}/timeline",
176 get(routes::targets::target_timeline),
177 )
178 .route(
179 "/targets/{username}/stats",
180 get(routes::targets::target_stats),
181 )
182 .route(
183 "/targets/{username}",
184 delete(routes::targets::remove_target),
185 )
186 .route("/strategy/current", get(routes::strategy::current))
188 .route("/strategy/history", get(routes::strategy::history))
189 .route("/strategy/refresh", post(routes::strategy::refresh))
190 .route("/strategy/inputs", get(routes::strategy::inputs))
191 .route("/costs/summary", get(routes::costs::summary))
193 .route("/costs/daily", get(routes::costs::daily))
194 .route("/costs/by-model", get(routes::costs::by_model))
195 .route("/costs/by-type", get(routes::costs::by_type))
196 .route("/costs/x-api/summary", get(routes::costs::x_api_summary))
198 .route("/costs/x-api/daily", get(routes::costs::x_api_daily))
199 .route(
200 "/costs/x-api/by-endpoint",
201 get(routes::costs::x_api_by_endpoint),
202 )
203 .route("/assist/tweet", post(routes::assist::assist_tweet))
205 .route("/assist/reply", post(routes::assist::assist_reply))
206 .route("/assist/thread", post(routes::assist::assist_thread))
207 .route("/assist/improve", post(routes::assist::assist_improve))
208 .route("/assist/topics", get(routes::assist::assist_topics))
209 .route(
210 "/assist/optimal-times",
211 get(routes::assist::assist_optimal_times),
212 )
213 .route("/assist/mode", get(routes::assist::get_mode))
214 .route("/vault/sources", get(routes::vault::vault_sources))
216 .route("/vault/notes", get(routes::vault::search_notes))
217 .route("/vault/notes/{id}", get(routes::vault::note_detail))
218 .route("/vault/search", get(routes::vault::search_fragments))
219 .route("/vault/resolve-refs", post(routes::vault::resolve_refs))
220 .route("/discovery/feed", get(routes::discovery::feed))
222 .route("/discovery/keywords", get(routes::discovery::keywords))
223 .route(
224 "/discovery/{tweet_id}/compose-reply",
225 post(routes::discovery::compose_reply),
226 )
227 .route(
228 "/discovery/{tweet_id}/queue-reply",
229 post(routes::discovery::queue_reply),
230 )
231 .route(
233 "/media/upload",
234 post(routes::media::upload).layer(DefaultBodyLimit::max(520 * 1024 * 1024)),
235 )
236 .route("/media/file", get(routes::media::serve_file))
237 .route(
239 "/settings/lan",
240 get(routes::lan::get_status).patch(routes::lan::toggle_lan),
241 )
242 .route(
243 "/settings/lan/reset-passphrase",
244 post(routes::lan::reset_passphrase),
245 )
246 .route("/settings/status", get(routes::settings::config_status))
248 .route("/settings/init", post(routes::settings::init_settings))
249 .route(
250 "/settings/validate",
251 post(routes::settings::validate_settings),
252 )
253 .route("/settings/defaults", get(routes::settings::get_defaults))
254 .route("/settings/test-llm", post(routes::settings::test_llm))
255 .route(
256 "/settings/factory-reset",
257 post(routes::settings::factory_reset),
258 )
259 .route(
260 "/settings/scraper-session",
261 get(routes::scraper_session::get_scraper_session)
262 .post(routes::scraper_session::import_scraper_session)
263 .delete(routes::scraper_session::delete_scraper_session),
264 )
265 .route(
266 "/settings",
267 get(routes::settings::get_settings).patch(routes::settings::patch_settings),
268 )
269 .route(
271 "/connectors/google-drive/link",
272 post(routes::connectors::link_google_drive),
273 )
274 .route(
275 "/connectors/google-drive/callback",
276 get(routes::connectors::callback_google_drive),
277 )
278 .route(
279 "/connectors/google-drive/status",
280 get(routes::connectors::status_google_drive),
281 )
282 .route(
283 "/connectors/google-drive/{id}",
284 delete(routes::connectors::disconnect_google_drive),
285 )
286 .route(
288 "/mcp/policy",
289 get(routes::mcp::get_policy).patch(routes::mcp::patch_policy),
290 )
291 .route("/mcp/policy/templates", get(routes::mcp::list_templates))
292 .route(
293 "/mcp/policy/templates/{name}",
294 post(routes::mcp::apply_template),
295 )
296 .route(
297 "/mcp/telemetry/summary",
298 get(routes::mcp::telemetry_summary),
299 )
300 .route(
301 "/mcp/telemetry/metrics",
302 get(routes::mcp::telemetry_metrics),
303 )
304 .route("/mcp/telemetry/errors", get(routes::mcp::telemetry_errors))
305 .route("/mcp/telemetry/recent", get(routes::mcp::telemetry_recent))
306 .route("/runtime/status", get(routes::runtime::status))
308 .route("/runtime/start", post(routes::runtime::start))
309 .route("/runtime/stop", post(routes::runtime::stop))
310 .route(
312 "/onboarding/x-auth/start",
313 post(routes::onboarding::start_onboarding_auth),
314 )
315 .route(
316 "/onboarding/x-auth/callback",
317 post(routes::onboarding::complete_onboarding_auth),
318 )
319 .route(
320 "/onboarding/x-auth/status",
321 get(routes::onboarding::onboarding_auth_status),
322 )
323 .route(
324 "/onboarding/analyze-profile",
325 post(routes::onboarding::analyze_profile),
326 )
327 .route(
329 "/accounts",
330 get(routes::accounts::list_accounts).post(routes::accounts::create_account),
331 )
332 .route(
333 "/accounts/{id}/roles",
334 get(routes::accounts::list_roles)
335 .post(routes::accounts::set_role)
336 .delete(routes::accounts::remove_role),
337 )
338 .route(
339 "/accounts/{id}/sync-profile",
340 post(routes::accounts::sync_profile),
341 )
342 .route(
344 "/accounts/{id}/x-auth/start",
345 post(routes::x_auth::start_link),
346 )
347 .route(
348 "/accounts/{id}/x-auth/callback",
349 post(routes::x_auth::complete_link),
350 )
351 .route(
352 "/accounts/{id}/x-auth/status",
353 get(routes::x_auth::link_status),
354 )
355 .route(
356 "/accounts/{id}/x-auth/tokens",
357 delete(routes::x_auth::unlink),
358 )
359 .route(
360 "/accounts/{id}",
361 get(routes::accounts::get_account)
362 .patch(routes::accounts::update_account)
363 .delete(routes::accounts::delete_account),
364 )
365 .route("/ws", get(ws::ws_handler))
367 .layer(middleware::from_fn_with_state(
369 state.clone(),
370 auth::auth_middleware,
371 ));
372
373 Router::new()
374 .nest("/api", api)
375 .fallback(dashboard::serve_dashboard)
376 .layer(CorsLayer::permissive())
377 .layer(TraceLayer::new_for_http())
378 .with_state(state)
379}