mirror of
https://github.com/Traceworks2023/nxtgauge-backend-rust.git
synced 2026-06-11 14:00:01 +00:00
fix(session1): customer list_requests path arg, external-role by-key endpoint, RuntimeRoleDetail type
This commit is contained in:
parent
2c6d102205
commit
319b384f0a
3 changed files with 108 additions and 16 deletions
|
|
@ -222,8 +222,7 @@ async fn submit_requirement(
|
||||||
|
|
||||||
async fn list_requests(
|
async fn list_requests(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(id): Path<Uuid>,
|
auth: AuthUser,
|
||||||
_auth: AuthUser,
|
|
||||||
Query(q): Query<PaginationQuery>,
|
Query(q): Query<PaginationQuery>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let page = q.page.unwrap_or(1);
|
let page = q.page.unwrap_or(1);
|
||||||
|
|
@ -233,12 +232,12 @@ async fn list_requests(
|
||||||
let rows_result = sqlx::query_as::<_, db::models::lead_request::LeadRequest>(
|
let rows_result = sqlx::query_as::<_, db::models::lead_request::LeadRequest>(
|
||||||
r#"
|
r#"
|
||||||
SELECT * FROM lead_requests
|
SELECT * FROM lead_requests
|
||||||
WHERE user_role_profile_id = $1
|
WHERE professional_user_id = $1
|
||||||
ORDER BY requested_at DESC
|
ORDER BY requested_at DESC
|
||||||
LIMIT $2 OFFSET $3
|
LIMIT $2 OFFSET $3
|
||||||
"#
|
"#
|
||||||
)
|
)
|
||||||
.bind(id)
|
.bind(auth.user_id)
|
||||||
.bind(limit)
|
.bind(limit)
|
||||||
.bind(offset)
|
.bind(offset)
|
||||||
.fetch_all(&state.pool)
|
.fetch_all(&state.pool)
|
||||||
|
|
@ -271,7 +270,7 @@ async fn approve_request(
|
||||||
Ok(updated) => {
|
Ok(updated) => {
|
||||||
match TracecoinWalletRepository::try_debit_reserved_tracecoins(
|
match TracecoinWalletRepository::try_debit_reserved_tracecoins(
|
||||||
&state.pool,
|
&state.pool,
|
||||||
lead.user_role_profile_id,
|
lead.user_role_profile_id.unwrap(),
|
||||||
lead.tracecoins_reserved,
|
lead.tracecoins_reserved,
|
||||||
lead.id,
|
lead.id,
|
||||||
).await {
|
).await {
|
||||||
|
|
@ -307,7 +306,7 @@ async fn reject_request(
|
||||||
Ok(updated) => {
|
Ok(updated) => {
|
||||||
match TracecoinWalletRepository::try_release_reserved_tracecoins(
|
match TracecoinWalletRepository::try_release_reserved_tracecoins(
|
||||||
&state.pool,
|
&state.pool,
|
||||||
lead.user_role_profile_id,
|
lead.user_role_profile_id.unwrap(),
|
||||||
lead.tracecoins_reserved,
|
lead.tracecoins_reserved,
|
||||||
lead.id,
|
lead.id,
|
||||||
"LEAD_REJECTED",
|
"LEAD_REJECTED",
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use axum::{
|
||||||
extract::{Path, Query, State},
|
extract::{Path, Query, State},
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
routing::get,
|
routing::{get, patch},
|
||||||
Json, Router,
|
Json, Router,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -14,6 +14,8 @@ use contracts::auth_middleware::{AuthUser, require_admin};
|
||||||
pub fn router() -> Router<AppState> {
|
pub fn router() -> Router<AppState> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(list_external_roles).post(create_external_role))
|
.route("/", get(list_external_roles).post(create_external_role))
|
||||||
|
.route("/by-key/{role_key}", get(get_external_role_by_key))
|
||||||
|
.route("/by-key/{role_key}", patch(update_external_role_by_key))
|
||||||
.route("/{id}", get(get_external_role).put(update_external_role).delete(delete_external_role))
|
.route("/{id}", get(get_external_role).put(update_external_role).delete(delete_external_role))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -247,6 +249,60 @@ async fn get_external_role(
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_external_role_by_key(
|
||||||
|
auth: AuthUser,
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(role_key): Path<String>,
|
||||||
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
if let Err(_e) = require_admin(&auth) {
|
||||||
|
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
|
||||||
|
}
|
||||||
|
let row = sqlx::query_as::<_, ExternalRoleDetailRow>(
|
||||||
|
r#"
|
||||||
|
SELECT r.id, r.name, r.key as code, r.audience, r.is_active, r.created_at,
|
||||||
|
rc.updated_at as updated_at, rc.config_json as config_json
|
||||||
|
FROM roles r
|
||||||
|
LEFT JOIN role_runtime_configs rc ON rc.role_id = r.id AND rc.is_active = true
|
||||||
|
WHERE r.key = $1 AND r.audience = 'EXTERNAL'
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(&role_key)
|
||||||
|
.fetch_optional(&state.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?
|
||||||
|
.ok_or((StatusCode::NOT_FOUND, "External role not found".to_string()))?;
|
||||||
|
|
||||||
|
Ok(Json(ExternalRoleDetail {
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
code: row.code,
|
||||||
|
audience: row.audience,
|
||||||
|
is_active: row.is_active,
|
||||||
|
runtime: row.config_json.unwrap_or_else(|| serde_json::json!({})),
|
||||||
|
created_at: row.created_at,
|
||||||
|
updated_at: row.updated_at,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_external_role_by_key(
|
||||||
|
auth: AuthUser,
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(role_key): Path<String>,
|
||||||
|
Json(payload): Json<UpdateExternalRolePayload>,
|
||||||
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
if let Err(_e) = require_admin(&auth) {
|
||||||
|
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
|
||||||
|
}
|
||||||
|
let row: (Uuid,) = sqlx::query_as("SELECT id FROM roles WHERE key = $1 AND audience = 'EXTERNAL'")
|
||||||
|
.bind(&role_key)
|
||||||
|
.fetch_optional(&state.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?
|
||||||
|
.ok_or((StatusCode::NOT_FOUND, "External role not found".to_string()))?;
|
||||||
|
|
||||||
|
update_external_role_impl(&state, row.0, payload).await
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct CreateExternalRolePayload {
|
struct CreateExternalRolePayload {
|
||||||
name: String,
|
name: String,
|
||||||
|
|
@ -340,6 +396,14 @@ async fn update_external_role(
|
||||||
if let Err(_e) = require_admin(&auth) {
|
if let Err(_e) = require_admin(&auth) {
|
||||||
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
|
return Err((StatusCode::FORBIDDEN, "Forbidden".to_string()));
|
||||||
}
|
}
|
||||||
|
update_external_role_impl(&state, id, payload).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_external_role_impl(
|
||||||
|
state: &AppState,
|
||||||
|
role_id: Uuid,
|
||||||
|
payload: UpdateExternalRolePayload,
|
||||||
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
if payload.name.is_some() || payload.is_active.is_some() {
|
if payload.name.is_some() || payload.is_active.is_some() {
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
|
|
@ -351,7 +415,7 @@ async fn update_external_role(
|
||||||
)
|
)
|
||||||
.bind(payload.name)
|
.bind(payload.name)
|
||||||
.bind(payload.is_active)
|
.bind(payload.is_active)
|
||||||
.bind(id)
|
.bind(role_id)
|
||||||
.execute(&state.pool)
|
.execute(&state.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||||
|
|
@ -364,7 +428,7 @@ async fn update_external_role(
|
||||||
WHERE role_id = $1 AND is_active = true
|
WHERE role_id = $1 AND is_active = true
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(id)
|
.bind(role_id)
|
||||||
.execute(&state.pool)
|
.execute(&state.pool)
|
||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
|
|
@ -379,13 +443,38 @@ async fn update_external_role(
|
||||||
)
|
)
|
||||||
"#,
|
"#,
|
||||||
)
|
)
|
||||||
.bind(id)
|
.bind(role_id)
|
||||||
.bind(runtime)
|
.bind(runtime)
|
||||||
.execute(&state.pool)
|
.execute(&state.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||||
}
|
}
|
||||||
get_external_role(auth, State(state), Path(id)).await
|
// Return the updated role detail
|
||||||
|
let row = sqlx::query_as::<_, ExternalRoleDetailRow>(
|
||||||
|
r#"
|
||||||
|
SELECT r.id, r.name, r.key as code, r.audience, r.is_active, r.created_at,
|
||||||
|
rc.updated_at as updated_at, rc.config_json as config_json
|
||||||
|
FROM roles r
|
||||||
|
LEFT JOIN role_runtime_configs rc ON rc.role_id = r.id AND rc.is_active = true
|
||||||
|
WHERE r.id = $1 AND r.audience = 'EXTERNAL'
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(role_id)
|
||||||
|
.fetch_optional(&state.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?
|
||||||
|
.ok_or((StatusCode::NOT_FOUND, "External role not found".to_string()))?;
|
||||||
|
|
||||||
|
Ok(Json(ExternalRoleDetail {
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
code: row.code,
|
||||||
|
audience: row.audience,
|
||||||
|
is_active: row.is_active,
|
||||||
|
runtime: row.config_json.unwrap_or_else(|| serde_json::json!({})),
|
||||||
|
created_at: row.created_at,
|
||||||
|
updated_at: row.updated_at,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_external_role(
|
async fn delete_external_role(
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ pub struct PaginationQuery {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct LeadRequestPayload {
|
pub struct LeadRequestPayload {
|
||||||
pub requirement_id: Uuid,
|
pub requirement_id: Uuid,
|
||||||
|
#[serde(default)]
|
||||||
|
pub message: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build the shared Router that every profession service merges into its own Router.
|
/// Build the shared Router that every profession service merges into its own Router.
|
||||||
|
|
@ -189,10 +191,12 @@ async fn send_lead_request(
|
||||||
return (StatusCode::PAYMENT_REQUIRED, "Insufficient Tracecoin balance").into_response();
|
return (StatusCode::PAYMENT_REQUIRED, "Insufficient Tracecoin balance").into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
let db_payload = CreateLeadRequestPayload {
|
let db_payload = CreateLeadRequestPayload::new(
|
||||||
user_role_profile_id: user_role_profile.id,
|
req.id,
|
||||||
expires_at: Utc::now() + chrono::Duration::hours(24),
|
user_role_profile.id,
|
||||||
};
|
auth.user_id,
|
||||||
|
payload.message.clone(),
|
||||||
|
);
|
||||||
|
|
||||||
match LeadRequestRepository::create(&state.pool, db_payload).await {
|
match LeadRequestRepository::create(&state.pool, db_payload).await {
|
||||||
Ok(lead) => {
|
Ok(lead) => {
|
||||||
|
|
@ -456,7 +460,7 @@ async fn cancel_request(
|
||||||
Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e.to_string() }))).into_response(),
|
Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": e.to_string() }))).into_response(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if lead.user_role_profile_id != user_role_profile.id {
|
if lead.user_role_profile_id != Some(user_role_profile.id) {
|
||||||
return (StatusCode::FORBIDDEN, Json(serde_json::json!({ "error": "Access denied" }))).into_response();
|
return (StatusCode::FORBIDDEN, Json(serde_json::json!({ "error": "Access denied" }))).into_response();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue