openapi cleanup

This commit is contained in:
2025-10-31 00:39:22 +01:00
parent 5ca7dc9678
commit 5b5916ff92
9 changed files with 204 additions and 620 deletions
+34 -33
View File
@@ -6,7 +6,7 @@ use axum::{
};
use diesel::{dsl::exists, prelude::*, PgConnection};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use utoipa::{IntoParams, ToSchema};
use uuid::Uuid;
use crate::models::{Document, DocumentType, Folder, NewFolder};
@@ -22,38 +22,40 @@ use crate::documents::{
asset::load_primary_assets, correspondents::load_correspondents_for_documents,
tags::load_tags_for_documents,
};
use crate::utils::{
json::{classify_nullable, NullableValue},
time::to_iso,
};
use crate::utils::time::to_iso;
#[derive(Deserialize)]
#[derive(Deserialize, ToSchema)]
pub struct CreateFolderRequest {
pub name: String,
#[schema(nullable)]
pub parent_id: Option<Uuid>,
}
#[derive(Deserialize)]
#[derive(Deserialize, ToSchema)]
pub struct EnsureFolderPathRequest {
#[schema(nullable)]
pub parent_id: Option<Uuid>,
pub segments: Vec<String>,
}
#[derive(Serialize)]
#[derive(Serialize, ToSchema)]
pub struct FolderResponse {
pub folder: FolderInfo,
}
#[derive(Serialize)]
#[derive(Serialize, ToSchema)]
pub struct FolderContentsResponse {
#[schema(nullable)]
pub folder: Option<FolderInfo>,
pub subfolders: Vec<FolderInfo>,
pub documents: Vec<DocumentResponse>,
}
#[derive(Deserialize)]
#[derive(Deserialize, IntoParams, ToSchema)]
#[into_params(parameter_in = Query)]
pub struct FolderContentsQuery {
#[serde(default = "default_include_documents")]
#[schema(default = true)]
pub include_documents: bool,
}
@@ -61,15 +63,27 @@ const fn default_include_documents() -> bool {
true
}
#[derive(Serialize)]
#[derive(Serialize, ToSchema)]
pub struct FolderInfo {
pub id: Uuid,
pub name: String,
#[schema(nullable)]
pub parent_id: Option<Uuid>,
pub created_at: String,
pub updated_at: String,
}
#[derive(Default, Deserialize, ToSchema)]
#[serde(default)]
pub struct UpdateFolderRequest {
#[serde(default)]
#[schema(nullable, value_type = Option<Uuid>)]
pub parent_id: Option<Option<Uuid>>,
#[serde(default)]
#[schema(nullable)]
pub name: Option<Option<String>>,
}
pub async fn get_folder(
Path(folder_id): Path<Uuid>,
TenantScopedConn {
@@ -435,15 +449,8 @@ pub async fn update_folder(
tenant_id,
..
}: TenantScopedConn,
Json(body): Json<Value>,
Json(payload): Json<UpdateFolderRequest>,
) -> AppResult<StatusCode> {
if !body.is_object() {
return Err(AppError::bad_request("request body must be a JSON object"));
}
let parent_class = classify_nullable(body.get("parent_id")).map_err(AppError::bad_request)?;
let name_class = classify_nullable(body.get("name")).map_err(AppError::bad_request)?;
conn.transaction::<(), AppError, _>(|conn| {
let folder: Folder = folders::table
.find(folder_id)
@@ -452,21 +459,15 @@ pub async fn update_folder(
let mut next_parent = folder.parent_id;
let mut parent_changed = false;
match parent_class {
NullableValue::Omitted => {}
NullableValue::Null => {
match payload.parent_id {
None => {}
Some(None) => {
if folder.parent_id.is_some() {
parent_changed = true;
}
next_parent = None;
}
NullableValue::String(value) => {
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(AppError::bad_request("parent_id must not be empty"));
}
let parent_id = Uuid::parse_str(trimmed)
.map_err(|_| AppError::bad_request("parent_id must be a valid UUID or null"))?;
Some(Some(parent_id)) => {
if parent_id == folder_id {
return Err(AppError::bad_request("folder cannot be its own parent"));
}
@@ -492,12 +493,12 @@ pub async fn update_folder(
let mut new_name = folder.name.clone();
let mut name_changed = false;
match name_class {
NullableValue::Omitted => {}
NullableValue::Null => {
match payload.name {
None => {}
Some(None) => {
return Err(AppError::bad_request("name cannot be null"));
}
NullableValue::String(value) => {
Some(Some(value)) => {
let trimmed = value.trim();
if trimmed.is_empty() {
return Err(AppError::bad_request("name must not be empty"));