From 8e460ec6dd467ec2a4c0037e0e38e842caf8ef60 Mon Sep 17 00:00:00 2001 From: Mika Bomm Date: Thu, 3 Apr 2025 10:40:59 +0200 Subject: [PATCH] feat: add validation to project creation and update endpoints --- .env.example | 5 +++- crates/backend/Cargo.toml | 1 + crates/backend/src/controller/project.rs | 25 +++++++++----------- crates/backend/src/db.rs | 2 +- crates/backend/src/db/project.rs | 29 +++++++++++++++++------- crates/backend/src/error.rs | 11 ++++++++- 6 files changed, 48 insertions(+), 25 deletions(-) diff --git a/.env.example b/.env.example index f5dd62b..5ebf9ac 100644 --- a/.env.example +++ b/.env.example @@ -13,4 +13,7 @@ REDIS_PORT= SECRET_KEY= # LDAP section -LDAP_ADMIN_PASSWORD= \ No newline at end of file +LDAP_ADMIN_PASSWORD= + +# Rust log level +RUST_LOG=info \ No newline at end of file diff --git a/crates/backend/Cargo.toml b/crates/backend/Cargo.toml index 8247638..fbeff56 100644 --- a/crates/backend/Cargo.toml +++ b/crates/backend/Cargo.toml @@ -20,6 +20,7 @@ env_logger = "0.11" log = "0.4" serde = { version = "1", features = ["derive"] } +validator = { version = "0.20.0", features = ["derive"] } sea-orm = { version = "1.1", features = [ "sqlx-postgres", "runtime-tokio-rustls", diff --git a/crates/backend/src/controller/project.rs b/crates/backend/src/controller/project.rs index 35e017b..53aa642 100644 --- a/crates/backend/src/controller/project.rs +++ b/crates/backend/src/controller/project.rs @@ -1,18 +1,13 @@ +use std::path; + use actix_web::{Result, delete, get, post, put, web}; use sea_orm::prelude::Uuid; -use serde::Deserialize; - -use crate::dto::project::UpdateProject; +use validator::Validate; use crate::db::Database; +use crate::db::project::CreateProject; use crate::error::ApiError; -// Maybe move this here into the corresponding DTO file -#[derive(Deserialize)] -struct CreateProject { - name: String, -} - pub fn setup(cfg: &mut actix_web::web::ServiceConfig) { cfg.service(get_project) .service(get_projects) @@ -45,20 +40,22 @@ async fn get_project( #[post("")] async fn create_project( db: web::Data, - create_project_struct: web::Json, + create_project: web::Json, ) -> Result, ApiError> { - let result = db.create_project(&create_project_struct.name).await?; + create_project.validate()?; + let result = db.create_project(create_project.into_inner()).await?; Ok(web::Json(result)) } -#[put("")] +#[put("/{id}")] async fn update_project( db: web::Data, - update_project_struct: web::Json, + path: web::Path, + update_project: web::Json, ) -> Result, ApiError> { let updated_project = db - .update_project(update_project_struct.into_inner()) + .update_project(&path, update_project.into_inner()) .await?; Ok(web::Json(updated_project)) diff --git a/crates/backend/src/db.rs b/crates/backend/src/db.rs index a9a326a..2cc47f5 100644 --- a/crates/backend/src/db.rs +++ b/crates/backend/src/db.rs @@ -1,7 +1,7 @@ use sea_orm::{ConnectOptions, DatabaseConnection}; mod group; -mod project; +pub mod project; mod user; #[derive(Clone)] diff --git a/crates/backend/src/db/project.rs b/crates/backend/src/db/project.rs index aa355f4..dff12bd 100644 --- a/crates/backend/src/db/project.rs +++ b/crates/backend/src/db/project.rs @@ -2,12 +2,18 @@ use super::Database; use crate::error::ApiError; use log::debug; -use crate::dto::project::UpdateProject; - use entity::project; use sea_orm::ActiveValue::{NotSet, Set, Unchanged}; use sea_orm::prelude::Uuid; use sea_orm::{ActiveModelTrait, DeleteResult, EntityTrait}; +use serde::Deserialize; +use validator::Validate; + +#[derive(Deserialize, Validate)] +pub struct CreateProject { + #[validate(length(min = 3))] + name: String, +} impl Database { pub async fn get_projects(&self) -> Result, ApiError> { @@ -30,23 +36,30 @@ impl Database { Ok(project) } - pub async fn create_project(&self, name: &str) -> Result { - debug!("Creating project with name: {}", name); + pub async fn create_project( + &self, + create_project: CreateProject, + ) -> Result { + debug!("Creating project with name: {}", create_project.name); let project = project::ActiveModel { id: NotSet, - name: Set(name.to_owned()), + name: Set(create_project.name), }; let project = project.insert(&self.conn).await?; Ok(project) } - pub async fn update_project(&self, project: UpdateProject) -> Result { - debug!("Updating project with id: {}", &project.id); + pub async fn update_project( + &self, + id: &Uuid, + project: CreateProject, + ) -> Result { + debug!("Updating project with id: {}", &id); let active_model = project::ActiveModel { - id: Unchanged(project.id), + id: Unchanged(*id), name: Set(project.name), }; diff --git a/crates/backend/src/error.rs b/crates/backend/src/error.rs index b54da1b..dfb4c67 100644 --- a/crates/backend/src/error.rs +++ b/crates/backend/src/error.rs @@ -1,12 +1,18 @@ -use actix_web::{HttpResponse, ResponseError, http::StatusCode}; +use actix_web::{HttpResponse, ResponseError, cookie::time::error, http::StatusCode}; use thiserror::Error; #[derive(Error, Debug)] pub enum ApiError { #[error("Database Error: {0}")] Database(#[from] sea_orm::DbErr), + #[error("Unauthorized")] + Unauthorized, #[error("Not Found")] NotFound, + #[error("Bad Request: {0}")] + BadRequest(String), + #[error("Validation Error: {0}")] + ValidationError(#[from] validator::ValidationErrors), } impl ResponseError for ApiError { @@ -14,6 +20,9 @@ impl ResponseError for ApiError { match self { ApiError::Database(..) => StatusCode::INTERNAL_SERVER_ERROR, ApiError::NotFound => StatusCode::NOT_FOUND, + ApiError::Unauthorized => StatusCode::UNAUTHORIZED, + ApiError::BadRequest(..) => StatusCode::BAD_REQUEST, + ApiError::ValidationError(..) => StatusCode::BAD_REQUEST, } }