cleanup
This commit is contained in:
+100
-54
@@ -1,5 +1,3 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use axum::{
|
||||
extract::{Json, Path, Query, State},
|
||||
http::StatusCode,
|
||||
@@ -9,20 +7,16 @@ use serde::{Deserialize, Serialize};
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::models::{Document, DocumentType, Folder, NewFolder};
|
||||
use crate::schema::{document_types, documents, folders};
|
||||
use crate::models::{Document, Folder, NewFolder};
|
||||
use crate::schema::{documents, folders};
|
||||
use crate::state::AppState;
|
||||
use crate::{
|
||||
auth::TenantScopedConn,
|
||||
error::{AppError, AppResult},
|
||||
};
|
||||
|
||||
use super::documents::{to_document_response, DocumentResponse};
|
||||
use crate::documents::{
|
||||
asset::load_primary_assets, correspondents::load_correspondents_for_documents,
|
||||
tags::load_tags_for_documents,
|
||||
};
|
||||
use crate::utils::time::to_iso;
|
||||
use super::documents::{hydrate_documents, DocumentResponse};
|
||||
use crate::utils::{json::deserialize_patch_field, time::to_iso};
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct CreateFolderRequest {
|
||||
@@ -74,16 +68,47 @@ pub struct FolderInfo {
|
||||
}
|
||||
|
||||
#[derive(Default, Deserialize, ToSchema)]
|
||||
#[serde(default)]
|
||||
pub struct UpdateFolderRequest {
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "deserialize_patch_field")]
|
||||
#[schema(nullable, value_type = Option<Uuid>)]
|
||||
pub parent_id: Option<Option<Uuid>>,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "deserialize_patch_field")]
|
||||
#[schema(nullable)]
|
||||
pub name: Option<Option<String>>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn update_folder_request_deserializes_null_parent() {
|
||||
let req: UpdateFolderRequest =
|
||||
serde_json::from_value(json!({ "parent_id": null })).unwrap();
|
||||
assert!(matches!(req.parent_id, Some(None)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_folder_request_deserializes_absent_parent() {
|
||||
let req: UpdateFolderRequest = serde_json::from_value(json!({})).unwrap();
|
||||
assert!(req.parent_id.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_folder_request_deserializes_null_name() {
|
||||
let req: UpdateFolderRequest = serde_json::from_value(json!({ "name": null })).unwrap();
|
||||
assert!(matches!(req.name, Some(None)));
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/folders/{id}",
|
||||
params(("id" = Uuid, Path, description = "Folder ID")),
|
||||
responses((status = 200, description = "Folder detail", body = FolderResponse)),
|
||||
tag = "Folders"
|
||||
)]
|
||||
pub async fn get_folder(
|
||||
Path(folder_id): Path<Uuid>,
|
||||
TenantScopedConn {
|
||||
@@ -102,6 +127,13 @@ pub async fn get_folder(
|
||||
}))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/folders/path",
|
||||
request_body = EnsureFolderPathRequest,
|
||||
responses((status = 200, description = "Folder path ensured", body = FolderResponse)),
|
||||
tag = "Folders"
|
||||
)]
|
||||
pub async fn ensure_folder_path(
|
||||
TenantScopedConn {
|
||||
mut conn,
|
||||
@@ -188,6 +220,16 @@ pub async fn ensure_folder_path(
|
||||
}))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/folders",
|
||||
request_body = CreateFolderRequest,
|
||||
responses(
|
||||
(status = 201, description = "Folder created", body = FolderResponse),
|
||||
(status = 200, description = "Folder already existed", body = FolderResponse)
|
||||
),
|
||||
tag = "Folders"
|
||||
)]
|
||||
pub async fn create_folder(
|
||||
TenantScopedConn {
|
||||
mut conn,
|
||||
@@ -275,6 +317,13 @@ pub async fn create_folder(
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/folders/{id}/contents",
|
||||
params(("id" = Uuid, Path, description = "Folder ID"), FolderContentsQuery),
|
||||
responses((status = 200, description = "Folder contents", body = FolderContentsResponse)),
|
||||
tag = "Folders"
|
||||
)]
|
||||
pub async fn list_folder_contents(
|
||||
State(state): State<AppState>,
|
||||
Path(folder_identifier): Path<String>,
|
||||
@@ -336,47 +385,7 @@ pub async fn list_folder_contents(
|
||||
.load(&mut conn)?
|
||||
};
|
||||
|
||||
let type_ids: HashSet<Uuid> = docs.iter().filter_map(|doc| doc.document_type_id).collect();
|
||||
let doc_type_map: HashMap<Uuid, DocumentType> = if type_ids.is_empty() {
|
||||
HashMap::new()
|
||||
} else {
|
||||
let ids: Vec<Uuid> = type_ids.iter().copied().collect();
|
||||
document_types::table
|
||||
.filter(document_types::tenant_id.eq(tenant_id))
|
||||
.filter(document_types::id.eq_any(&ids))
|
||||
.load::<DocumentType>(&mut conn)?
|
||||
.into_iter()
|
||||
.map(|typ| (typ.id, typ))
|
||||
.collect()
|
||||
};
|
||||
|
||||
let doc_ids: Vec<Uuid> = docs.iter().map(|doc| doc.id).collect();
|
||||
let tags_map = load_tags_for_documents(&mut conn, &doc_ids)?;
|
||||
let mut correspondents_map = load_correspondents_for_documents(&mut conn, &doc_ids)?;
|
||||
drop(conn);
|
||||
|
||||
let primary_versions = load_primary_assets(&state, tenant_id, &docs)?;
|
||||
|
||||
let mut documents = Vec::with_capacity(doc_ids.len());
|
||||
for doc in docs {
|
||||
let tags = tags_map.get(&doc.id).cloned();
|
||||
let correspondents = correspondents_map.remove(&doc.id).unwrap_or_default();
|
||||
let current_version = primary_versions.get(&doc.id).cloned();
|
||||
let doc_type = doc
|
||||
.document_type_id
|
||||
.and_then(|id| doc_type_map.get(&id).cloned());
|
||||
documents.push(to_document_response(
|
||||
&state,
|
||||
user_id,
|
||||
doc,
|
||||
tags,
|
||||
correspondents,
|
||||
current_version,
|
||||
doc_type,
|
||||
)?);
|
||||
}
|
||||
|
||||
documents
|
||||
hydrate_documents(&state, &mut conn, tenant_id, user_id, docs)?
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
@@ -388,6 +397,13 @@ pub async fn list_folder_contents(
|
||||
}))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/folders/{id}",
|
||||
params(("id" = Uuid, Path, description = "Folder ID")),
|
||||
responses((status = 204, description = "Folder deleted")),
|
||||
tag = "Folders"
|
||||
)]
|
||||
pub async fn delete_folder(
|
||||
Path(folder_id): Path<Uuid>,
|
||||
TenantScopedConn {
|
||||
@@ -442,6 +458,14 @@ pub async fn delete_folder(
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
patch,
|
||||
path = "/api/folders/{id}",
|
||||
params(("id" = Uuid, Path, description = "Folder ID")),
|
||||
request_body = UpdateFolderRequest,
|
||||
responses((status = 204, description = "Folder updated")),
|
||||
tag = "Folders"
|
||||
)]
|
||||
pub async fn update_folder(
|
||||
Path(folder_id): Path<Uuid>,
|
||||
TenantScopedConn {
|
||||
@@ -586,3 +610,25 @@ pub(super) fn gather_descendant_folder_ids(
|
||||
|
||||
Ok(ids)
|
||||
}
|
||||
|
||||
#[derive(utoipa::OpenApi)]
|
||||
#[openapi(
|
||||
paths(
|
||||
crate::routes::folders::create_folder,
|
||||
crate::routes::folders::ensure_folder_path,
|
||||
crate::routes::folders::get_folder,
|
||||
crate::routes::folders::list_folder_contents,
|
||||
crate::routes::folders::delete_folder,
|
||||
crate::routes::folders::update_folder
|
||||
),
|
||||
components(schemas(
|
||||
crate::routes::folders::CreateFolderRequest,
|
||||
crate::routes::folders::EnsureFolderPathRequest,
|
||||
crate::routes::folders::FolderResponse,
|
||||
crate::routes::folders::FolderInfo,
|
||||
crate::routes::folders::FolderContentsQuery,
|
||||
crate::routes::folders::FolderContentsResponse,
|
||||
crate::routes::folders::UpdateFolderRequest
|
||||
))
|
||||
)]
|
||||
pub struct FoldersApiDoc;
|
||||
|
||||
Reference in New Issue
Block a user