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