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