mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-16 19:59:42 +02:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013qMexTfVRaegbPAgsagCAJ
127 lines
4.1 KiB
Rust
127 lines
4.1 KiB
Rust
//! Assemblage des routes HTTP.
|
|
|
|
mod analytics;
|
|
mod auth;
|
|
mod core;
|
|
mod external;
|
|
mod facebook;
|
|
mod legal;
|
|
mod reels;
|
|
mod tiktok;
|
|
|
|
use axum::extract::{DefaultBodyLimit, Request, State};
|
|
use axum::http::{HeaderMap, StatusCode};
|
|
use axum::middleware;
|
|
use axum::response::{IntoResponse, Response};
|
|
use axum::Router;
|
|
use serde_json::json;
|
|
use tower::ServiceExt;
|
|
use tower_http::services::{ServeDir, ServeFile};
|
|
|
|
use crate::extract::MAX_JSON_BODY;
|
|
use crate::http;
|
|
use crate::state::AppState;
|
|
|
|
pub fn router(state: AppState) -> Router {
|
|
let api = Router::new()
|
|
.route("/healthz", axum::routing::get(healthz))
|
|
.merge(auth::router())
|
|
.merge(core::router())
|
|
.nest("/api/analytics", analytics::router())
|
|
.nest("/api/facebook", facebook::router())
|
|
.nest("/api/tiktok", tiktok::router())
|
|
.nest("/api/v1", external::router())
|
|
.merge(reels::router())
|
|
.merge(legal::router());
|
|
|
|
let config = &state.config;
|
|
for dir in [
|
|
&config.uploads_dir,
|
|
&config.audio_upload_dir,
|
|
&config.temp_upload_dir,
|
|
] {
|
|
let _ = std::fs::create_dir_all(dir);
|
|
}
|
|
for sub in ["media", "logos", "stories"] {
|
|
let _ = std::fs::create_dir_all(config.uploads_dir.join(sub));
|
|
}
|
|
|
|
// Fichiers téléversés (médias, logos, stories, musique, fichiers temporaires des rendus)
|
|
let uploads = Router::new()
|
|
.nest_service("/uploads/audio", ServeDir::new(&config.audio_upload_dir))
|
|
.nest_service("/uploads/temp", ServeDir::new(&config.temp_upload_dir))
|
|
.nest_service("/uploads", ServeDir::new(&config.uploads_dir));
|
|
|
|
api.merge(uploads)
|
|
.fallback(fallback)
|
|
.layer(DefaultBodyLimit::max(MAX_JSON_BODY))
|
|
.layer(middleware::from_fn_with_state(
|
|
state.clone(),
|
|
http::rate_limit,
|
|
))
|
|
.layer(middleware::from_fn(http::log_requests))
|
|
.layer(middleware::from_fn(http::security_headers))
|
|
.with_state(state)
|
|
}
|
|
|
|
/// Sonde de santé (Docker) : le serveur répond et la base est joignable.
|
|
async fn healthz(State(state): State<AppState>) -> Response {
|
|
match sqlx::query_scalar::<_, i32>("SELECT 1")
|
|
.fetch_one(&state.db.pool)
|
|
.await
|
|
{
|
|
Ok(_) => (StatusCode::OK, "ok").into_response(),
|
|
Err(_) => (StatusCode::SERVICE_UNAVAILABLE, "database unavailable").into_response(),
|
|
}
|
|
}
|
|
|
|
/// Route inconnue : JSON pour `/api`, sinon fichiers publics puis l'application React.
|
|
async fn fallback(State(state): State<AppState>, headers: HeaderMap, request: Request) -> Response {
|
|
let path = request.uri().path().to_string();
|
|
if path == "/api" || path.starts_with("/api/") {
|
|
return if crate::auth::current_user(&state, &headers).await.is_none() {
|
|
(
|
|
StatusCode::UNAUTHORIZED,
|
|
axum::Json(json!({ "error": "Non authentifié" })),
|
|
)
|
|
.into_response()
|
|
} else {
|
|
(
|
|
StatusCode::NOT_FOUND,
|
|
axum::Json(json!({ "error": "Route introuvable" })),
|
|
)
|
|
.into_response()
|
|
};
|
|
}
|
|
|
|
// Vérifications de domaine (TikTok, Google, Meta…), sans les fichiers cachés
|
|
let is_hidden = path.split('/').any(|part| part.starts_with('.'));
|
|
if !is_hidden && path != "/" {
|
|
let candidate = state
|
|
.config
|
|
.public_root_dir
|
|
.join(path.trim_start_matches('/'));
|
|
if candidate.is_file() {
|
|
return serve(ServeFile::new(candidate), request).await;
|
|
}
|
|
}
|
|
|
|
let index = state.config.static_dir.join("index.html");
|
|
let spa = ServeDir::new(&state.config.static_dir)
|
|
.append_index_html_on_directories(true)
|
|
.fallback(ServeFile::new(index));
|
|
serve(spa, request).await
|
|
}
|
|
|
|
async fn serve<S>(service: S, request: Request) -> Response
|
|
where
|
|
S: tower::Service<Request, Error = std::convert::Infallible> + Send,
|
|
S::Response: IntoResponse,
|
|
S::Future: Send,
|
|
{
|
|
match service.oneshot(request).await {
|
|
Ok(response) => response.into_response(),
|
|
Err(never) => match never {},
|
|
}
|
|
}
|