This commit is contained in:
2025-10-10 22:26:03 +02:00
parent 5fd4a41e64
commit 30f3fe015b
6 changed files with 739 additions and 100 deletions
+93
View File
@@ -29,6 +29,11 @@ pub struct EnsureFolderPathRequest {
pub segments: Vec<String>,
}
#[derive(Deserialize)]
pub struct UpdateFolderRequest {
pub parent_id: Option<Uuid>,
}
#[derive(Serialize)]
pub struct FolderResponse {
pub folder: FolderInfo,
@@ -361,6 +366,78 @@ pub async fn delete_folder(
Ok(StatusCode::NO_CONTENT)
}
pub async fn update_folder_parent(
State(state): State<AppState>,
Path(folder_id): Path<Uuid>,
Json(payload): Json<UpdateFolderRequest>,
) -> AppResult<StatusCode> {
if payload.parent_id == Some(folder_id) {
return Err(AppError::bad_request("folder cannot be its own parent"));
}
let mut conn = state.db()?;
conn.transaction::<(), AppError, _>(|conn| {
let folder: Folder = folders::table.find(folder_id).first(conn)?;
let current_parent = folder.parent_id;
let next_parent = payload.parent_id;
if current_parent == next_parent {
return Ok(());
}
if let Some(parent_id) = next_parent {
let _parent: Folder = folders::table.find(parent_id).first(conn)?;
}
if let Some(parent_id) = next_parent {
let descendant_ids = gather_descendant_folder_ids(conn, folder_id)?;
if descendant_ids.contains(&parent_id) {
return Err(AppError::bad_request(
"cannot move folder into itself or a descendant",
));
}
}
let conflict = if let Some(parent_id) = next_parent {
folders::table
.filter(folders::parent_id.eq(Some(parent_id)))
.filter(folders::name.eq(&folder.name))
.filter(folders::id.ne(folder_id))
.first::<Folder>(conn)
.optional()?
} else {
folders::table
.filter(folders::parent_id.is_null())
.filter(folders::name.eq(&folder.name))
.filter(folders::id.ne(folder_id))
.first::<Folder>(conn)
.optional()?
};
if conflict.is_some() {
return Err(AppError::bad_request(
"a folder with the same name already exists in the target",
));
}
let new_path = build_path_cache(conn, next_parent, &folder.name)?;
diesel::update(folders::table.find(folder_id))
.set((
folders::parent_id.eq(next_parent),
folders::path_cache.eq(new_path),
))
.execute(conn)?;
refresh_descendant_paths(conn, folder_id)?;
Ok(())
})?;
Ok(StatusCode::NO_CONTENT)
}
fn build_path_cache(
conn: &mut PgConnection,
parent_id: Option<Uuid>,
@@ -406,3 +483,19 @@ fn gather_descendant_folder_ids(conn: &mut PgConnection, folder_id: Uuid) -> App
Ok(ids)
}
fn refresh_descendant_paths(conn: &mut PgConnection, parent_id: Uuid) -> AppResult<()> {
let children: Vec<Folder> = folders::table
.filter(folders::parent_id.eq(Some(parent_id)))
.load(conn)?;
for child in children {
let path_cache = build_path_cache(conn, Some(parent_id), &child.name)?;
diesel::update(folders::table.find(child.id))
.set(folders::path_cache.eq(path_cache))
.execute(conn)?;
refresh_descendant_paths(conn, child.id)?;
}
Ok(())
}
+4 -1
View File
@@ -47,7 +47,10 @@ pub fn create_router(state: AppState) -> Router<()> {
let folders_routes = Router::new()
.route("/", post(folders::create_folder))
.route("/path", post(folders::ensure_folder_path))
.route("/:id", delete(folders::delete_folder))
.route(
"/:id",
delete(folders::delete_folder).patch(folders::update_folder_parent),
)
.route("/:id/contents", get(folders::list_folder_contents))
.route("/:id/documents", get(folders::search_documents));