mirror of
https://github.com/Traceworks2023/nxtgauge-backend-rust.git
synced 2026-06-11 22:15:25 +00:00
DB:
- Add niche_tags column to ugc_content_creator_profiles (was blocking UGC service)
- Add turnaround_days and fix user_role_profile_id NOT NULL for UGC
- leads/lead_requests tables (already created in session 1)
Code:
- Add UGC_CONTENT_CREATOR to is_professional_role() to auto-create user_role_profiles
- Fix onboarding INSERT to include user_id for photographer_profiles
- Fix send_lead_request_ai to use correct customer_user_id (was self-notifying)
- Add PATCH /api/leads/:id support + mount leads at /api/* for gateway compatibility
- Fix admin_list_cases query (WHERE was using wrong params)
- Fix admin_get_case query (was using list query instead of fetch-by-id)
- Add GET /api/me in profile.rs (moved from onboarding)
- Add KB articles by ID route /api/kb/articles/id/{id}
- Rewrite reviews handlers to match actual reviews table schema
- Add public reviews router GET /api/reviews
Gateway:
- Add /api/reviews route to users service
136 lines
3.8 KiB
Rust
136 lines
3.8 KiB
Rust
use crate::AppState;
|
|
use axum::{
|
|
extract::State,
|
|
http::StatusCode,
|
|
response::IntoResponse,
|
|
routing::{get, post},
|
|
Json, Router,
|
|
};
|
|
use contracts::auth_middleware::AuthUser;
|
|
use db::models::role::RoleRepository;
|
|
use db::models::user_role_profile::UserRoleProfileRepository;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
pub fn router() -> Router<AppState> {
|
|
Router::new()
|
|
.route("/", get(list_my_roles))
|
|
.route("/register", post(register_role))
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct RegisterRolePayload {
|
|
pub role_key: String,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
pub struct UserRoleResponse {
|
|
pub role_key: String,
|
|
pub role_name: String,
|
|
pub status: String,
|
|
pub approved_at: Option<chrono::DateTime<chrono::Utc>>,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn is_professional_role(role_key: &str) -> bool {
|
|
matches!(
|
|
role_key,
|
|
"PHOTOGRAPHER"
|
|
| "MAKEUP_ARTIST"
|
|
| "TUTOR"
|
|
| "DEVELOPER"
|
|
| "VIDEO_EDITOR"
|
|
| "GRAPHIC_DESIGNER"
|
|
| "SOCIAL_MEDIA_MANAGER"
|
|
| "FITNESS_TRAINER"
|
|
| "CATERING_SERVICES"
|
|
| "UGC_CONTENT_CREATOR"
|
|
)
|
|
}
|
|
|
|
#[derive(sqlx::FromRow)]
|
|
struct UserRoleRow {
|
|
key: String,
|
|
name: String,
|
|
status: String,
|
|
approved_at: Option<chrono::DateTime<chrono::Utc>>,
|
|
}
|
|
|
|
async fn list_my_roles(
|
|
auth: AuthUser,
|
|
State(state): State<AppState>,
|
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
|
let rows = sqlx::query_as::<_, UserRoleRow>(
|
|
r#"
|
|
SELECT r.key, r.name, ur.status, ur.approved_at
|
|
FROM user_role_assignments ur
|
|
INNER JOIN roles r ON r.id = ur.role_id
|
|
WHERE ur.user_id = $1
|
|
ORDER BY ur.created_at ASC
|
|
"#,
|
|
)
|
|
.bind(auth.user_id)
|
|
.fetch_all(&state.pool)
|
|
.await
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
|
|
|
let mapped = rows
|
|
.into_iter()
|
|
.map(|r| UserRoleResponse {
|
|
role_key: r.key,
|
|
role_name: r.name,
|
|
status: r.status,
|
|
approved_at: r.approved_at,
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
Ok((StatusCode::OK, Json(mapped)))
|
|
}
|
|
|
|
async fn register_role(
|
|
auth: AuthUser,
|
|
State(state): State<AppState>,
|
|
Json(payload): Json<RegisterRolePayload>,
|
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
|
let role_key = payload.role_key.trim().to_uppercase();
|
|
if role_key.is_empty() {
|
|
return Err((StatusCode::BAD_REQUEST, "role_key is required".to_string()));
|
|
}
|
|
|
|
let role = RoleRepository::get_by_key(&state.pool, &role_key)
|
|
.await
|
|
.map_err(|_| (StatusCode::NOT_FOUND, format!("Role '{}' not found", role_key)))?;
|
|
|
|
sqlx::query(
|
|
r#"
|
|
INSERT INTO user_role_assignments (user_id, role_id, status, approved_at)
|
|
VALUES ($1, $2, 'APPROVED', NOW())
|
|
ON CONFLICT (user_id, role_id)
|
|
DO UPDATE SET status = 'APPROVED', approved_at = NOW()
|
|
"#,
|
|
)
|
|
.bind(auth.user_id)
|
|
.bind(role.id)
|
|
.execute(&state.pool)
|
|
.await
|
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
|
|
|
// For professional/external roles, also create the user_role_profiles entry so
|
|
// downstream services (e.g. leads) can find the professional's profile.
|
|
if is_professional_role(&role_key) {
|
|
if let Err(e) = UserRoleProfileRepository::create(&state.pool, auth.user_id, &role_key).await {
|
|
tracing::warn!("Failed to create user_role_profiles entry for {}: {}", role_key, e);
|
|
// Non-fatal — the assignment is created; the profile row can be backfilled later.
|
|
}
|
|
}
|
|
|
|
Ok((
|
|
StatusCode::OK,
|
|
Json(serde_json::json!({
|
|
"message": "Role registered successfully",
|
|
"role_key": role_key,
|
|
"role_id": role.id,
|
|
"user_id": auth.user_id.to_string(),
|
|
"status": "APPROVED"
|
|
})),
|
|
))
|
|
}
|