Skip to main content

tuitbot_server/
lib.rs

1//! Tuitbot HTTP API server.
2//!
3//! Exposes `tuitbot-core`'s storage layer as a REST API with read + write
4//! endpoints, multi-strategy auth (bearer token + session cookie), and a
5//! WebSocket for real-time events.
6
7pub 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::middleware;
18use axum::routing::{delete, get, patch, post};
19use axum::Router;
20use tower_http::cors::CorsLayer;
21use tower_http::trace::TraceLayer;
22
23use crate::state::AppState;
24
25/// Build the complete axum router with all API routes and middleware.
26pub fn build_router(state: Arc<AppState>) -> Router {
27    let api = Router::new()
28        .route("/health", get(routes::health::health))
29        .route("/health/detailed", get(routes::health::health_detailed))
30        // Auth
31        .route("/auth/login", post(auth::routes::login))
32        .route("/auth/logout", post(auth::routes::logout))
33        .route("/auth/status", get(auth::routes::status))
34        // Analytics
35        .route("/analytics/summary", get(routes::analytics::summary))
36        .route("/analytics/followers", get(routes::analytics::followers))
37        .route(
38            "/analytics/performance",
39            get(routes::analytics::performance),
40        )
41        .route("/analytics/topics", get(routes::analytics::topics))
42        .route(
43            "/analytics/recent-performance",
44            get(routes::analytics::recent_performance),
45        )
46        // Approval
47        .route("/approval/export", get(routes::approval::export_items))
48        .route("/approval", get(routes::approval::list_items))
49        .route("/approval/stats", get(routes::approval::stats))
50        .route("/approval/approve-all", post(routes::approval::approve_all))
51        .route(
52            "/approval/{id}/history",
53            get(routes::approval::get_edit_history),
54        )
55        .route("/approval/{id}", patch(routes::approval::edit_item))
56        .route(
57            "/approval/{id}/approve",
58            post(routes::approval::approve_item),
59        )
60        .route("/approval/{id}/reject", post(routes::approval::reject_item))
61        // Activity
62        .route("/activity/export", get(routes::activity::export_activity))
63        .route("/activity", get(routes::activity::list_activity))
64        .route(
65            "/activity/rate-limits",
66            get(routes::activity::rate_limit_usage),
67        )
68        // Replies
69        .route("/replies", get(routes::replies::list_replies))
70        // Content
71        .route(
72            "/content/tweets",
73            get(routes::content::list_tweets).post(routes::content::compose_tweet),
74        )
75        .route(
76            "/content/threads",
77            get(routes::content::list_threads).post(routes::content::compose_thread),
78        )
79        .route("/content/calendar", get(routes::content::calendar))
80        .route("/content/schedule", get(routes::content::schedule))
81        .route("/content/compose", post(routes::content::compose))
82        .route(
83            "/content/scheduled/{id}",
84            patch(routes::content::edit_scheduled).delete(routes::content::cancel_scheduled),
85        )
86        // Drafts
87        .route(
88            "/content/drafts",
89            get(routes::content::list_drafts).post(routes::content::create_draft),
90        )
91        .route(
92            "/content/drafts/{id}",
93            patch(routes::content::edit_draft).delete(routes::content::delete_draft),
94        )
95        .route(
96            "/content/drafts/{id}/schedule",
97            post(routes::content::schedule_draft),
98        )
99        .route(
100            "/content/drafts/{id}/publish",
101            post(routes::content::publish_draft),
102        )
103        // Ingest
104        .route("/ingest", post(routes::ingest::ingest))
105        // Targets
106        .route(
107            "/targets",
108            get(routes::targets::list_targets).post(routes::targets::add_target),
109        )
110        .route(
111            "/targets/{username}/timeline",
112            get(routes::targets::target_timeline),
113        )
114        .route(
115            "/targets/{username}/stats",
116            get(routes::targets::target_stats),
117        )
118        .route(
119            "/targets/{username}",
120            delete(routes::targets::remove_target),
121        )
122        // Strategy
123        .route("/strategy/current", get(routes::strategy::current))
124        .route("/strategy/history", get(routes::strategy::history))
125        .route("/strategy/refresh", post(routes::strategy::refresh))
126        .route("/strategy/inputs", get(routes::strategy::inputs))
127        // Costs — LLM
128        .route("/costs/summary", get(routes::costs::summary))
129        .route("/costs/daily", get(routes::costs::daily))
130        .route("/costs/by-model", get(routes::costs::by_model))
131        .route("/costs/by-type", get(routes::costs::by_type))
132        // Costs — X API
133        .route("/costs/x-api/summary", get(routes::costs::x_api_summary))
134        .route("/costs/x-api/daily", get(routes::costs::x_api_daily))
135        .route(
136            "/costs/x-api/by-endpoint",
137            get(routes::costs::x_api_by_endpoint),
138        )
139        // AI Assist
140        .route("/assist/tweet", post(routes::assist::assist_tweet))
141        .route("/assist/reply", post(routes::assist::assist_reply))
142        .route("/assist/thread", post(routes::assist::assist_thread))
143        .route("/assist/improve", post(routes::assist::assist_improve))
144        .route("/assist/topics", get(routes::assist::assist_topics))
145        .route(
146            "/assist/optimal-times",
147            get(routes::assist::assist_optimal_times),
148        )
149        .route("/assist/mode", get(routes::assist::get_mode))
150        // Discovery feed
151        .route("/discovery/feed", get(routes::discovery::feed))
152        .route("/discovery/keywords", get(routes::discovery::keywords))
153        .route(
154            "/discovery/{tweet_id}/compose-reply",
155            post(routes::discovery::compose_reply),
156        )
157        .route(
158            "/discovery/{tweet_id}/queue-reply",
159            post(routes::discovery::queue_reply),
160        )
161        // Media
162        .route("/media/upload", post(routes::media::upload))
163        .route("/media/file", get(routes::media::serve_file))
164        // LAN settings
165        .route(
166            "/settings/lan",
167            get(routes::lan::get_status).patch(routes::lan::toggle_lan),
168        )
169        .route(
170            "/settings/lan/reset-passphrase",
171            post(routes::lan::reset_passphrase),
172        )
173        // Settings
174        .route("/settings/status", get(routes::settings::config_status))
175        .route("/settings/init", post(routes::settings::init_settings))
176        .route(
177            "/settings/validate",
178            post(routes::settings::validate_settings),
179        )
180        .route("/settings/defaults", get(routes::settings::get_defaults))
181        .route("/settings/test-llm", post(routes::settings::test_llm))
182        .route(
183            "/settings/factory-reset",
184            post(routes::settings::factory_reset),
185        )
186        .route(
187            "/settings",
188            get(routes::settings::get_settings).patch(routes::settings::patch_settings),
189        )
190        // Connectors
191        .route(
192            "/connectors/google-drive/link",
193            post(routes::connectors::link_google_drive),
194        )
195        .route(
196            "/connectors/google-drive/callback",
197            get(routes::connectors::callback_google_drive),
198        )
199        .route(
200            "/connectors/google-drive/status",
201            get(routes::connectors::status_google_drive),
202        )
203        .route(
204            "/connectors/google-drive/{id}",
205            delete(routes::connectors::disconnect_google_drive),
206        )
207        // MCP governance
208        .route(
209            "/mcp/policy",
210            get(routes::mcp::get_policy).patch(routes::mcp::patch_policy),
211        )
212        .route("/mcp/policy/templates", get(routes::mcp::list_templates))
213        .route(
214            "/mcp/policy/templates/{name}",
215            post(routes::mcp::apply_template),
216        )
217        .route(
218            "/mcp/telemetry/summary",
219            get(routes::mcp::telemetry_summary),
220        )
221        .route(
222            "/mcp/telemetry/metrics",
223            get(routes::mcp::telemetry_metrics),
224        )
225        .route("/mcp/telemetry/errors", get(routes::mcp::telemetry_errors))
226        .route("/mcp/telemetry/recent", get(routes::mcp::telemetry_recent))
227        // Runtime
228        .route("/runtime/status", get(routes::runtime::status))
229        .route("/runtime/start", post(routes::runtime::start))
230        .route("/runtime/stop", post(routes::runtime::stop))
231        // Accounts
232        .route(
233            "/accounts",
234            get(routes::accounts::list_accounts).post(routes::accounts::create_account),
235        )
236        .route(
237            "/accounts/{id}/roles",
238            get(routes::accounts::list_roles)
239                .post(routes::accounts::set_role)
240                .delete(routes::accounts::remove_role),
241        )
242        .route(
243            "/accounts/{id}",
244            get(routes::accounts::get_account)
245                .patch(routes::accounts::update_account)
246                .delete(routes::accounts::delete_account),
247        )
248        // WebSocket
249        .route("/ws", get(ws::ws_handler))
250        // Auth middleware — applied to all routes; exempt paths handled internally.
251        .layer(middleware::from_fn_with_state(
252            state.clone(),
253            auth::auth_middleware,
254        ));
255
256    Router::new()
257        .nest("/api", api)
258        .fallback(dashboard::serve_dashboard)
259        .layer(CorsLayer::permissive())
260        .layer(TraceLayer::new_for_http())
261        .with_state(state)
262}