refactor: labrinth ApiError and error reporting (#6981)

* refactor: labrinth `ApiError` and error reporting

* fix clippy

* fix ci
This commit is contained in:
aecsocket
2026-08-07 15:47:54 +00:00
committed by GitHub
parent d344cbcb7a
commit 3fa6905006
102 changed files with 6449 additions and 3791 deletions
@@ -1,3 +1,5 @@
use crate::util::error::ApiContext as _;
use crate::util::error::Context as _;
use std::collections::HashMap;
use actix_web::{HttpRequest, get, patch, post, web};
@@ -99,9 +101,9 @@ impl LicenseId {
match self {
LicenseId::Number(id) => Ok(id),
LicenseId::String(id) => id.parse().map_err(|_| {
ApiError::InvalidInput(
"license_id must be a valid integer".to_string(),
)
ApiError::Request(eyre::eyre!(
"license_id must be a valid integer",
))
}),
}
}
@@ -143,9 +145,9 @@ fn normalize_sha1_hashes(hashes: &[String]) -> Result<Vec<String>, ApiError> {
let hash = hash.trim().to_lowercase();
if hash.len() != 40 || !hash.chars().all(|c| c.is_ascii_hexdigit())
{
return Err(ApiError::InvalidInput(
"hash must be a valid SHA1 hex string".to_string(),
));
return Err(ApiError::Request(eyre::eyre!(
"hash must be a valid SHA1 hex string",
)));
}
Ok(hash)
@@ -196,7 +198,8 @@ async fn fetch_linked_files(
license_ids,
)
.fetch_all(pool)
.await?;
.await
.wrap_internal_err("fetching file rows from database")?;
let mut map: HashMap<i64, Vec<LinkedFile>> = HashMap::new();
for row in file_rows {
@@ -247,10 +250,12 @@ async fn fetch_by_hashes(
&hash_bytes,
)
.fetch_all(pool)
.await?;
.await.wrap_internal_err("querying database for `fetch_by_hashes`")?;
let license_ids = rows.iter().map(|row| row.id).collect::<Vec<_>>();
let files_map = fetch_linked_files(pool, &license_ids).await?;
let files_map = fetch_linked_files(pool, &license_ids)
.await
.wrap_api_err("fetching linked files")?;
let mut results = HashMap::new();
for row in rows {
@@ -309,10 +314,13 @@ async fn fetch_by_flame_ids(
flame_ids,
)
.fetch_all(pool)
.await?;
.await
.wrap_internal_err("querying database for `fetch_by_flame_ids`")?;
let license_ids = rows.iter().map(|row| row.id).collect::<Vec<_>>();
let files_map = fetch_linked_files(pool, &license_ids).await?;
let files_map = fetch_linked_files(pool, &license_ids)
.await
.wrap_api_err("fetching linked files")?;
let mut results: HashMap<i32, Vec<ExternalProject>> = HashMap::new();
for row in rows {
@@ -350,7 +358,8 @@ pub async fn search(
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
.await
.wrap_auth_err("authenticating external license search")?;
let rows = sqlx::query_as!(
LicenseRow,
@@ -381,10 +390,13 @@ pub async fn search(
body.flame_ids.as_deref(),
)
.fetch_all(&**pool)
.await?;
.await
.wrap_internal_err("querying database for `search`")?;
let license_ids: Vec<i64> = rows.iter().map(|r| r.id).collect();
let files_map = fetch_linked_files(&pool, &license_ids).await?;
let files_map = fetch_linked_files(&pool, &license_ids)
.await
.wrap_api_err("fetching linked files")?;
let results = rows
.into_iter()
@@ -419,12 +431,18 @@ pub async fn lookup(
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
.await
.wrap_auth_err("authenticating API request")?;
let body = body.into_inner();
let hashes = normalize_sha1_hashes(&body.hashes)?;
let flame_ids = fetch_by_flame_ids(&pool, &body.flame_ids).await?;
let hashes = fetch_by_hashes(&pool, &hashes).await?;
let hashes = normalize_sha1_hashes(&body.hashes)
.wrap_api_err("executing `normalize_sha1_hashes`")?;
let flame_ids = fetch_by_flame_ids(&pool, &body.flame_ids)
.await
.wrap_api_err("fetching by flame ids")?;
let hashes = fetch_by_hashes(&pool, &hashes)
.await
.wrap_api_err("fetching by hashes")?;
Ok(web::Json(ExternalLicenseLookupResponse {
flame_ids,
@@ -453,12 +471,18 @@ pub async fn get_by_sha1(
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
.await
.wrap_auth_err("authenticating API request")?;
let hashes = normalize_sha1_hashes(&[path.into_inner().0])?;
let hash = hashes.first().ok_or(ApiError::NotFound)?;
let mut results = fetch_by_hashes(&pool, &hashes).await?;
let result = results.remove(hash).ok_or(ApiError::NotFound)?;
let hashes = normalize_sha1_hashes(&[path.into_inner().0])
.wrap_api_err("normalizing SHA-1 hash")?;
let hash = hashes.first().wrap_not_found_err("resource not found")?;
let mut results = fetch_by_hashes(&pool, &hashes)
.await
.wrap_api_err("fetching by hashes")?;
let result = results
.remove(hash)
.wrap_not_found_err("resource not found")?;
Ok(web::Json(result))
}
@@ -484,10 +508,14 @@ pub async fn get_by_sha1_bulk(
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
.await
.wrap_auth_err("authenticating API request")?;
let hashes = normalize_sha1_hashes(&body.hashes)?;
let results = fetch_by_hashes(&pool, &hashes).await?;
let hashes = normalize_sha1_hashes(&body.hashes)
.wrap_api_err("executing `normalize_sha1_hashes`")?;
let results = fetch_by_hashes(&pool, &hashes)
.await
.wrap_api_err("fetching by hashes")?;
Ok(web::Json(results))
}
@@ -540,16 +568,21 @@ async fn upsert_file_license(
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
.await
.wrap_auth_err("authenticating API request")?;
let body = body.into_inner();
let license_id = body.license_id.parse()?;
let license_id = body
.license_id
.parse()
.wrap_api_err("parsing external license ID")?;
if body.hashes.is_empty() {
return Err(ApiError::InvalidInput(
"hashes must contain at least one SHA1 hex string".to_string(),
));
return Err(ApiError::Request(eyre::eyre!(
"hashes must contain at least one SHA1 hex string",
)));
}
let hashes = normalize_sha1_hashes(&body.hashes)?;
let hashes = normalize_sha1_hashes(&body.hashes)
.wrap_api_err("executing `normalize_sha1_hashes`")?;
let hash_bytes = hashes
.iter()
.map(|hash| hash.as_bytes().to_vec())
@@ -557,7 +590,10 @@ async fn upsert_file_license(
let filenames = vec![None; hashes.len()];
let license_ids = vec![license_id; hashes.len()];
let mut transaction = pool.begin().await?;
let mut transaction = pool
.begin()
.await
.wrap_internal_err("starting database transaction")?;
let license = sqlx::query!(
r#"
@@ -579,8 +615,9 @@ async fn upsert_file_license(
license_id,
)
.fetch_optional(&mut transaction)
.await?
.ok_or(ApiError::NotFound)?;
.await
.wrap_internal_err("fetching license from database")?
.wrap_not_found_err("resource not found")?;
ExternalLicense::insert_files(
&mut transaction,
@@ -589,11 +626,19 @@ async fn upsert_file_license(
&license_ids,
DBUserId(user.id.0 as i64),
)
.await?;
.await
.wrap_internal_err(
"inserting database records for `upsert_file_license`",
)?;
transaction.commit().await?;
transaction
.commit()
.await
.wrap_internal_err("committing database transaction")?;
let files_map = fetch_linked_files(&pool, &[license_id]).await?;
let files_map = fetch_linked_files(&pool, &[license_id])
.await
.wrap_api_err("fetching linked files")?;
let linked_files = files_map.get(&license_id).cloned().unwrap_or_default();
Ok(web::Json(
@@ -636,7 +681,8 @@ pub async fn update_license(
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
.await
.wrap_auth_err("authenticating API request")?;
let id = path.into_inner().0;
@@ -666,10 +712,13 @@ pub async fn update_license(
user.id.0 as i64,
)
.fetch_optional(&**pool)
.await?
.ok_or(ApiError::NotFound)?;
.await
.wrap_internal_err("querying database for `update_license`")?
.wrap_not_found_err("resource not found")?;
let files_map = fetch_linked_files(&pool, &[id]).await?;
let files_map = fetch_linked_files(&pool, &[id])
.await
.wrap_api_err("fetching linked files")?;
let linked_files = files_map.get(&id).cloned().unwrap_or_default();
Ok(web::Json(
@@ -8,6 +8,7 @@ use crate::models::ids::{OrganizationId, ProjectId};
use crate::models::projects::{ProjectStatus, VersionStatus};
use crate::queue::moderation::{ApprovalType, IdentifiedFile, MissingMetadata};
use crate::queue::session::AuthQueue;
use crate::util::error::ApiContext as _;
use crate::util::error::Context;
use crate::{
auth::{check_is_moderator_from_headers, get_user_from_bearer_token},
@@ -254,7 +255,8 @@ pub async fn get_projects_internal(
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
.await
.wrap_auth_err("authenticating API request")?;
let request_opts = request_opts.into_inner();
let query = normalize_optional_string(request_opts.query.as_deref());
@@ -518,7 +520,9 @@ pub async fn get_projects_internal(
row.owner_icon_url,
row.project_types,
row.external_dependencies_count,
)? {
)
.wrap_api_err("executing `row_to_queue_project`")?
{
projects.push(project);
}
}
@@ -693,7 +697,9 @@ pub async fn get_projects_internal(
row.owner_icon_url,
row.project_types,
row.external_dependencies_count,
)? {
)
.wrap_api_err("executing `row_to_queue_project`")?
{
projects.push(project);
}
}
@@ -731,7 +737,8 @@ pub async fn get_project_ids(
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
.await
.wrap_auth_err("authenticating API request")?;
let request_opts = request_opts.into_inner();
let query = normalize_optional_string(request_opts.query.as_deref());
@@ -978,7 +985,8 @@ fn row_to_queue_project(
owner_id,
owner_name,
owner_icon_url,
)?;
)
.wrap_api_err("executing `row_to_ownership`")?;
Ok(Some(ModerationQueueProject {
id: project_id,
@@ -1076,11 +1084,14 @@ pub async fn get_project_meta(
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
.await
.wrap_auth_err("authenticating API request")?;
let project_id = info.into_inner().0;
let project =
database::models::DBProject::get(&project_id, &**pool, &redis).await?;
database::models::DBProject::get(&project_id, &**pool, &redis)
.await
.wrap_api_err("fetching project from database")?;
if let Some(project) = project {
let rows = sqlx::query!(
@@ -1094,7 +1105,8 @@ pub async fn get_project_meta(
project.inner.id.0
)
.fetch_all(&**pool)
.await?;
.await
.wrap_internal_err("querying database for `get_project_meta`")?;
let mut merged = MissingMetadata {
identified: HashMap::new(),
@@ -1134,7 +1146,7 @@ pub async fn get_project_meta(
.collect::<Vec<_>>()
)
.fetch_all(&**pool)
.await?;
.await.wrap_internal_err("querying database for `get_project_meta`")?;
for row in rows {
if let Some(sha1) = row.sha1 {
@@ -1169,7 +1181,8 @@ pub async fn get_project_meta(
&check_flames,
)
.fetch_all(&**pool)
.await?;
.await
.wrap_internal_err("querying database for `get_project_meta`")?;
for row in rows {
if let Some(sha1) = merged
@@ -1192,7 +1205,7 @@ pub async fn get_project_meta(
Ok(web::Json(merged))
} else {
Err(ApiError::NotFound)
Err(ApiError::NotFound(eyre::eyre!("resource not found")))
}
}
@@ -1234,9 +1247,13 @@ pub async fn set_project_meta(
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
.await
.wrap_auth_err("authenticating API request")?;
let mut transaction = pool.begin().await?;
let mut transaction = pool
.begin()
.await
.wrap_internal_err("starting database transaction")?;
let mut licenses = Vec::new();
let mut file_hashes = Vec::new();
@@ -1287,7 +1304,8 @@ pub async fn set_project_meta(
&licenses,
user_id,
)
.await?;
.await
.wrap_internal_err("inserting database records for `set_project_meta`")?;
moderation_external_item::ExternalLicense::insert_files(
&mut transaction,
@@ -1299,9 +1317,13 @@ pub async fn set_project_meta(
&file_license_ids,
user_id,
)
.await?;
.await
.wrap_internal_err("inserting database records for `set_project_meta`")?;
transaction.commit().await?;
transaction
.commit()
.await
.wrap_internal_err("committing database transaction")?;
Ok(())
}
@@ -1331,18 +1353,23 @@ pub async fn acquire_lock(
&session_queue,
Scopes::PROJECT_WRITE,
)
.await?;
.await
.wrap_auth_err("authenticating API request")?;
let project_id_str = path.into_inner().0;
let project =
database::models::DBProject::get(&project_id_str, &**pool, &redis)
.await?
.ok_or(ApiError::NotFound)?;
.await
.wrap_api_err("fetching project from database")?
.wrap_not_found_err("resource not found")?;
let db_project_id = project.inner.id;
let db_user_id = database::models::DBUserId::from(user.id);
match DBModerationLock::acquire(db_project_id, db_user_id, &pool).await? {
match DBModerationLock::acquire(db_project_id, db_user_id, &pool)
.await
.wrap_internal_err("executing `DBModerationLock::acquire`")?
{
Ok(()) => Ok(web::Json(LockAcquireResponse {
success: true,
is_own_lock: true,
@@ -1390,18 +1417,22 @@ pub async fn override_lock(
&session_queue,
Scopes::PROJECT_WRITE,
)
.await?;
.await
.wrap_auth_err("authenticating API request")?;
let project_id_str = path.into_inner().0;
let project =
database::models::DBProject::get(&project_id_str, &**pool, &redis)
.await?
.ok_or(ApiError::NotFound)?;
.await
.wrap_api_err("fetching project from database")?
.wrap_not_found_err("resource not found")?;
let db_project_id = project.inner.id;
let db_user_id = database::models::DBUserId::from(user.id);
DBModerationLock::force_acquire(db_project_id, db_user_id, &pool).await?;
DBModerationLock::force_acquire(db_project_id, db_user_id, &pool)
.await
.wrap_internal_err("executing `DBModerationLock::force_acquire`")?;
Ok(web::Json(LockAcquireResponse {
success: true,
@@ -1437,18 +1468,23 @@ pub async fn get_lock_status(
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
.await
.wrap_auth_err("authenticating API request")?;
let project_id_str = path.into_inner().0;
let project =
database::models::DBProject::get(&project_id_str, &**pool, &redis)
.await?
.ok_or(ApiError::NotFound)?;
.await
.wrap_api_err("fetching project from database")?
.wrap_not_found_err("resource not found")?;
let db_project_id = project.inner.id;
let db_user_id = database::models::DBUserId::from(user.id);
match DBModerationLock::get_with_user(db_project_id, &pool).await? {
match DBModerationLock::get_with_user(db_project_id, &pool)
.await
.wrap_internal_err("fetching moderation lock from database")?
{
Some(lock) => {
let is_own_lock = lock.moderator_id == db_user_id;
Ok(web::Json(LockStatusResponse {
@@ -1499,19 +1535,22 @@ pub async fn release_lock(
&session_queue,
Scopes::PROJECT_WRITE,
)
.await?;
.await
.wrap_auth_err("authenticating API request")?;
let project_id_str = path.into_inner().0;
let project =
database::models::DBProject::get(&project_id_str, &**pool, &redis)
.await?
.ok_or(ApiError::NotFound)?;
.await
.wrap_api_err("fetching project from database")?
.wrap_not_found_err("resource not found")?;
let db_project_id = project.inner.id;
let db_user_id = database::models::DBUserId::from(user.id);
let released =
DBModerationLock::release(db_project_id, db_user_id, &pool).await?;
let released = DBModerationLock::release(db_project_id, db_user_id, &pool)
.await
.wrap_internal_err("executing `DBModerationLock::release`")?;
let _ = DBModerationLock::cleanup_expired(&pool).await;
@@ -1547,9 +1586,9 @@ pub async fn release_lock_beacon(
) -> Result<web::Json<LockReleaseResponse>, ApiError> {
let token = body.trim();
if token.is_empty() {
return Err(ApiError::InvalidInput(
"missing token in request body".to_string(),
));
return Err(ApiError::Request(eyre::eyre!(
"missing token in request body",
)));
}
let token = token.strip_prefix("Bearer ").unwrap_or(token).trim();
@@ -1561,30 +1600,33 @@ pub async fn release_lock_beacon(
&session_queue,
false,
)
.await?;
.await
.wrap_auth_err("authenticating API request")?;
if !scopes.contains(Scopes::PROJECT_WRITE) {
return Err(ApiError::CustomAuthentication(
"token is missing required scopes".to_string(),
));
return Err(ApiError::Auth(eyre::eyre!(
"token is missing required scopes",
)));
}
if !user.role.is_mod() {
return Err(ApiError::CustomAuthentication(
"only moderators may release moderation locks".to_string(),
));
return Err(ApiError::Auth(eyre::eyre!(
"only moderators may release moderation locks",
)));
}
let project_id_str = path.into_inner().0;
let project =
database::models::DBProject::get(&project_id_str, &**pool, &redis)
.await?
.ok_or(ApiError::NotFound)?;
.await
.wrap_api_err("fetching project from database")?
.wrap_not_found_err("resource not found")?;
let db_project_id = project.inner.id;
let db_user_id = database::models::DBUserId::from(user.id);
let released =
DBModerationLock::release(db_project_id, db_user_id, &pool).await?;
let released = DBModerationLock::release(db_project_id, db_user_id, &pool)
.await
.wrap_internal_err("executing `DBModerationLock::release`")?;
let _ = DBModerationLock::cleanup_expired(&pool).await;
@@ -1614,16 +1656,19 @@ pub async fn delete_all_locks(
&session_queue,
Scopes::PROJECT_WRITE,
)
.await?
.await
.wrap_auth_err("authenticating API request")?
.1;
if !user.role.is_admin() {
return Err(ApiError::CustomAuthentication(
"You must be an admin to delete all locks".to_string(),
));
return Err(ApiError::Auth(eyre::eyre!(
"You must be an admin to delete all locks",
)));
}
let deleted_count = DBModerationLock::delete_all(&pool).await?;
let deleted_count = DBModerationLock::delete_all(&pool)
.await
.wrap_internal_err("deleting moderation locks from database")?;
Ok(web::Json(DeleteAllLocksResponse { deleted_count }))
}
@@ -1,3 +1,4 @@
use crate::util::error::ApiContext as _;
use std::{collections::HashMap, fmt};
use xredis::RedisPool;
@@ -225,7 +226,8 @@ pub async fn get_issue(
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
.await
.wrap_auth_err("authenticating API request")?;
let (issue_id,) = path.into_inner();
let row = sqlx::query!(
@@ -261,7 +263,7 @@ pub async fn get_issue(
.fetch_optional(&**pool)
.await
.wrap_internal_err("failed to fetch issue from database")?
.ok_or(ApiError::NotFound)?;
.wrap_not_found_err("resource not found")?;
Ok(web::Json(row.data.0))
}
@@ -288,7 +290,8 @@ pub async fn get_report(
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
.await
.wrap_auth_err("authenticating API request")?;
let (report_id,) = path.into_inner();
@@ -348,7 +351,7 @@ pub async fn get_report(
.fetch_optional(&**pool)
.await
.wrap_internal_err("failed to fetch report from database")?
.ok_or(ApiError::NotFound)?;
.wrap_not_found_err("resource not found")?;
Ok(web::Json(row.data.0))
}
@@ -703,7 +706,8 @@ pub async fn search_projects(
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
.await
.wrap_auth_err("authenticating API request")?;
let sort_by = search_req.sort_by.to_string();
let limit = search_req.limit.max(50);
@@ -819,8 +823,9 @@ pub async fn search_projects(
thread_ids.push(row.thread_id);
}
let project_reports =
fetch_project_reports(&project_ids, &pool, &redis).await?;
let project_reports = fetch_project_reports(&project_ids, &pool, &redis)
.await
.wrap_api_err("fetching project reports")?;
let projects = DBProject::get_many_ids(&project_ids, &**pool, &redis)
.await
@@ -913,7 +918,8 @@ pub async fn get_project_report(
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
.await
.wrap_auth_err("authenticating API request")?;
let (project_id,) = path.into_inner();
let db_project_id = DBProjectId::from(project_id);
@@ -929,10 +935,12 @@ pub async fn get_project_report(
.fetch_optional(&**pool)
.await
.wrap_internal_err("failed to fetch thread")?
.ok_or(ApiError::NotFound)?;
.wrap_not_found_err("resource not found")?;
let project_reports =
fetch_project_reports(&[db_project_id], &pool, &redis).await?;
fetch_project_reports(&[db_project_id], &pool, &redis)
.await
.wrap_api_err("fetching project reports")?;
let project_report = project_reports.into_iter().next();
@@ -966,7 +974,7 @@ pub async fn get_project_report(
let thread = threads
.get(&row.thread_id.into())
.cloned()
.ok_or(ApiError::NotFound)?;
.wrap_not_found_err("resource not found")?;
Ok(web::Json(ProjectReportResponse {
project_report,
@@ -988,8 +996,8 @@ pub struct SubmitReport {
///
/// Before this is called, all issues for this project's reports must have been
/// marked as either safe or unsafe. Otherwise, this will error with
/// [`ApiError::TechReviewIssuesWithNoVerdict`], providing the issue IDs which
/// are still unmarked.
/// A request error is returned with the issue detail IDs which are still
/// unmarked.
#[utoipa::path(
context_path = "/moderation/tech-review",
tag = "moderation",
@@ -1013,7 +1021,8 @@ pub async fn submit_report(
&session_queue,
Scopes::PROJECT_WRITE,
)
.await?;
.await
.wrap_auth_err("authenticating API request")?;
let (project_id,) = path.into_inner();
let project_id = DBProjectId::from(project_id);
@@ -1045,14 +1054,13 @@ pub async fn submit_report(
.wrap_internal_err("failed to fetch pending issues")?;
if !pending_issue_details.is_empty() {
return Err(ApiError::TechReviewDetailsWithNoVerdict {
details: pending_issue_details
.into_iter()
.map(|record| {
DelphiReportIssueDetailsId(record.issue_detail_id)
})
.collect(),
});
let details = pending_issue_details
.into_iter()
.map(|record| DelphiReportIssueDetailsId(record.issue_detail_id))
.collect_vec();
return Err(ApiError::Request(eyre::eyre!(
"report still has issue details with no verdict: {details:?}"
)));
}
sqlx::query!(
@@ -1171,7 +1179,10 @@ pub async fn submit_report(
None,
None,
)
.await?;
.await
.wrap_api_err(
"executing `projects::clear_project_cache_and_queue_search`",
)?;
}
Ok(())
@@ -1224,7 +1235,8 @@ pub async fn update_issue_details(
&session_queue,
Scopes::PROJECT_WRITE,
)
.await?;
.await
.wrap_auth_err("updating database records for `update_issue_details`")?;
let mut txn = pool
.begin()
@@ -1347,7 +1359,10 @@ pub async fn update_issue_details(
TechReviewExitReason::Resolved,
&mut txn,
)
.await?;
.await
.wrap_api_err(
"executing `tech_review_sync::sync_project_tech_review_state`",
)?;
txn.commit()
.await
@@ -1381,7 +1396,10 @@ pub async fn update_global_issue_details(
&session_queue,
Scopes::PROJECT_WRITE,
)
.await?;
.await
.wrap_auth_err(
"updating database records for `update_global_issue_details`",
)?;
let updates = update_reqs.into_inner();
@@ -1461,7 +1479,10 @@ pub async fn update_global_issue_details(
TechReviewExitReason::Resolved,
&mut txn,
)
.await?;
.await
.wrap_api_err(
"executing `tech_review_sync::sync_detail_key_tech_review_state`",
)?;
txn.commit()
.await
@@ -1498,7 +1519,8 @@ pub async fn add_report(
&session_queue,
Scopes::PROJECT_WRITE,
)
.await?;
.await
.wrap_auth_err("inserting database records for `add_report`")?;
let file_id = add_report.file_id;
let mut txn = pool
@@ -148,7 +148,8 @@ pub async fn search_global_issue_details(
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
.await
.wrap_auth_err("authenticating global issue search")?;
let query = search_req
.query
@@ -370,7 +371,8 @@ pub async fn get_global_issue_detail(
&session_queue,
Scopes::PROJECT_READ,
)
.await?;
.await
.wrap_auth_err("authenticating global issue detail request")?;
let detail_key = get_req.detail_key.trim();
if detail_key.is_empty() {
@@ -404,7 +406,7 @@ pub async fn get_global_issue_detail(
.fetch_optional(&**pool)
.await
.wrap_internal_err("failed to fetch global issue detail")?
.ok_or(ApiError::NotFound)?;
.wrap_not_found_err("resource not found")?;
let local_rows = sqlx::query!(
r#"