feat: add validation to project creation and update endpoints
Some checks failed
ci/woodpecker/push/check_fmt Pipeline failed

This commit is contained in:
Mika Bomm 2025-04-03 10:40:59 +02:00
parent f20802682a
commit 8e460ec6dd
6 changed files with 48 additions and 25 deletions

View file

@ -14,3 +14,6 @@ SECRET_KEY=
# LDAP section # LDAP section
LDAP_ADMIN_PASSWORD= LDAP_ADMIN_PASSWORD=
# Rust log level
RUST_LOG=info

View file

@ -20,6 +20,7 @@ env_logger = "0.11"
log = "0.4" log = "0.4"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
validator = { version = "0.20.0", features = ["derive"] }
sea-orm = { version = "1.1", features = [ sea-orm = { version = "1.1", features = [
"sqlx-postgres", "sqlx-postgres",
"runtime-tokio-rustls", "runtime-tokio-rustls",

View file

@ -1,18 +1,13 @@
use std::path;
use actix_web::{Result, delete, get, post, put, web}; use actix_web::{Result, delete, get, post, put, web};
use sea_orm::prelude::Uuid; use sea_orm::prelude::Uuid;
use serde::Deserialize; use validator::Validate;
use crate::dto::project::UpdateProject;
use crate::db::Database; use crate::db::Database;
use crate::db::project::CreateProject;
use crate::error::ApiError; 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) { pub fn setup(cfg: &mut actix_web::web::ServiceConfig) {
cfg.service(get_project) cfg.service(get_project)
.service(get_projects) .service(get_projects)
@ -45,20 +40,22 @@ async fn get_project(
#[post("")] #[post("")]
async fn create_project( async fn create_project(
db: web::Data<Database>, db: web::Data<Database>,
create_project_struct: web::Json<CreateProject>, create_project: web::Json<CreateProject>,
) -> Result<web::Json<entity::project::Model>, ApiError> { ) -> Result<web::Json<entity::project::Model>, 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)) Ok(web::Json(result))
} }
#[put("")] #[put("/{id}")]
async fn update_project( async fn update_project(
db: web::Data<Database>, db: web::Data<Database>,
update_project_struct: web::Json<UpdateProject>, path: web::Path<Uuid>,
update_project: web::Json<CreateProject>,
) -> Result<web::Json<entity::project::Model>, ApiError> { ) -> Result<web::Json<entity::project::Model>, ApiError> {
let updated_project = db let updated_project = db
.update_project(update_project_struct.into_inner()) .update_project(&path, update_project.into_inner())
.await?; .await?;
Ok(web::Json(updated_project)) Ok(web::Json(updated_project))

View file

@ -1,7 +1,7 @@
use sea_orm::{ConnectOptions, DatabaseConnection}; use sea_orm::{ConnectOptions, DatabaseConnection};
mod group; mod group;
mod project; pub mod project;
mod user; mod user;
#[derive(Clone)] #[derive(Clone)]

View file

@ -2,12 +2,18 @@ use super::Database;
use crate::error::ApiError; use crate::error::ApiError;
use log::debug; use log::debug;
use crate::dto::project::UpdateProject;
use entity::project; use entity::project;
use sea_orm::ActiveValue::{NotSet, Set, Unchanged}; use sea_orm::ActiveValue::{NotSet, Set, Unchanged};
use sea_orm::prelude::Uuid; use sea_orm::prelude::Uuid;
use sea_orm::{ActiveModelTrait, DeleteResult, EntityTrait}; 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 { impl Database {
pub async fn get_projects(&self) -> Result<Vec<project::Model>, ApiError> { pub async fn get_projects(&self) -> Result<Vec<project::Model>, ApiError> {
@ -30,23 +36,30 @@ impl Database {
Ok(project) Ok(project)
} }
pub async fn create_project(&self, name: &str) -> Result<project::Model, ApiError> { pub async fn create_project(
debug!("Creating project with name: {}", name); &self,
create_project: CreateProject,
) -> Result<project::Model, ApiError> {
debug!("Creating project with name: {}", create_project.name);
let project = project::ActiveModel { let project = project::ActiveModel {
id: NotSet, id: NotSet,
name: Set(name.to_owned()), name: Set(create_project.name),
}; };
let project = project.insert(&self.conn).await?; let project = project.insert(&self.conn).await?;
Ok(project) Ok(project)
} }
pub async fn update_project(&self, project: UpdateProject) -> Result<project::Model, ApiError> { pub async fn update_project(
debug!("Updating project with id: {}", &project.id); &self,
id: &Uuid,
project: CreateProject,
) -> Result<project::Model, ApiError> {
debug!("Updating project with id: {}", &id);
let active_model = project::ActiveModel { let active_model = project::ActiveModel {
id: Unchanged(project.id), id: Unchanged(*id),
name: Set(project.name), name: Set(project.name),
}; };

View file

@ -1,12 +1,18 @@
use actix_web::{HttpResponse, ResponseError, http::StatusCode}; use actix_web::{HttpResponse, ResponseError, cookie::time::error, http::StatusCode};
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum ApiError { pub enum ApiError {
#[error("Database Error: {0}")] #[error("Database Error: {0}")]
Database(#[from] sea_orm::DbErr), Database(#[from] sea_orm::DbErr),
#[error("Unauthorized")]
Unauthorized,
#[error("Not Found")] #[error("Not Found")]
NotFound, NotFound,
#[error("Bad Request: {0}")]
BadRequest(String),
#[error("Validation Error: {0}")]
ValidationError(#[from] validator::ValidationErrors),
} }
impl ResponseError for ApiError { impl ResponseError for ApiError {
@ -14,6 +20,9 @@ impl ResponseError for ApiError {
match self { match self {
ApiError::Database(..) => StatusCode::INTERNAL_SERVER_ERROR, ApiError::Database(..) => StatusCode::INTERNAL_SERVER_ERROR,
ApiError::NotFound => StatusCode::NOT_FOUND, ApiError::NotFound => StatusCode::NOT_FOUND,
ApiError::Unauthorized => StatusCode::UNAUTHORIZED,
ApiError::BadRequest(..) => StatusCode::BAD_REQUEST,
ApiError::ValidationError(..) => StatusCode::BAD_REQUEST,
} }
} }