feat(approvals): add GET /submission/{user_id} handler for admin submission viewer

Returns user info + onboarding state progress_json for a given user and
roleKey, enabling the admin panel to display full onboarding form answers
during approval review.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Ashwin Kumar 2026-03-23 00:34:49 +01:00
parent 91534d74c0
commit ac27184ae2

View file

@ -8,13 +8,18 @@ use axum::{
}; };
use contracts::auth_middleware::{require_admin, AuthUser}; use contracts::auth_middleware::{require_admin, AuthUser};
use db::models::job::JobRepository; use db::models::job::JobRepository;
use db::models::onboarding_state::OnboardingStateRepository;
use db::models::requirement::RequirementRepository; use db::models::requirement::RequirementRepository;
use db::models::role::RoleRepository;
use db::models::user::UserRepository;
use serde::Deserialize; use serde::Deserialize;
use uuid::Uuid; use uuid::Uuid;
pub fn router() -> Router<AppState> { pub fn router() -> Router<AppState> {
Router::new() Router::new()
.route("/", get(list_pending)) .route("/", get(list_pending))
// Submission viewer: GET /api/admin/approvals/submission/{user_id}?roleKey=PHOTOGRAPHER
.route("/submission/{user_id}", get(get_submission))
.route("/profiles/company/{user_id}/approve", post(approve_company_profile)) .route("/profiles/company/{user_id}/approve", post(approve_company_profile))
.route("/profiles/company/{user_id}/reject", post(reject_company_profile)) .route("/profiles/company/{user_id}/reject", post(reject_company_profile))
.route("/profiles/customer/{user_id}/approve", post(approve_customer_profile)) .route("/profiles/customer/{user_id}/approve", post(approve_customer_profile))
@ -27,6 +32,67 @@ pub fn router() -> Router<AppState> {
.route("/requirements/{id}/reject", post(reject_requirement)) .route("/requirements/{id}/reject", post(reject_requirement))
} }
#[derive(Deserialize)]
pub struct RoleKeyQuery {
#[serde(rename = "roleKey", alias = "role_key")]
pub role_key: Option<String>,
}
/// GET /api/admin/approvals/submission/{user_id}?roleKey=PHOTOGRAPHER
/// Returns the user info + their onboarding state (submitted form answers) for admin review.
async fn get_submission(
auth: AuthUser,
State(state): State<AppState>,
Path(user_id): Path<Uuid>,
Query(q): Query<RoleKeyQuery>,
) -> impl IntoResponse {
if let Err(e) = require_admin(&auth) {
return e.into_response();
}
// Fetch user
let user = match UserRepository::get_by_id(&state.pool, user_id).await {
Ok(u) => u,
Err(_) => return (StatusCode::NOT_FOUND, "User not found").into_response(),
};
// Fetch onboarding state (for the given roleKey, or the user's active role)
let role_key = q.role_key.filter(|k| !k.is_empty());
let onboarding = if let Some(ref rk) = role_key {
match RoleRepository::get_by_key(&state.pool, rk).await {
Ok(role) => OnboardingStateRepository::get(&state.pool, user_id, role.id)
.await
.unwrap_or(None),
Err(_) => None,
}
} else {
None
};
(
StatusCode::OK,
Json(serde_json::json!({
"user": {
"id": user.id,
"name": user.full_name,
"email": user.email,
"phone": user.phone,
"status": user.status,
"email_verified": user.email_verified,
"created_at": user.created_at,
},
"role_key": role_key,
"onboarding": onboarding.map(|s| serde_json::json!({
"status": s.status,
"progress_json": s.progress_json,
"completed_at": s.completed_at,
"updated_at": s.updated_at,
})),
})),
)
.into_response()
}
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct ListQuery { pub struct ListQuery {
pub page: Option<i64>, pub page: Option<i64>,