Skip to main content

imessage_http/
server.rs

1/// HTTP server setup: builds the Axum router with all routes, middleware, and CORS.
2use std::net::SocketAddr;
3use std::time::Duration;
4
5use axum::Router;
6use axum::extract::DefaultBodyLimit;
7use axum::http::StatusCode;
8use axum::middleware as axum_mw;
9use axum::routing::{delete, get, post};
10use tower_http::cors::CorsLayer;
11use tower_http::timeout::TimeoutLayer;
12use tracing::info;
13
14use crate::middleware::auth::auth_middleware;
15use crate::middleware::pretty::pretty_json_middleware;
16use crate::routes::{
17    attachment, chat, facetime, general, handle, icloud, message, server, webhook,
18};
19use crate::state::AppState;
20
21/// Build the full Axum router.
22pub fn build_router(state: AppState) -> Router {
23    // Routes that require authentication
24    let protected = Router::new()
25        // General
26        .route("/api/v1/ping", get(general::ping))
27        // Server
28        .route("/api/v1/server/info", get(server::get_info))
29        .route("/api/v1/server/logs", get(server::get_logs))
30        .route("/api/v1/server/permissions", get(server::get_permissions))
31        .route(
32            "/api/v1/server/statistics/totals",
33            get(server::get_stat_totals),
34        )
35        .route(
36            "/api/v1/server/statistics/media",
37            get(server::get_stat_media),
38        )
39        .route(
40            "/api/v1/server/statistics/media/chat",
41            get(server::get_stat_media_by_chat),
42        )
43        // Webhook
44        .route("/api/v1/webhook", get(webhook::get_webhooks))
45        // Handle
46        .route("/api/v1/handle/count", get(handle::count))
47        .route("/api/v1/handle/query", post(handle::query))
48        .route("/api/v1/handle/{guid}", get(handle::find))
49        .route("/api/v1/handle/{guid}/focus", get(handle::get_focus_status))
50        .route(
51            "/api/v1/handle/availability/imessage",
52            get(handle::get_imessage_availability).post(handle::post_imessage_availability),
53        )
54        .route(
55            "/api/v1/handle/availability/facetime",
56            get(handle::get_facetime_availability).post(handle::post_facetime_availability),
57        )
58        // Chat — NOTE: share/contact/status MUST be before the {guid}/{messageGuid} catch-all
59        .route("/api/v1/chat/count", get(chat::count))
60        .route("/api/v1/chat/query", post(chat::query))
61        .route("/api/v1/chat/new", post(chat::create))
62        .route("/api/v1/chat/{guid}/message", get(chat::get_messages))
63        .route(
64            "/api/v1/chat/{guid}",
65            get(chat::find).put(chat::update).delete(chat::delete_chat),
66        )
67        .route("/api/v1/chat/{guid}/read", post(chat::mark_read))
68        .route("/api/v1/chat/{guid}/unread", post(chat::mark_unread))
69        .route("/api/v1/chat/{guid}/leave", post(chat::leave))
70        .route(
71            "/api/v1/chat/{guid}/typing",
72            post(chat::start_typing).delete(chat::stop_typing),
73        )
74        .route(
75            "/api/v1/chat/{guid}/participant/add",
76            post(chat::add_participant),
77        )
78        .route(
79            "/api/v1/chat/{guid}/participant/remove",
80            post(chat::remove_participant),
81        )
82        .route(
83            "/api/v1/chat/{guid}/participant",
84            post(chat::add_participant).delete(chat::remove_participant_delete),
85        )
86        .route(
87            "/api/v1/chat/{guid}/icon",
88            get(chat::get_icon)
89                .post(chat::set_icon)
90                .delete(chat::remove_icon),
91        )
92        .route(
93            "/api/v1/chat/{guid}/share/contact/status",
94            get(chat::share_contact_status),
95        )
96        .route(
97            "/api/v1/chat/{guid}/share/contact",
98            post(chat::share_contact),
99        )
100        .route(
101            "/api/v1/chat/{guid}/{messageGuid}",
102            delete(chat::delete_message),
103        )
104        // Message
105        .route("/api/v1/message/count", get(message::count))
106        .route("/api/v1/message/count/updated", get(message::count_updated))
107        .route("/api/v1/message/count/me", get(message::sent_count))
108        .route("/api/v1/message/query", post(message::query))
109        .route("/api/v1/message/text", post(message::send_text))
110        .route("/api/v1/message/attachment", post(message::send_attachment))
111        .route(
112            "/api/v1/message/attachment/chunk",
113            post(message::send_attachment_chunk),
114        )
115        .route("/api/v1/message/multipart", post(message::send_multipart))
116        .route("/api/v1/message/react", post(message::send_reaction))
117        .route("/api/v1/message/{guid}/edit", post(message::edit_message))
118        .route(
119            "/api/v1/message/{guid}/unsend",
120            post(message::unsend_message),
121        )
122        .route(
123            "/api/v1/message/{guid}/notify",
124            post(message::notify_message),
125        )
126        .route(
127            "/api/v1/message/{guid}/embedded-media",
128            get(message::get_embedded_media),
129        )
130        .route("/api/v1/message/{guid}", get(message::find))
131        // Attachment
132        .route("/api/v1/attachment/count", get(attachment::count))
133        .route("/api/v1/attachment/upload", post(attachment::upload))
134        .route(
135            "/api/v1/attachment/{guid}/download/force",
136            get(attachment::force_download),
137        )
138        .route(
139            "/api/v1/attachment/{guid}/download",
140            get(attachment::download),
141        )
142        .route(
143            "/api/v1/attachment/{guid}/live",
144            get(attachment::download_live),
145        )
146        .route(
147            "/api/v1/attachment/{guid}/blurhash",
148            get(attachment::blurhash),
149        )
150        .route("/api/v1/attachment/{guid}", get(attachment::find))
151        // iCloud
152        .route("/api/v1/icloud/account", get(icloud::get_account_info))
153        .route("/api/v1/icloud/account/alias", post(icloud::change_alias))
154        .route("/api/v1/icloud/contact", get(icloud::get_contact_card))
155        .route(
156            "/api/v1/icloud/findmy/devices",
157            get(icloud::get_findmy_devices),
158        )
159        .route(
160            "/api/v1/icloud/findmy/devices/refresh",
161            post(icloud::refresh_findmy_devices),
162        )
163        .route(
164            "/api/v1/icloud/findmy/friends",
165            get(icloud::get_findmy_friends),
166        )
167        .route(
168            "/api/v1/icloud/findmy/friends/refresh",
169            post(icloud::refresh_findmy_friends),
170        )
171        // FaceTime
172        .route("/api/v1/facetime/session", post(facetime::create_session))
173        .route(
174            "/api/v1/facetime/answer/{call_uuid}",
175            post(facetime::answer_call),
176        )
177        .route(
178            "/api/v1/facetime/leave/{call_uuid}",
179            post(facetime::leave_call),
180        )
181        // Auth middleware applies to all routes
182        .layer(axum_mw::from_fn_with_state(state.clone(), auth_middleware))
183        .with_state(state.clone());
184
185    // Compose the full app
186    Router::new()
187        .merge(protected)
188        // ?pretty JSON middleware (must be before CORS layer)
189        .layer(axum_mw::from_fn(pretty_json_middleware))
190        // CORS (permissive)
191        .layer(CorsLayer::permissive())
192        // Request timeout: 5 minutes
193        .layer(TimeoutLayer::with_status_code(
194            StatusCode::REQUEST_TIMEOUT,
195            Duration::from_secs(300),
196        ))
197        // Body size limit: 1GB
198        .layer(DefaultBodyLimit::max(1024 * 1024 * 1024))
199        // Fallback: plain text "Not Found" for unknown routes
200        .fallback(fallback_handler)
201}
202
203/// 404 handler: plain text "Not Found" (not JSON).
204async fn fallback_handler() -> (axum::http::StatusCode, &'static str) {
205    (axum::http::StatusCode::NOT_FOUND, "Not Found")
206}
207
208/// Start the HTTP server.
209pub async fn start_server(state: AppState) -> anyhow::Result<()> {
210    let port = state.config.socket_port;
211    let app = build_router(state);
212
213    let addr = SocketAddr::from(([127, 0, 0, 1], port));
214    let listener = tokio::net::TcpListener::bind(addr).await?;
215    info!("Successfully started HTTP server on {addr}");
216    axum::serve(listener, app).await?;
217
218    Ok(())
219}