refactor
This commit is contained in:
+78
-146
@@ -48,10 +48,7 @@ use asset_utils::{
|
||||
build_download_path, derive_document_title, filename_with_retained_extension,
|
||||
to_asset_detail_response, to_asset_object_response, to_asset_summary, to_version_response,
|
||||
};
|
||||
use correspondent_utils::{
|
||||
is_valid_correspondent_role, normalize_correspondent_assignments, normalize_role,
|
||||
CORRESPONDENT_ROLES,
|
||||
};
|
||||
use correspondent_utils::normalize_correspondent_assignments;
|
||||
use search_utils::{build_quickwit_query, extract_document_id};
|
||||
|
||||
const PRESIGNED_URL_EXPIRY_SECONDS: u64 = 300;
|
||||
@@ -186,7 +183,6 @@ pub struct DocumentVersionDetailResponse {
|
||||
pub struct DocumentCorrespondentResponse {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub role: String,
|
||||
pub metadata: Value,
|
||||
pub assigned_at: String,
|
||||
}
|
||||
@@ -280,7 +276,6 @@ pub struct BulkCorrespondentResponse {
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct CorrespondentAssignmentInput {
|
||||
pub correspondent_id: Uuid,
|
||||
pub role: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
@@ -319,12 +314,6 @@ pub struct BulkCorrespondentsRequest {
|
||||
pub action: BulkCorrespondentAction,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, IntoParams, ToSchema)]
|
||||
#[into_params(parameter_in = Query)]
|
||||
pub struct CorrespondentRoleQuery {
|
||||
pub role: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct BulkReanalyzeSelectionRequest {
|
||||
pub document_ids: Vec<Uuid>,
|
||||
@@ -853,7 +842,7 @@ pub async fn upload_document(
|
||||
})?;
|
||||
correspondents = serde_json::from_str(&value).map_err(|err| {
|
||||
let msg = format!(
|
||||
"correspondents must be a JSON array of {{correspondent_id, role}} objects: {err}"
|
||||
"correspondents must be a JSON array of {{correspondent_id}} objects: {err}"
|
||||
);
|
||||
error!(error = %err, "invalid correspondents json");
|
||||
AppError::bad_request(msg)
|
||||
@@ -1672,8 +1661,7 @@ pub async fn assign_correspondents(
|
||||
return Err(AppError::bad_request("assignments must not be empty"));
|
||||
}
|
||||
|
||||
let (normalized_pairs, _correspondent_ids, roles_vec) =
|
||||
normalize_correspondent_assignments(&payload.assignments)?;
|
||||
let correspondent_ids = normalize_correspondent_assignments(&payload.assignments)?;
|
||||
let replace = payload.replace;
|
||||
|
||||
conn.transaction::<(), AppError, _>(|conn| {
|
||||
@@ -1685,21 +1673,41 @@ pub async fn assign_correspondents(
|
||||
return Err(AppError::not_found());
|
||||
}
|
||||
|
||||
let mut deleted = 0;
|
||||
let mut updated = false;
|
||||
if replace {
|
||||
deleted = diesel::delete(
|
||||
document_correspondents::table
|
||||
.filter(document_correspondents::document_id.eq(document_id))
|
||||
.filter(document_correspondents::tenant_id.eq(tenant_id))
|
||||
.filter(document_correspondents::role.eq_any(&roles_vec)),
|
||||
)
|
||||
.execute(conn)?;
|
||||
use diesel::dsl::not;
|
||||
|
||||
let base = document_correspondents::table
|
||||
.filter(document_correspondents::document_id.eq(document_id))
|
||||
.filter(document_correspondents::tenant_id.eq(tenant_id));
|
||||
|
||||
let removed = if correspondent_ids.is_empty() {
|
||||
diesel::delete(base).execute(conn)?
|
||||
} else {
|
||||
diesel::delete(base.filter(not(
|
||||
document_correspondents::correspondent_id.eq_any(&correspondent_ids),
|
||||
)))
|
||||
.execute(conn)?
|
||||
};
|
||||
|
||||
if removed > 0 {
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
let inserted =
|
||||
insert_document_correspondents(conn, tenant_id, &document, user_id, &normalized_pairs)?;
|
||||
let inserted = insert_document_correspondents(
|
||||
conn,
|
||||
tenant_id,
|
||||
document.id,
|
||||
user_id,
|
||||
&correspondent_ids,
|
||||
)?;
|
||||
|
||||
if replace && deleted > 0 && inserted == 0 {
|
||||
if inserted > 0 {
|
||||
updated = true;
|
||||
}
|
||||
|
||||
if updated && inserted == 0 {
|
||||
diesel::update(
|
||||
documents::table
|
||||
.find(document_id)
|
||||
@@ -1731,8 +1739,7 @@ pub async fn bulk_assign_correspondents(
|
||||
let mut document_ids = payload.document_ids;
|
||||
validate_bulk_ids(&mut document_ids, "document_ids")?;
|
||||
|
||||
let (normalized_pairs, correspondents_vec, _roles_vec) =
|
||||
normalize_correspondent_assignments(&payload.assignments)?;
|
||||
let correspondent_ids = normalize_correspondent_assignments(&payload.assignments)?;
|
||||
let action = payload.action;
|
||||
let user_id_val = user_id;
|
||||
let (assigned, removed) = conn.transaction::<(usize, usize), AppError, _>(|conn| {
|
||||
@@ -1754,12 +1761,12 @@ pub async fn bulk_assign_correspondents(
|
||||
));
|
||||
}
|
||||
|
||||
if !correspondents_vec.is_empty() {
|
||||
if !correspondent_ids.is_empty() {
|
||||
let existing: Vec<Correspondent> = correspondents::table
|
||||
.filter(correspondents::id.eq_any(&correspondents_vec))
|
||||
.filter(correspondents::id.eq_any(&correspondent_ids))
|
||||
.filter(correspondents::tenant_id.eq(tenant_id))
|
||||
.load(conn)?;
|
||||
if existing.len() != correspondents_vec.len() {
|
||||
if existing.len() != correspondent_ids.len() {
|
||||
return Err(AppError::bad_request(
|
||||
"one or more correspondents do not exist",
|
||||
));
|
||||
@@ -1768,92 +1775,34 @@ pub async fn bulk_assign_correspondents(
|
||||
|
||||
match action {
|
||||
BulkCorrespondentAction::Add => {
|
||||
use diesel::dsl::not;
|
||||
|
||||
let mut grouped_by_role: HashMap<String, Vec<Uuid>> = HashMap::new();
|
||||
for (correspondent_id, role) in &normalized_pairs {
|
||||
grouped_by_role
|
||||
.entry(role.clone())
|
||||
.or_default()
|
||||
.push(*correspondent_id);
|
||||
let mut assigned_total = 0;
|
||||
for (doc_id, _) in &docs {
|
||||
assigned_total += insert_document_correspondents(
|
||||
conn,
|
||||
tenant_id,
|
||||
*doc_id,
|
||||
user_id_val,
|
||||
&correspondent_ids,
|
||||
)?;
|
||||
}
|
||||
|
||||
let mut removed = 0;
|
||||
for (role, ids) in grouped_by_role.iter() {
|
||||
if ids.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let maintained_ids = ids.clone();
|
||||
let deleted = diesel::delete(
|
||||
document_correspondents::table
|
||||
.filter(document_correspondents::document_id.eq_any(&document_ids))
|
||||
.filter(document_correspondents::tenant_id.eq(tenant_id))
|
||||
.filter(document_correspondents::role.eq(role.as_str()))
|
||||
.filter(not(
|
||||
document_correspondents::correspondent_id.eq_any(maintained_ids)
|
||||
)),
|
||||
)
|
||||
.execute(conn)?;
|
||||
removed += deleted;
|
||||
}
|
||||
|
||||
let mut new_rows = Vec::with_capacity(document_ids.len() * normalized_pairs.len());
|
||||
for doc_id in &document_ids {
|
||||
for (correspondent_id, role) in &normalized_pairs {
|
||||
new_rows.push(NewDocumentCorrespondent {
|
||||
document_id: *doc_id,
|
||||
correspondent_id: *correspondent_id,
|
||||
role: role.clone(),
|
||||
assigned_by: Some(user_id_val),
|
||||
tenant_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let assigned = if new_rows.is_empty() {
|
||||
0
|
||||
} else {
|
||||
diesel::insert_into(document_correspondents::table)
|
||||
.values(&new_rows)
|
||||
.on_conflict_do_nothing()
|
||||
.execute(conn)?
|
||||
};
|
||||
|
||||
if assigned > 0 || removed > 0 {
|
||||
diesel::update(
|
||||
documents::table
|
||||
.filter(documents::id.eq_any(&document_ids))
|
||||
.filter(documents::tenant_id.eq(tenant_id)),
|
||||
)
|
||||
.set(documents::updated_at.eq(Utc::now().naive_utc()))
|
||||
.execute(conn)?;
|
||||
}
|
||||
|
||||
Ok((assigned, removed))
|
||||
Ok((assigned_total, 0))
|
||||
}
|
||||
BulkCorrespondentAction::Remove => {
|
||||
let mut removed = 0;
|
||||
if !normalized_pairs.is_empty() {
|
||||
let mut grouped: HashMap<String, Vec<Uuid>> = HashMap::new();
|
||||
for (correspondent_id, role) in &normalized_pairs {
|
||||
grouped
|
||||
.entry(role.clone())
|
||||
.or_default()
|
||||
.push(*correspondent_id);
|
||||
}
|
||||
|
||||
for (role, ids) in grouped {
|
||||
removed += diesel::delete(
|
||||
document_correspondents::table
|
||||
.filter(document_correspondents::document_id.eq_any(&document_ids))
|
||||
.filter(document_correspondents::tenant_id.eq(tenant_id))
|
||||
.filter(document_correspondents::role.eq(role.as_str()))
|
||||
.filter(document_correspondents::correspondent_id.eq_any(&ids)),
|
||||
)
|
||||
.execute(conn)?;
|
||||
}
|
||||
if correspondent_ids.is_empty() {
|
||||
return Ok((0, 0));
|
||||
}
|
||||
|
||||
let removed = diesel::delete(
|
||||
document_correspondents::table
|
||||
.filter(document_correspondents::document_id.eq_any(&document_ids))
|
||||
.filter(document_correspondents::tenant_id.eq(tenant_id))
|
||||
.filter(
|
||||
document_correspondents::correspondent_id.eq_any(&correspondent_ids),
|
||||
),
|
||||
)
|
||||
.execute(conn)?;
|
||||
|
||||
if removed > 0 {
|
||||
diesel::update(
|
||||
documents::table
|
||||
@@ -1875,24 +1824,12 @@ pub async fn bulk_assign_correspondents(
|
||||
|
||||
pub async fn remove_correspondent(
|
||||
Path((document_id, correspondent_id)): Path<(Uuid, Uuid)>,
|
||||
Query(query): Query<CorrespondentRoleQuery>,
|
||||
TenantScopedConn {
|
||||
mut conn,
|
||||
tenant_id,
|
||||
..
|
||||
}: TenantScopedConn,
|
||||
) -> AppResult<StatusCode> {
|
||||
let role = normalize_role(&query.role);
|
||||
if role.is_empty() {
|
||||
return Err(AppError::bad_request("role must not be empty"));
|
||||
}
|
||||
if !is_valid_correspondent_role(&role) {
|
||||
return Err(AppError::bad_request(format!(
|
||||
"invalid correspondent role '{role}'. Allowed roles: {}",
|
||||
CORRESPONDENT_ROLES.join(", ")
|
||||
)));
|
||||
}
|
||||
|
||||
let document: Document = documents::table
|
||||
.find(document_id)
|
||||
.filter(documents::tenant_id.eq(tenant_id))
|
||||
@@ -1905,8 +1842,7 @@ pub async fn remove_correspondent(
|
||||
document_correspondents::table
|
||||
.filter(document_correspondents::document_id.eq(document_id))
|
||||
.filter(document_correspondents::tenant_id.eq(tenant_id))
|
||||
.filter(document_correspondents::correspondent_id.eq(correspondent_id))
|
||||
.filter(document_correspondents::role.eq(&role)),
|
||||
.filter(document_correspondents::correspondent_id.eq(correspondent_id)),
|
||||
)
|
||||
.execute(&mut conn)?;
|
||||
|
||||
@@ -2374,47 +2310,45 @@ fn assign_correspondents_internal(
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let (normalized_pairs, _correspondent_ids, _roles) =
|
||||
normalize_correspondent_assignments(assignments)?;
|
||||
let ids = normalize_correspondent_assignments(assignments)?;
|
||||
|
||||
insert_document_correspondents(conn, tenant_id, document, user_id, &normalized_pairs)
|
||||
insert_document_correspondents(conn, tenant_id, document.id, user_id, &ids)
|
||||
}
|
||||
|
||||
fn insert_document_correspondents(
|
||||
conn: &mut PgConnection,
|
||||
tenant_id: Uuid,
|
||||
document: &Document,
|
||||
document_id: Uuid,
|
||||
user_id: Uuid,
|
||||
normalized_pairs: &[(Uuid, String)],
|
||||
correspondent_ids: &[Uuid],
|
||||
) -> AppResult<usize> {
|
||||
if normalized_pairs.is_empty() {
|
||||
if correspondent_ids.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let mut correspondent_ids: Vec<Uuid> = normalized_pairs.iter().map(|(id, _)| *id).collect();
|
||||
correspondent_ids.sort_unstable();
|
||||
correspondent_ids.dedup();
|
||||
let mut unique_ids: Vec<Uuid> = correspondent_ids.to_vec();
|
||||
unique_ids.sort_unstable();
|
||||
unique_ids.dedup();
|
||||
|
||||
if !correspondent_ids.is_empty() {
|
||||
if !unique_ids.is_empty() {
|
||||
let existing: Vec<Uuid> = correspondents::table
|
||||
.filter(correspondents::id.eq_any(&correspondent_ids))
|
||||
.filter(correspondents::id.eq_any(&unique_ids))
|
||||
.filter(correspondents::tenant_id.eq(tenant_id))
|
||||
.select(correspondents::id)
|
||||
.load(conn)?;
|
||||
|
||||
if existing.len() != correspondent_ids.len() {
|
||||
if existing.len() != unique_ids.len() {
|
||||
return Err(AppError::bad_request(
|
||||
"one or more correspondents do not exist",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let new_rows: Vec<NewDocumentCorrespondent> = normalized_pairs
|
||||
.iter()
|
||||
.map(|(correspondent_id, role)| NewDocumentCorrespondent {
|
||||
document_id: document.id,
|
||||
correspondent_id: *correspondent_id,
|
||||
role: role.clone(),
|
||||
let new_rows: Vec<NewDocumentCorrespondent> = unique_ids
|
||||
.into_iter()
|
||||
.map(|correspondent_id| NewDocumentCorrespondent {
|
||||
document_id,
|
||||
correspondent_id,
|
||||
assigned_by: Some(user_id),
|
||||
tenant_id,
|
||||
})
|
||||
@@ -2432,7 +2366,7 @@ fn insert_document_correspondents(
|
||||
if inserted > 0 {
|
||||
diesel::update(
|
||||
documents::table
|
||||
.find(document.id)
|
||||
.find(document_id)
|
||||
.filter(documents::tenant_id.eq(tenant_id)),
|
||||
)
|
||||
.set(documents::updated_at.eq(Utc::now().naive_utc()))
|
||||
@@ -2490,7 +2424,6 @@ pub(crate) fn load_correspondents_for_documents(
|
||||
.filter(document_correspondents::document_id.eq_any(document_ids))
|
||||
.order((
|
||||
document_correspondents::document_id.asc(),
|
||||
document_correspondents::role.asc(),
|
||||
document_correspondents::assigned_at.asc(),
|
||||
))
|
||||
.load(conn)?;
|
||||
@@ -2502,7 +2435,6 @@ pub(crate) fn load_correspondents_for_documents(
|
||||
.push(DocumentCorrespondentResponse {
|
||||
id: correspondent.id,
|
||||
name: correspondent.name,
|
||||
role: assignment.role,
|
||||
metadata: correspondent.metadata,
|
||||
assigned_at: to_iso(assignment.assigned_at),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user