init
This commit is contained in:
12
Cargo.lock
generated
12
Cargo.lock
generated
@@ -97,6 +97,17 @@ version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atoi"
|
||||
version = "2.0.0"
|
||||
@@ -1131,6 +1142,7 @@ name = "ox_speak_server"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"async-trait",
|
||||
"axum",
|
||||
"chrono",
|
||||
"dotenvy",
|
||||
|
||||
@@ -32,3 +32,4 @@ hyper = "1.7"
|
||||
sqlx = { version = "0.8", features = ["runtime-tokio", "tls-rustls", "sqlite", "postgres", "mysql", "uuid", "chrono", "migrate"] }
|
||||
uuid = { version = "1.18", features = ["v4", "v7", "serde"] }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
async-trait = "0.1.89"
|
||||
@@ -2,11 +2,19 @@ use std::io;
|
||||
use std::sync::Arc;
|
||||
use std::net::SocketAddr;
|
||||
use crate::app::http::router::configure_routes;
|
||||
use crate::db::context::DbContext;
|
||||
use crate::network::http::server::HttpServer;
|
||||
use crate::network::udp::server::UdpServer;
|
||||
use crate::utils::config::Config;
|
||||
use crate::utils::logger::ContextLogger;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Context {
|
||||
udp_server: Arc<UdpServer>,
|
||||
http_server: Arc<HttpServer>,
|
||||
db: Arc<DbContext>
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
config: Config,
|
||||
context: Arc<Context>,
|
||||
@@ -33,9 +41,17 @@ impl App {
|
||||
}
|
||||
};
|
||||
|
||||
let db = match DbContext::new("sqlite:ox_speak_server.db").await {
|
||||
Ok(db) => db,
|
||||
Err(e) => {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, format!("Failed to create database context: {}", e)))
|
||||
}
|
||||
};
|
||||
|
||||
let context = Context {
|
||||
udp_server,
|
||||
http_server,
|
||||
udp_server: Arc::new(udp_server),
|
||||
http_server: Arc::new(http_server),
|
||||
db: Arc::new(db)
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
@@ -47,15 +63,38 @@ impl App {
|
||||
|
||||
pub async fn run(self) -> io::Result<()> {
|
||||
self.logger.info("Starting application");
|
||||
self.context.udp_server.run().await?;
|
||||
|
||||
self.context.http_server.run().await?;
|
||||
let udp_server = self.context.udp_server.clone();
|
||||
let http_server = self.context.http_server.clone();
|
||||
let db = self.context.db.clone();
|
||||
|
||||
let udp_handle = tokio::spawn(async move {
|
||||
udp_server.run().await
|
||||
});
|
||||
|
||||
let router = configure_routes(db);
|
||||
let http_handle = tokio::spawn(async move {
|
||||
http_server.run(router).await
|
||||
});
|
||||
|
||||
tokio::select! {
|
||||
result = udp_handle => {
|
||||
match result {
|
||||
Ok(Ok(())) => println!("UDP server terminated"),
|
||||
Ok(Err(e)) => return Err(e),
|
||||
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
|
||||
}
|
||||
}
|
||||
result = http_handle => {
|
||||
match result {
|
||||
Ok(Ok(())) => println!("HTTP server terminated"),
|
||||
Ok(Err(e)) => return Err(e),
|
||||
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct Context {
|
||||
udp_server: UdpServer,
|
||||
http_server: HttpServer
|
||||
}
|
||||
42
src/app/http/api/channel.rs
Normal file
42
src/app/http/api/channel.rs
Normal file
@@ -0,0 +1,42 @@
|
||||
use std::sync::Arc;
|
||||
use axum::{Extension, Json, Router};
|
||||
use axum::body::Body;
|
||||
use axum::extract::{Path, State};
|
||||
use axum::http::Request;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::routing::{get, post};
|
||||
use hyper::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
use crate::app::http::request_context::RequestContext;
|
||||
use crate::app::http::router::AppState;
|
||||
use crate::db::{Message, Server};
|
||||
|
||||
pub fn routes() -> Router<Arc<AppState>> {
|
||||
Router::new()
|
||||
.route("/:server_id/", get(channel_list))
|
||||
.route("/create/", post(channel_create))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateServerRequest {
|
||||
name: String,
|
||||
}
|
||||
|
||||
async fn channel_list(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Extension(ctx): Extension<RequestContext>,
|
||||
Path(server_id): Path<Uuid>
|
||||
) -> Result<Json<Vec<Server>>, StatusCode> {
|
||||
Ok(Json(vec![]))
|
||||
}
|
||||
|
||||
async fn channel_create(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Extension(ctx): Extension<RequestContext>,
|
||||
Path(server_id): Path<Uuid>,
|
||||
Json(payload): Json<CreateServerRequest>,
|
||||
) -> Result<Json<Server>, StatusCode> {
|
||||
let server = Server::new("test".to_string(), None);
|
||||
Ok(Json(server))
|
||||
}
|
||||
@@ -1,7 +1,10 @@
|
||||
use std::sync::Arc;
|
||||
use axum::http::StatusCode;
|
||||
use axum::{Json, Router};
|
||||
use axum::extract::State;
|
||||
use axum::routing::get;
|
||||
use serde_json::json;
|
||||
use crate::app::http::router::AppState;
|
||||
|
||||
pub fn routes() -> Router {
|
||||
Router::new()
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
use axum::Router;
|
||||
use std::sync::Arc;
|
||||
use axum::extract::{Path, State};
|
||||
use axum::{Json, Router};
|
||||
use axum::routing::get;
|
||||
use axum::http::StatusCode;
|
||||
use crate::app::http::router::AppState;
|
||||
use crate::db::Message;
|
||||
|
||||
pub fn routes() -> Router {
|
||||
pub fn routes() -> Router<Arc<AppState>> {
|
||||
Router::new()
|
||||
.nest("/message", Router::new())
|
||||
.route("/message/:channel_id/", get(list_messages))
|
||||
}
|
||||
|
||||
async fn list_messages(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(channel_id): Path<i64>
|
||||
) -> Result<Json<Vec<Message>>, StatusCode> {
|
||||
// todo : Récupérer les messages du channel
|
||||
// vérifier les permissions
|
||||
|
||||
Ok(Json(vec![]))
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod core;
|
||||
pub mod message;
|
||||
pub mod server;
|
||||
pub mod message;
|
||||
pub mod channel;
|
||||
@@ -0,0 +1,36 @@
|
||||
use std::sync::Arc;
|
||||
use axum::{Extension, Json, Router};
|
||||
use axum::extract::State;
|
||||
use axum::response::IntoResponse;
|
||||
use axum::routing::get;
|
||||
use hyper::StatusCode;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::app::http::request_context::RequestContext;
|
||||
use crate::app::http::router::AppState;
|
||||
use crate::db::{Message, Server};
|
||||
|
||||
pub fn routes() -> Router<Arc<AppState>> {
|
||||
Router::new()
|
||||
.route("/", get(list_servers))
|
||||
.route("/create", get(create_server))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct CreateServerRequest {
|
||||
name: String,
|
||||
}
|
||||
|
||||
async fn list_servers(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Extension(ctx): Extension<RequestContext>,
|
||||
) -> Result<Json<Vec<Server>>, StatusCode> {
|
||||
Ok(Json(vec![]))
|
||||
}
|
||||
|
||||
async fn create_server(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Json(payload): Json<CreateServerRequest>
|
||||
) -> Result<Json<Server>, StatusCode> {
|
||||
let server = Server::new("test".to_string(), None);
|
||||
Ok(Json(server))
|
||||
}
|
||||
97
src/app/http/middleware/auth_middleware.rs
Normal file
97
src/app/http/middleware/auth_middleware.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
use axum::{
|
||||
body::Body,
|
||||
extract::{Request, State},
|
||||
http::header,
|
||||
middleware::Next,
|
||||
response::Response,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use crate::app::http::request_context::RequestContext;
|
||||
use crate::db::context::DbContext;
|
||||
|
||||
/// Middleware d'authentification.
|
||||
pub async fn auth_middleware(
|
||||
State(db): State<Arc<DbContext>>,
|
||||
req: Request<Body>,
|
||||
next: Next,
|
||||
) -> Response {
|
||||
// Extraire les informations de base
|
||||
let method = req.method().clone();
|
||||
let uri = req.uri().clone();
|
||||
let path = uri.path().to_string();
|
||||
let query_string = uri.query().map(|q| q.to_string());
|
||||
let headers = req.headers().clone();
|
||||
|
||||
// Extraire l'IP du client
|
||||
let ip_address = headers
|
||||
.get("x-forwarded-for")
|
||||
.or_else(|| headers.get("x-real-ip"))
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.map(|s| s.split(',').next().unwrap_or(s).trim().to_string())
|
||||
.or_else(|| {
|
||||
// Fallback sur l'adresse de connexion si disponible
|
||||
req.extensions()
|
||||
.get::<std::net::SocketAddr>()
|
||||
.map(|addr| addr.ip().to_string())
|
||||
});
|
||||
|
||||
// Extraire User-Agent
|
||||
let user_agent = headers
|
||||
.get(header::USER_AGENT)
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
// Extraire Content-Type
|
||||
let content_type = headers
|
||||
.get(header::CONTENT_TYPE)
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
// Session simple (vous pouvez l'enrichir avec un vrai système de session)
|
||||
let session = Arc::new(HashMap::new());
|
||||
|
||||
// Extraire le token d'authentification
|
||||
let mut auth_token = None;
|
||||
let mut user = None;
|
||||
|
||||
if let Some(auth_header) = headers.get(header::AUTHORIZATION) {
|
||||
if let Ok(auth_str) = auth_header.to_str() {
|
||||
if let Some(token) = auth_str.strip_prefix("Bearer ") {
|
||||
auth_token = Some(token.to_string());
|
||||
|
||||
// TODO: Récupérer l'utilisateur depuis la DB
|
||||
// match db.repositories().user_repository.find_by_token(token).await {
|
||||
// Ok(found_user) => user = Some(found_user),
|
||||
// Err(e) => log::warn!("Token invalide: {}", e),
|
||||
// }
|
||||
|
||||
log::debug!("Token d'authentification détecté");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Construire le contexte complet
|
||||
let context = RequestContext {
|
||||
user,
|
||||
method,
|
||||
uri,
|
||||
path,
|
||||
query_string,
|
||||
headers,
|
||||
session,
|
||||
ip_address,
|
||||
user_agent,
|
||||
content_type,
|
||||
auth_token,
|
||||
};
|
||||
|
||||
log::debug!("Request: {}", context.debug_info());
|
||||
|
||||
// Injecter le contexte dans les extensions
|
||||
let (mut parts, body) = req.into_parts();
|
||||
parts.extensions.insert(context);
|
||||
let req = Request::from_parts(parts, body);
|
||||
|
||||
next.run(req).await
|
||||
}
|
||||
1
src/app/http/middleware/mod.rs
Normal file
1
src/app/http/middleware/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod auth_middleware;
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod router;
|
||||
|
||||
pub mod api;
|
||||
mod request_context;
|
||||
mod middleware;
|
||||
|
||||
40
src/app/http/request_context.rs
Normal file
40
src/app/http/request_context.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use axum::http::{header::HeaderMap, Method, Uri};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use crate::db::models::User;
|
||||
|
||||
/// Contexte de requête (style Django)
|
||||
#[derive(Clone)]
|
||||
pub struct RequestContext {
|
||||
pub user: Option<User>,
|
||||
pub method: Method,
|
||||
pub uri: Uri,
|
||||
pub path: String,
|
||||
pub query_string: Option<String>,
|
||||
pub headers: HeaderMap,
|
||||
pub session: Arc<HashMap<String, String>>,
|
||||
pub ip_address: Option<String>,
|
||||
pub user_agent: Option<String>,
|
||||
pub content_type: Option<String>,
|
||||
pub auth_token: Option<String>,
|
||||
}
|
||||
|
||||
impl RequestContext {
|
||||
pub fn is_authenticated(&self) -> bool {
|
||||
self.user.is_some()
|
||||
}
|
||||
|
||||
pub fn get_header(&self, name: &str) -> Option<&str> {
|
||||
self.headers.get(name)?.to_str().ok()
|
||||
}
|
||||
|
||||
pub fn debug_info(&self) -> String {
|
||||
format!(
|
||||
"{} {} - User: {} - IP: {}",
|
||||
self.method,
|
||||
self.path,
|
||||
self.user.as_ref().map(|u| u.username.as_str()).unwrap_or("Anonymous"),
|
||||
self.ip_address.as_deref().unwrap_or("unknown"),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,29 @@
|
||||
use axum::Router;
|
||||
use std::sync::Arc;
|
||||
use axum::{Router, middleware};
|
||||
use crate::app::http::api;
|
||||
use crate::app::http::api::core;
|
||||
use crate::app::http::api::{channel, core, message, server};
|
||||
use crate::app::http::middleware::auth_middleware;
|
||||
use crate::app::http::middleware::auth_middleware::auth_middleware;
|
||||
use crate::db::context::DbContext;
|
||||
|
||||
pub fn configure_routes() -> Router {
|
||||
let api_router = Router::new();
|
||||
#[derive(Clone)]
|
||||
pub struct AppState {
|
||||
db: Arc<DbContext>
|
||||
}
|
||||
|
||||
pub fn configure_routes(db_context: Arc<DbContext>) -> Router {
|
||||
let app_state = Arc::new(AppState {
|
||||
db: db_context.clone()
|
||||
});
|
||||
|
||||
let api_router = Router::new()
|
||||
.nest("/server/", server::routes())
|
||||
.nest("/channel/", channel::routes())
|
||||
.nest("/message/", message::routes())
|
||||
.with_state(app_state);
|
||||
|
||||
Router::new()
|
||||
.merge(core::routes())
|
||||
.nest("/api", api_router)
|
||||
|
||||
.layer(middleware::from_fn_with_state(db_context.clone(), auth_middleware))
|
||||
}
|
||||
|
||||
0
src/app/http/ws/ws.rs
Normal file
0
src/app/http/ws/ws.rs
Normal file
@@ -15,6 +15,7 @@ struct Repositories {
|
||||
channel_user: ChannelUserRepository
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DbContext {
|
||||
pool: Arc<SqlitePool>,
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
pub mod models;
|
||||
pub mod repositories;
|
||||
mod context;
|
||||
pub mod context;
|
||||
|
||||
pub use models::*;
|
||||
pub use repositories::*;
|
||||
@@ -4,6 +4,7 @@ use axum::{Router};
|
||||
use tokio::net::TcpListener;
|
||||
use crate::utils::logger::ContextLogger;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HttpServer {
|
||||
bind_addr: SocketAddr,
|
||||
logger: ContextLogger,
|
||||
@@ -20,7 +21,7 @@ impl HttpServer {
|
||||
Ok(Self { bind_addr, logger })
|
||||
}
|
||||
|
||||
pub async fn run(self, router: Router) -> io::Result<()> {
|
||||
pub async fn run(&self, router: Router) -> io::Result<()> {
|
||||
self.logger.info(&format!("Starting HTTP server on {}", self.bind_addr));
|
||||
|
||||
let listener = TcpListener::bind(self.bind_addr).await?;
|
||||
|
||||
@@ -14,9 +14,9 @@ use tokio::task;
|
||||
use crate::utils::shared_store::{SharedArcMap, SharedArcVec};
|
||||
use crate::utils::toolbox::number_of_cpus;
|
||||
use crate::utils::logger::ContextLogger;
|
||||
use crate::{log_info, log_warn, log_error, log_debug};
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UdpServer {
|
||||
// table de routage channel -> socket
|
||||
table_router: SharedArcMap<String, SharedArcVec<SocketAddr>>,
|
||||
@@ -52,7 +52,12 @@ impl UdpServer {
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn run(self) -> io::Result<()> {
|
||||
|
||||
pub fn router(&self) -> SharedArcMap<String, SharedArcVec<SocketAddr>> {
|
||||
self.table_router.clone()
|
||||
}
|
||||
|
||||
pub async fn run(&self) -> io::Result<()> {
|
||||
// S'assure qu'on est sur une runtime (utile si appelé hors #[tokio::main])
|
||||
let _ = Handle::try_current().map_err(|e| {
|
||||
self.logger.error(&format!("Runtime Tokio not available: {}", e));
|
||||
@@ -78,7 +83,7 @@ impl UdpServer {
|
||||
// Impl Linux/macOS (SO_REUSEPORT)
|
||||
// -------------------------
|
||||
#[cfg(unix)]
|
||||
async fn run_unix(self) -> io::Result<()> {
|
||||
async fn run_unix(&self) -> io::Result<()> {
|
||||
use socket2::{Domain, Protocol, Socket, Type};
|
||||
|
||||
self.logger.info(&format!("Starting UDP server on {} with {} workers - Unix mode", self.bind_addr, self.workers));
|
||||
@@ -121,7 +126,7 @@ impl UdpServer {
|
||||
// Impl Windows (1 socket partagé, N tâches concurrentes)
|
||||
// -------------------------
|
||||
#[cfg(windows)]
|
||||
async fn run_windows(self) -> io::Result<()> {
|
||||
async fn run_windows(&self) -> io::Result<()> {
|
||||
self.logger.info(&format!("Starting UDP server on {} with {} workers - Win mode", self.bind_addr, self.workers));
|
||||
|
||||
let udp = UdpSocket::bind(self.bind_addr).await?;
|
||||
|
||||
Reference in New Issue
Block a user