folders
ci / docker (backend, backend/Dockerfile, backend) (push) Has been cancelled
ci / docker (frontend, frontend/Dockerfile, frontend) (push) Has been cancelled

This commit is contained in:
2025-10-27 00:20:53 +01:00
parent 157f03b655
commit c625c80958
10 changed files with 806 additions and 71 deletions
+39 -10
View File
@@ -1304,7 +1304,6 @@ pub async fn update_document(
}
pub async fn move_document(
State(state): State<AppState>,
Path(document_id): Path<Uuid>,
TenantScopedConn {
mut conn,
@@ -1314,7 +1313,7 @@ pub async fn move_document(
Json(payload): Json<MoveDocumentRequest>,
) -> AppResult<impl IntoResponse> {
if let Some(folder_id) = payload.folder_id {
ensure_folder_exists(&state, tenant_id, folder_id)?;
ensure_folder_exists_on_conn(&mut conn, tenant_id, folder_id)?;
}
let now = Utc::now().naive_utc();
@@ -1333,7 +1332,6 @@ pub async fn move_document(
}
pub async fn bulk_move_documents(
State(state): State<AppState>,
TenantScopedConn {
mut conn,
tenant_id,
@@ -1354,7 +1352,7 @@ pub async fn bulk_move_documents(
document_ids.dedup();
if let Some(target_folder) = folder_id {
ensure_folder_exists(&state, tenant_id, target_folder)?;
ensure_folder_exists_on_conn(&mut conn, tenant_id, target_folder)?;
}
let existing: Vec<(Uuid, Option<NaiveDateTime>)> = documents::table
@@ -1374,7 +1372,7 @@ pub async fn bulk_move_documents(
}
let now = Utc::now().naive_utc();
let updated = diesel::update(
let updated = match diesel::update(
documents::table
.filter(documents::id.eq_any(&document_ids))
.filter(documents::tenant_id.eq(tenant_id)),
@@ -1383,7 +1381,34 @@ pub async fn bulk_move_documents(
documents::folder_id.eq(folder_id),
documents::updated_at.eq(now),
))
.execute(&mut conn)?;
.execute(&mut conn)
{
Ok(value) => value,
Err(diesel::result::Error::DatabaseError(kind, info)) => {
error!(
?kind,
detail = ?info.details(),
constraint = info.constraint_name(),
tenant_id = %tenant_id,
target_folder = folder_id.map(|id| id.to_string()),
"bulk move update failed"
);
let message = info
.constraint_name()
.map(|name| format!("constraint {name} prevented moving documents"))
.unwrap_or_else(|| "unable to move documents due to a constraint".to_string());
return Err(AppError::conflict(message));
}
Err(err) => {
error!(
?err,
tenant_id = %tenant_id,
target_folder = folder_id.map(|id| id.to_string()),
"bulk move update failed"
);
return Err(AppError::from(err));
}
};
let body = BulkMoveResponse { updated };
Ok((StatusCode::OK, body.into_json()?))
@@ -1810,7 +1835,8 @@ async fn process_upload(
} = request;
if let Some(folder) = folder_id {
ensure_folder_exists(state, tenant_id, folder)?;
let mut conn = state.db_for_tenant(tenant_id)?;
ensure_folder_exists_on_conn(&mut conn, tenant_id, folder)?;
}
let doc_id = Uuid::new_v4();
@@ -2173,14 +2199,17 @@ fn insert_document_correspondents(
Ok(inserted)
}
fn ensure_folder_exists(state: &AppState, tenant_id: Uuid, folder_id: Uuid) -> AppResult<()> {
let mut conn = state.db_for_tenant(tenant_id)?;
fn ensure_folder_exists_on_conn(
conn: &mut PgConnection,
tenant_id: Uuid,
folder_id: Uuid,
) -> AppResult<()> {
let exists: bool = diesel::select(exists(
folders::table
.filter(folders::id.eq(folder_id))
.filter(folders::tenant_id.eq(tenant_id)),
))
.get_result(&mut conn)?;
.get_result(conn)?;
ensure_exists(exists, "folder")
}
+77 -15
View File
@@ -109,16 +109,16 @@ pub async fn ensure_folder_path(
let existing: Option<Folder> = if let Some(parent_id) = current_parent {
folders::table
.filter(folders::tenant_id.eq(tenant_id))
.filter(folders::parent_id.eq(Some(parent_id)))
.filter(folders::name.eq(name))
.filter(folders::tenant_id.eq(tenant_id))
.first(conn)
.optional()?
} else {
folders::table
.filter(folders::tenant_id.eq(tenant_id))
.filter(folders::parent_id.is_null())
.filter(folders::name.eq(name))
.filter(folders::tenant_id.eq(tenant_id))
.first(conn)
.optional()?
};
@@ -133,13 +133,32 @@ pub async fn ensure_folder_path(
tenant_id,
};
diesel::insert_into(folders::table)
let inserted_id: Option<Uuid> = diesel::insert_into(folders::table)
.values(&new_folder)
.execute(conn)?;
.on_conflict_do_nothing()
.returning(folders::id)
.get_result(conn)
.optional()?;
folders::table.find(new_folder.id).first(conn)?
if let Some(id) = inserted_id {
folders::table
.find(id)
.filter(folders::tenant_id.eq(tenant_id))
.first(conn)?
} else if let Some(parent_id) = current_parent {
folders::table
.filter(folders::tenant_id.eq(tenant_id))
.filter(folders::parent_id.eq(Some(parent_id)))
.filter(folders::name.eq(name))
.first(conn)?
} else {
folders::table
.filter(folders::tenant_id.eq(tenant_id))
.filter(folders::parent_id.is_null())
.filter(folders::name.eq(name))
.first(conn)?
}
};
current_parent = Some(folder.id);
last_folder = Some(folder);
}
@@ -164,18 +183,61 @@ pub async fn create_folder(
return Err(AppError::bad_request("name must not be empty"));
}
let new_folder = NewFolder {
id: Uuid::new_v4(),
name: payload.name.trim().to_string(),
parent_id: payload.parent_id,
tenant_id,
let name = payload.name.trim();
let existing: Option<Folder> = if let Some(parent_id) = payload.parent_id {
folders::table
.filter(folders::tenant_id.eq(tenant_id))
.filter(folders::parent_id.eq(Some(parent_id)))
.filter(folders::name.eq(name))
.first(&mut conn)
.optional()?
} else {
folders::table
.filter(folders::tenant_id.eq(tenant_id))
.filter(folders::parent_id.is_null())
.filter(folders::name.eq(name))
.first(&mut conn)
.optional()?
};
diesel::insert_into(folders::table)
.values(&new_folder)
.execute(&mut conn)?;
let folder: Folder = if let Some(folder) = existing {
folder
} else {
let new_folder = NewFolder {
id: Uuid::new_v4(),
name: name.to_string(),
parent_id: payload.parent_id,
tenant_id,
};
let inserted_id: Option<Uuid> = diesel::insert_into(folders::table)
.values(&new_folder)
.on_conflict_do_nothing()
.returning(folders::id)
.get_result(&mut conn)
.optional()?;
if let Some(id) = inserted_id {
folders::table
.find(id)
.filter(folders::tenant_id.eq(tenant_id))
.first(&mut conn)?
} else if let Some(parent_id) = payload.parent_id {
folders::table
.filter(folders::tenant_id.eq(tenant_id))
.filter(folders::parent_id.eq(Some(parent_id)))
.filter(folders::name.eq(name))
.first(&mut conn)?
} else {
folders::table
.filter(folders::tenant_id.eq(tenant_id))
.filter(folders::parent_id.is_null())
.filter(folders::name.eq(name))
.first(&mut conn)?
}
};
let folder: Folder = folders::table.find(new_folder.id).first(&mut conn)?;
Ok(Json(FolderResponse {
folder: folder_to_info(folder),
}))