Initial commit.

This commit is contained in:
Florian THIERRY
2026-07-13 21:57:58 +02:00
commit b5a95b0fa3
49 changed files with 11243 additions and 0 deletions
+2627
View File
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
[package]
name = "lumiere-exposition"
version = "1.0.0"
edition = "2024"
[lib]
name = "surveillant_systeme_exposition"
path = "src/lib.rs"
[dependencies]
lumiere-domain = { path = "../domain" }
lumiere-application = { path = "../application" }
actix-web = { version = "4.13.0", features = ["rustls-0_23"] }
actix-web-httpauth = "0.8.2"
chrono = "0.4.44"
jsonwebtoken = { version = "10.3.0", features = ["rust_crypto"] }
serde = { version = "1.0.228", features = ["derive"] }
uuid = { version = "1.23.0", features = ["v4", "serde"] }
+36
View File
@@ -0,0 +1,36 @@
extern crate core;
use crate::user::handler::{create_new_user, delete_by_id, get_all_users, initialise, login, update_user, update_user_password};
use crate::user::validator::validator;
use actix_web::web::ServiceConfig;
use actix_web::{get, web, HttpResponse, Responder};
use actix_web_httpauth::middleware::HttpAuthentication;
pub mod user;
#[get("/health")]
async fn health() -> impl Responder {
"Ok!"
}
pub fn init(cfg: &mut ServiceConfig) {
cfg.service(health)
.service(health)
.service(initialise)
.service(login)
.service(
web::scope("")
.wrap(HttpAuthentication::bearer(validator))
.service(create_new_user)
.service(update_user)
.service(update_user_password)
.service(get_all_users)
.service(delete_by_id)
);
cfg.default_service(web::to(not_found));
}
async fn not_found() -> impl Responder {
HttpResponse::NotFound().finish()
}
+141
View File
@@ -0,0 +1,141 @@
use actix_web::{delete, get, post, put, web, HttpMessage, HttpRequest, HttpResponse, Responder};
use std::io::ErrorKind;
use std::sync::Arc;
use uuid::Uuid;
use surveillant_systeme_application::user::service::UserService;
use surveillant_systeme_domain::user::model::Claims;
use crate::user::model::request::{CreateFirstUserRequestDto, CreateUserRequestDto, LoginRequestDto, UpdateUserPasswordRequestDto, UpdateUserRequestDto};
use crate::user::model::response::{LoginResponseDto, UserDto};
#[post("/api/login")]
pub async fn login(
user_service: web::Data<Arc<UserService>>,
login_request: web::Json<LoginRequestDto>,
) -> impl Responder {
let login_result = user_service.login(
&login_request.username.to_string(),
&login_request.password.to_string(),
);
match login_result {
Ok(login_response) => HttpResponse::Ok().json(LoginResponseDto::new(&login_response)),
Err(error) => match error.kind() {
ErrorKind::InvalidInput => HttpResponse::BadRequest().body(error.to_string()),
_ => HttpResponse::InternalServerError().finish(),
}
}
}
#[post("/api/users")]
pub async fn create_new_user(
user_service: web::Data<Arc<UserService>>,
create_user_request: web::Json<CreateUserRequestDto>,
) -> impl Responder {
let user_creation_result = user_service.create_user(
&create_user_request.username.to_string(),
&create_user_request.password.to_string(),
);
match user_creation_result {
Ok(_) => HttpResponse::NoContent().finish(),
Err(error) => match error.kind() {
ErrorKind::InvalidInput => HttpResponse::BadRequest().body(error.to_string()),
_ => HttpResponse::InternalServerError().finish(),
}
}
}
#[put("/api/users/{user_id}")]
pub async fn update_user(
user_service: web::Data<Arc<UserService>>,
user_id: web::Path<Uuid>,
update_user_request: web::Json<UpdateUserRequestDto>
) -> impl Responder {
let user_uuid = user_id.into_inner();
let user_update_result = user_service.update_user(
user_uuid,
update_user_request.username.clone()
);
match user_update_result {
Ok(_) => HttpResponse::NoContent().finish(),
Err(error) => match error.kind() {
ErrorKind::InvalidInput => HttpResponse::BadRequest().body(error.to_string()),
_ => HttpResponse::InternalServerError().body(error.to_string())
}
}
}
#[put("/api/users/{user_id}/password")]
pub async fn update_user_password(
user_service: web::Data<Arc<UserService>>,
user_id: web::Path<Uuid>,
update_user_password_request: web::Json<UpdateUserPasswordRequestDto>
) -> impl Responder {
let user_uuid = user_id.into_inner();
let user_password_update_result = user_service.update_user_password(
user_uuid,
update_user_password_request.password.clone()
);
match user_password_update_result {
Ok(_) => HttpResponse::NoContent().finish(),
Err(error) => match error.kind() {
ErrorKind::InvalidInput => HttpResponse::BadRequest().body(error.to_string()),
_ => HttpResponse::InternalServerError().body(error.to_string())
}
}
}
#[post("/api/initialise")]
pub async fn initialise(
user_service: web::Data<Arc<UserService>>,
request: web::Json<CreateFirstUserRequestDto>,
) -> impl Responder {
let initialisation_result = user_service.initialise(
&request.initialisation_code,
&request.username,
&request.password,
);
match initialisation_result {
Ok(_) => HttpResponse::NoContent().finish(),
Err(error) => HttpResponse::BadRequest().body(error.to_string()),
}
}
#[get("/api/users")]
pub async fn get_all_users(user_service: web::Data<Arc<UserService>>) -> impl Responder {
match user_service.get_all_users() {
Ok(users) => {
let user_dto_list = users.iter().map(UserDto::new).collect::<Vec<UserDto>>();
HttpResponse::Ok().json(user_dto_list)
}
Err(_) => HttpResponse::InternalServerError().finish(),
}
}
#[delete("/api/users/{user_id}")]
pub async fn delete_by_id(
user_service: web::Data<Arc<UserService>>,
user_id: web::Path<Uuid>,
req: HttpRequest,
) -> impl Responder {
let authenticated_user_id = get_authenticated_user_id(req);
match user_service.delete_by_id(&authenticated_user_id, &user_id) {
Ok(()) => HttpResponse::NoContent().finish(),
Err(error) => match error.kind() {
ErrorKind::InvalidInput => HttpResponse::BadRequest().body(error.to_string()),
_ => HttpResponse::InternalServerError().body(error.to_string())
}
}
}
fn get_authenticated_user_id(req: HttpRequest) -> Uuid {
req.extensions()
.get::<Claims>()
.map(|claims| &claims.sub)
.map(|authenticated_user_id_as_str| authenticated_user_id_as_str.as_str())
.map(Uuid::parse_str)
.unwrap()
.expect("Unable to retrieve the authenticated user id…")
}
+3
View File
@@ -0,0 +1,3 @@
pub mod handler;
mod model;
pub mod validator;
+2
View File
@@ -0,0 +1,2 @@
pub mod request;
pub mod response;
@@ -0,0 +1,31 @@
use serde::Deserialize;
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateFirstUserRequestDto {
pub initialisation_code: String,
pub username: String,
pub password: String,
}
#[derive(Deserialize)]
pub struct CreateUserRequestDto {
pub username: String,
pub password: String,
}
#[derive(Deserialize)]
pub struct UpdateUserRequestDto {
pub username: String
}
#[derive(Deserialize)]
pub struct UpdateUserPasswordRequestDto {
pub password: String
}
#[derive(Deserialize)]
pub struct LoginRequestDto {
pub username: String,
pub password: String,
}
@@ -0,0 +1,55 @@
use chrono::{DateTime, Utc};
use serde::Serialize;
use uuid::Uuid;
use surveillant_systeme_domain::user::model::{LoginResponse, RefreshToken, User};
#[derive(Serialize)]
pub struct UserDto {
pub id: Uuid,
pub username: String,
}
impl UserDto {
pub fn new(user: &User) -> UserDto {
Self {
id: user.id,
username: user.username.clone(),
}
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct LoginResponseDto {
pub token_type: String,
pub access_token: String,
pub refresh_token: RefreshTokenDto,
}
impl LoginResponseDto {
pub fn new(login_response: &LoginResponse) -> Self {
Self {
token_type: login_response.token_type.clone(),
access_token: login_response.access_token.clone(),
refresh_token: RefreshTokenDto::new(&login_response.refresh_token),
}
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RefreshTokenDto {
pub user_id: Uuid,
pub value: String,
pub expiration_date: DateTime<Utc>,
}
impl RefreshTokenDto {
pub fn new(refresh_token: &RefreshToken) -> Self {
Self {
user_id: refresh_token.user_id.clone(),
value: refresh_token.value.clone(),
expiration_date: refresh_token.expiration_date.clone(),
}
}
}
+34
View File
@@ -0,0 +1,34 @@
use std::env;
use actix_web::{dev, HttpMessage};
use actix_web_httpauth::extractors::bearer::BearerAuth;
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use surveillant_systeme_domain::user::model::Claims;
fn get_jwt_secret() -> String {
env::var("JWT_SECRET").expect("JWT_SECRET must be set.")
}
pub async fn validator(
request: dev::ServiceRequest,
credentials: BearerAuth
) -> Result<dev::ServiceRequest, (actix_web::Error, dev::ServiceRequest)> {
let jwt_secret = get_jwt_secret();
let token = credentials.token();
match decode::<Claims>(
token,
&DecodingKey::from_secret(jwt_secret.as_bytes()),
&Validation::new(Algorithm::HS256)
) {
Ok(decoded) => {
request.extensions_mut().insert(decoded.claims);
Ok(request)
}
Err(_) => {
Err((
actix_web::error::ErrorUnauthorized("The provided JWT is invalid or expired."),
request
))
}
}
}