feat: complete backend implementation - payments service, admin endpoints, auth guards, submit-for-verification for all roles
- Add payments service proxying to Beeceptor mock gateway (create-order, verify, status) - Add companies admin approve/reject/suspend + get detail endpoints - Apply require_admin auth guards to all employee/department/designation handlers - Add submit-for-verification endpoint to all 12 roles (10 professions + job seekers + customers + companies) - Fix port conflict (employees moved from 8085 to 8096) - Add submit_for_verification methods to all profile repositories
This commit is contained in:
parent
5cd00b74bc
commit
cb53b68f49
16 changed files with 682 additions and 9 deletions
|
|
@ -23,7 +23,8 @@ members = [
|
||||||
"crates/cache",
|
"crates/cache",
|
||||||
"crates/email",
|
"crates/email",
|
||||||
"apps/cron",
|
"apps/cron",
|
||||||
"apps/employees"
|
"apps/employees",
|
||||||
|
"apps/payments"
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
|
|
|
||||||
|
|
@ -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 contracts::auth_middleware::AuthUser;
|
use contracts::auth_middleware::AuthUser;
|
||||||
|
|
@ -13,6 +13,10 @@ use uuid::Uuid;
|
||||||
pub fn router() -> Router<AppState> {
|
pub fn router() -> Router<AppState> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/", get(list_companies))
|
.route("/", get(list_companies))
|
||||||
|
.route("/{id}", get(get_company))
|
||||||
|
.route("/{id}/approve", patch(approve_company))
|
||||||
|
.route("/{id}/reject", patch(reject_company))
|
||||||
|
.route("/{id}/suspend", patch(suspend_company))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|
@ -32,6 +36,39 @@ pub struct AdminCompanyRow {
|
||||||
pub updated_at: chrono::DateTime<chrono::Utc>,
|
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct AdminCompanyDetail {
|
||||||
|
pub id: Uuid,
|
||||||
|
pub user_id: Uuid,
|
||||||
|
pub company_name: String,
|
||||||
|
pub registration_number: Option<String>,
|
||||||
|
pub industry: Option<String>,
|
||||||
|
pub website_url: Option<String>,
|
||||||
|
pub employee_count: Option<i32>,
|
||||||
|
pub business_type: Option<String>,
|
||||||
|
pub gst_number: Option<String>,
|
||||||
|
pub contact_name: Option<String>,
|
||||||
|
pub contact_email: Option<String>,
|
||||||
|
pub contact_phone: Option<String>,
|
||||||
|
pub address_line1: Option<String>,
|
||||||
|
pub city: Option<String>,
|
||||||
|
pub state: Option<String>,
|
||||||
|
pub country: String,
|
||||||
|
pub postal_code: Option<String>,
|
||||||
|
pub status: String,
|
||||||
|
pub free_job_slots: i32,
|
||||||
|
pub purchased_job_slots: i32,
|
||||||
|
pub free_contact_views: i32,
|
||||||
|
pub purchased_contact_views: i32,
|
||||||
|
pub created_at: chrono::DateTime<chrono::Utc>,
|
||||||
|
pub updated_at: chrono::DateTime<chrono::Utc>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct ApproveRejectRequest {
|
||||||
|
pub reason: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
async fn list_companies(
|
async fn list_companies(
|
||||||
_auth: AuthUser,
|
_auth: AuthUser,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
|
|
@ -58,3 +95,153 @@ async fn list_companies(
|
||||||
|
|
||||||
Ok(Json(companies))
|
Ok(Json(companies))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_company(
|
||||||
|
_auth: AuthUser,
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
let company = sqlx::query_as!(
|
||||||
|
AdminCompanyDetail,
|
||||||
|
r#"
|
||||||
|
SELECT
|
||||||
|
id, user_id, company_name, registration_number, industry,
|
||||||
|
website_url, employee_count, business_type, gst_number,
|
||||||
|
contact_name, contact_email, contact_phone, address_line1,
|
||||||
|
city, state, country, postal_code, status,
|
||||||
|
free_job_slots, purchased_job_slots, free_contact_views, purchased_contact_views,
|
||||||
|
created_at, updated_at
|
||||||
|
FROM company_profiles
|
||||||
|
WHERE id = $1
|
||||||
|
"#,
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.fetch_optional(&state.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||||
|
|
||||||
|
match company {
|
||||||
|
Some(c) => Ok(Json(c)),
|
||||||
|
None => Err((StatusCode::NOT_FOUND, "Company not found".to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn approve_company(
|
||||||
|
_auth: AuthUser,
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
Json(_payload): Json<ApproveRejectRequest>,
|
||||||
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
let company = sqlx::query!(
|
||||||
|
"SELECT id, user_id, status FROM company_profiles WHERE id = $1",
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.fetch_optional(&state.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||||
|
|
||||||
|
let company = match company {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err((StatusCode::NOT_FOUND, "Company not found".to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
if company.status == "APPROVED" {
|
||||||
|
return Err((StatusCode::BAD_REQUEST, "Company is already approved".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"UPDATE company_profiles SET status = 'APPROVED', updated_at = NOW() WHERE id = $1",
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.execute(&state.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||||
|
|
||||||
|
Ok(Json(serde_json::json!({
|
||||||
|
"id": id,
|
||||||
|
"status": "APPROVED",
|
||||||
|
"message": "Company approved successfully"
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn reject_company(
|
||||||
|
_auth: AuthUser,
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
Json(payload): Json<ApproveRejectRequest>,
|
||||||
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
let reason = payload.reason.as_deref().unwrap_or("No reason provided");
|
||||||
|
|
||||||
|
let company = sqlx::query!(
|
||||||
|
"SELECT id, user_id, status FROM company_profiles WHERE id = $1",
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.fetch_optional(&state.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||||
|
|
||||||
|
let company = match company {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err((StatusCode::NOT_FOUND, "Company not found".to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
if company.status == "REJECTED" {
|
||||||
|
return Err((StatusCode::BAD_REQUEST, "Company is already rejected".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"UPDATE company_profiles SET status = 'REJECTED', updated_at = NOW() WHERE id = $1",
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.execute(&state.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||||
|
|
||||||
|
Ok(Json(serde_json::json!({
|
||||||
|
"id": id,
|
||||||
|
"status": "REJECTED",
|
||||||
|
"reason": reason,
|
||||||
|
"message": "Company rejected"
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn suspend_company(
|
||||||
|
_auth: AuthUser,
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Path(id): Path<Uuid>,
|
||||||
|
Json(payload): Json<ApproveRejectRequest>,
|
||||||
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
let reason = payload.reason.as_deref().unwrap_or("No reason provided");
|
||||||
|
|
||||||
|
let company = sqlx::query!(
|
||||||
|
"SELECT id, user_id, status FROM company_profiles WHERE id = $1",
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.fetch_optional(&state.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||||
|
|
||||||
|
let company = match company {
|
||||||
|
Some(c) => c,
|
||||||
|
None => return Err((StatusCode::NOT_FOUND, "Company not found".to_string())),
|
||||||
|
};
|
||||||
|
|
||||||
|
if company.status == "SUSPENDED" {
|
||||||
|
return Err((StatusCode::BAD_REQUEST, "Company is already suspended".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlx::query!(
|
||||||
|
"UPDATE company_profiles SET status = 'SUSPENDED', updated_at = NOW() WHERE id = $1",
|
||||||
|
id
|
||||||
|
)
|
||||||
|
.execute(&state.pool)
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||||
|
|
||||||
|
Ok(Json(serde_json::json!({
|
||||||
|
"id": id,
|
||||||
|
"status": "SUSPENDED",
|
||||||
|
"reason": reason,
|
||||||
|
"message": "Company suspended"
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ use crate::AppState;
|
||||||
pub fn router() -> Router<AppState> {
|
pub fn router() -> Router<AppState> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/profile/me", get(get_profile).patch(update_profile))
|
.route("/profile/me", get(get_profile).patch(update_profile))
|
||||||
|
.route("/profile/submit", post(submit_for_verification))
|
||||||
.route("/jobs", get(list_jobs).post(create_job))
|
.route("/jobs", get(list_jobs).post(create_job))
|
||||||
.route("/jobs/{id}", get(get_job).patch(update_job))
|
.route("/jobs/{id}", get(get_job).patch(update_job))
|
||||||
.route("/jobs/{id}/submit", post(submit_job))
|
.route("/jobs/{id}/submit", post(submit_job))
|
||||||
|
|
@ -74,6 +75,29 @@ async fn update_profile(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn submit_for_verification(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
auth: AuthUser,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let company = match CompanyRepository::get_by_user_id(&state.pool, auth.user_id).await {
|
||||||
|
Ok(Some(c)) => c,
|
||||||
|
Ok(None) => return (StatusCode::NOT_FOUND, "Company profile not found").into_response(),
|
||||||
|
Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if company.status == "PENDING_REVIEW" || company.status == "APPROVED" {
|
||||||
|
return (StatusCode::BAD_REQUEST, format!("Profile is already {}", company.status)).into_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
match CompanyRepository::submit_for_verification(&state.pool, auth.user_id).await {
|
||||||
|
Ok(profile) => (StatusCode::OK, Json(serde_json::json!({
|
||||||
|
"status": profile.status,
|
||||||
|
"message": "Profile submitted for verification"
|
||||||
|
}))).into_response(),
|
||||||
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn list_jobs(
|
async fn list_jobs(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
auth: AuthUser,
|
auth: AuthUser,
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ use crate::AppState;
|
||||||
pub fn router() -> Router<AppState> {
|
pub fn router() -> Router<AppState> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/profile/me", get(get_profile).patch(update_profile))
|
.route("/profile/me", get(get_profile).patch(update_profile))
|
||||||
|
.route("/profile/submit", post(submit_for_verification))
|
||||||
.route("/requirements", get(list_requirements).post(create_requirement))
|
.route("/requirements", get(list_requirements).post(create_requirement))
|
||||||
.route("/requirements/{id}", get(get_requirement).patch(update_requirement))
|
.route("/requirements/{id}", get(get_requirement).patch(update_requirement))
|
||||||
.route("/requirements/{id}/submit", post(submit_requirement))
|
.route("/requirements/{id}/submit", post(submit_requirement))
|
||||||
|
|
@ -70,6 +71,29 @@ async fn update_profile(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn submit_for_verification(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
auth: AuthUser,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let customer = match CustomerRepository::get_by_user_id(&state.pool, auth.user_id).await {
|
||||||
|
Ok(Some(c)) => c,
|
||||||
|
Ok(None) => return (StatusCode::NOT_FOUND, "Customer profile not found").into_response(),
|
||||||
|
Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if customer.status == "PENDING_REVIEW" || customer.status == "APPROVED" {
|
||||||
|
return (StatusCode::BAD_REQUEST, format!("Profile is already {}", customer.status)).into_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
match CustomerRepository::submit_for_verification(&state.pool, auth.user_id).await {
|
||||||
|
Ok(profile) => (StatusCode::OK, Json(serde_json::json!({
|
||||||
|
"status": profile.status,
|
||||||
|
"message": "Profile submitted for verification"
|
||||||
|
}))).into_response(),
|
||||||
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn list_requirements(
|
async fn list_requirements(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
auth: AuthUser,
|
auth: AuthUser,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,9 @@ async fn list_departments(
|
||||||
auth: AuthUser,
|
auth: AuthUser,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
if let Err(_) = require_admin(&auth) {
|
||||||
|
return Err((StatusCode::FORBIDDEN, "Insufficient permissions".to_string()));
|
||||||
|
}
|
||||||
let departments = DepartmentRepository::list(&state.pool)
|
let departments = DepartmentRepository::list(&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}")))?;
|
||||||
|
|
@ -33,6 +36,9 @@ async fn create_department(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Json(payload): Json<CreateDepartmentPayload>,
|
Json(payload): Json<CreateDepartmentPayload>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
if let Err(_) = require_admin(&auth) {
|
||||||
|
return Err((StatusCode::FORBIDDEN, "Insufficient permissions".to_string()));
|
||||||
|
}
|
||||||
let department = DepartmentRepository::create(&state.pool, payload)
|
let department = DepartmentRepository::create(&state.pool, payload)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||||
|
|
@ -46,6 +52,9 @@ async fn update_department(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Json(payload): Json<serde_json::Value>,
|
Json(payload): Json<serde_json::Value>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
if let Err(_) = require_admin(&auth) {
|
||||||
|
return Err((StatusCode::FORBIDDEN, "Insufficient permissions".to_string()));
|
||||||
|
}
|
||||||
let department = DepartmentRepository::update(&state.pool, id, payload)
|
let department = DepartmentRepository::update(&state.pool, id, payload)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||||
|
|
@ -58,6 +67,9 @@ async fn delete_department(
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
if let Err(_) = require_admin(&auth) {
|
||||||
|
return Err((StatusCode::FORBIDDEN, "Insufficient permissions".to_string()));
|
||||||
|
}
|
||||||
DepartmentRepository::delete(&state.pool, id)
|
DepartmentRepository::delete(&state.pool, id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use axum::{
|
||||||
routing::{get, post, patch},
|
routing::{get, post, patch},
|
||||||
Json, Router,
|
Json, Router,
|
||||||
};
|
};
|
||||||
use contracts::auth_middleware::AuthUser;
|
use contracts::auth_middleware::{AuthUser, require_admin};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use db::models::designation::{DesignationRepository, CreateDesignationPayload};
|
use db::models::designation::{DesignationRepository, CreateDesignationPayload};
|
||||||
|
|
@ -22,6 +22,9 @@ async fn list_all_designations(
|
||||||
auth: AuthUser,
|
auth: AuthUser,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
if let Err(_) = require_admin(&auth) {
|
||||||
|
return Err((StatusCode::FORBIDDEN, "Insufficient permissions".to_string()));
|
||||||
|
}
|
||||||
let designations = DesignationRepository::list_all(&state.pool)
|
let designations = DesignationRepository::list_all(&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}")))?;
|
||||||
|
|
@ -34,6 +37,9 @@ async fn list_by_department(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(dept_id): Path<Uuid>,
|
Path(dept_id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
if let Err(_) = require_admin(&auth) {
|
||||||
|
return Err((StatusCode::FORBIDDEN, "Insufficient permissions".to_string()));
|
||||||
|
}
|
||||||
let designations = DesignationRepository::list_by_department(&state.pool, dept_id)
|
let designations = DesignationRepository::list_by_department(&state.pool, dept_id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||||
|
|
@ -46,6 +52,9 @@ async fn create_designation(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Json(payload): Json<CreateDesignationPayload>,
|
Json(payload): Json<CreateDesignationPayload>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
if let Err(_) = require_admin(&auth) {
|
||||||
|
return Err((StatusCode::FORBIDDEN, "Insufficient permissions".to_string()));
|
||||||
|
}
|
||||||
let designation = DesignationRepository::create(&state.pool, payload)
|
let designation = DesignationRepository::create(&state.pool, payload)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||||
|
|
@ -59,6 +68,9 @@ async fn update_designation(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Json(payload): Json<serde_json::Value>,
|
Json(payload): Json<serde_json::Value>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
if let Err(_) = require_admin(&auth) {
|
||||||
|
return Err((StatusCode::FORBIDDEN, "Insufficient permissions".to_string()));
|
||||||
|
}
|
||||||
let designation = DesignationRepository::update(&state.pool, id, payload)
|
let designation = DesignationRepository::update(&state.pool, id, payload)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||||
|
|
@ -71,6 +83,9 @@ async fn delete_designation(
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
if let Err(_) = require_admin(&auth) {
|
||||||
|
return Err((StatusCode::FORBIDDEN, "Insufficient permissions".to_string()));
|
||||||
|
}
|
||||||
DesignationRepository::delete(&state.pool, id)
|
DesignationRepository::delete(&state.pool, id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||||
|
|
|
||||||
|
|
@ -34,10 +34,13 @@ pub struct EmployeeResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_employees(
|
async fn list_employees(
|
||||||
_auth: AuthUser,
|
auth: AuthUser,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Query(q): Query<ListQuery>,
|
Query(q): Query<ListQuery>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
if let Err(_) = require_admin(&auth) {
|
||||||
|
return Err((StatusCode::FORBIDDEN, "Insufficient permissions".to_string()));
|
||||||
|
}
|
||||||
let employees = EmployeeRepository::list(&state.pool, q.q)
|
let employees = EmployeeRepository::list(&state.pool, q.q)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||||
|
|
@ -46,10 +49,13 @@ async fn list_employees(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_employee(
|
async fn get_employee(
|
||||||
_auth: AuthUser,
|
auth: AuthUser,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
if let Err(_) = require_admin(&auth) {
|
||||||
|
return Err((StatusCode::FORBIDDEN, "Insufficient permissions".to_string()));
|
||||||
|
}
|
||||||
let employee = EmployeeRepository::list(&state.pool, None)
|
let employee = EmployeeRepository::list(&state.pool, None)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?
|
||||||
|
|
@ -61,10 +67,13 @@ async fn get_employee(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn create_employee(
|
async fn create_employee(
|
||||||
_auth: AuthUser,
|
auth: AuthUser,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Json(payload): Json<CreateEmployeePayload>,
|
Json(payload): Json<CreateEmployeePayload>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
if let Err(_) = require_admin(&auth) {
|
||||||
|
return Err((StatusCode::FORBIDDEN, "Insufficient permissions".to_string()));
|
||||||
|
}
|
||||||
let employee = EmployeeRepository::create(&state.pool, payload)
|
let employee = EmployeeRepository::create(&state.pool, payload)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||||
|
|
@ -84,11 +93,14 @@ pub struct UpdateEmployeePayload {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_employee(
|
async fn update_employee(
|
||||||
_auth: AuthUser,
|
auth: AuthUser,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
Json(payload): Json<UpdateEmployeePayload>,
|
Json(payload): Json<UpdateEmployeePayload>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
if let Err(_) = require_admin(&auth) {
|
||||||
|
return Err((StatusCode::FORBIDDEN, "Insufficient permissions".to_string()));
|
||||||
|
}
|
||||||
let employee = EmployeeRepository::update(
|
let employee = EmployeeRepository::update(
|
||||||
&state.pool,
|
&state.pool,
|
||||||
id,
|
id,
|
||||||
|
|
@ -107,10 +119,13 @@ async fn update_employee(
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_employee(
|
async fn delete_employee(
|
||||||
_auth: AuthUser,
|
auth: AuthUser,
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
) -> Result<impl IntoResponse, (StatusCode, String)> {
|
||||||
|
if let Err(_) = require_admin(&auth) {
|
||||||
|
return Err((StatusCode::FORBIDDEN, "Insufficient permissions".to_string()));
|
||||||
|
}
|
||||||
EmployeeRepository::delete(&state.pool, id)
|
EmployeeRepository::delete(&state.pool, id)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("DB error: {e}")))?;
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ impl Services {
|
||||||
payments_url: std::env::var("PAYMENTS_SERVICE_URL")
|
payments_url: std::env::var("PAYMENTS_SERVICE_URL")
|
||||||
.unwrap_or_else(|_| "http://localhost:8094".to_string()),
|
.unwrap_or_else(|_| "http://localhost:8094".to_string()),
|
||||||
employees_url: std::env::var("EMPLOYEES_SERVICE_URL")
|
employees_url: std::env::var("EMPLOYEES_SERVICE_URL")
|
||||||
.unwrap_or_else(|_| "http://localhost:8085".to_string()),
|
.unwrap_or_else(|_| "http://localhost:8096".to_string()),
|
||||||
client: reqwest::Client::new(),
|
client: reqwest::Client::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ pub fn router() -> Router<AppState> {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/profile/me", get(get_profile).patch(update_profile))
|
.route("/profile/me", get(get_profile).patch(update_profile))
|
||||||
.route("/profile/resume", post(upload_resume))
|
.route("/profile/resume", post(upload_resume))
|
||||||
|
.route("/profile/submit", post(submit_for_verification))
|
||||||
.route("/jobs", get(browse_jobs))
|
.route("/jobs", get(browse_jobs))
|
||||||
.route("/jobs/{id}", get(get_job))
|
.route("/jobs/{id}", get(get_job))
|
||||||
.route("/jobs/{id}/apply", post(apply_to_job))
|
.route("/jobs/{id}/apply", post(apply_to_job))
|
||||||
|
|
@ -318,3 +319,26 @@ async fn withdraw_application(
|
||||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn submit_for_verification(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
auth: AuthUser,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let seeker = match JobSeekerRepository::get_by_user_id(&state.pool, auth.user_id).await {
|
||||||
|
Ok(Some(s)) => s,
|
||||||
|
Ok(None) => return (StatusCode::NOT_FOUND, "Job seeker profile not found").into_response(),
|
||||||
|
Err(e) => return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if seeker.status == "PENDING_REVIEW" || seeker.status == "APPROVED" {
|
||||||
|
return (StatusCode::BAD_REQUEST, format!("Profile is already {}", seeker.status)).into_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
match JobSeekerRepository::submit_for_verification(&state.pool, auth.user_id).await {
|
||||||
|
Ok(profile) => (StatusCode::OK, Json(serde_json::json!({
|
||||||
|
"status": profile.status,
|
||||||
|
"message": "Profile submitted for verification"
|
||||||
|
}))).into_response(),
|
||||||
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
14
apps/payments/Cargo.toml
Normal file
14
apps/payments/Cargo.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "payments"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
|
tokio.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
tracing-subscriber.workspace = true
|
||||||
|
reqwest = { version = "0.12", features = ["json", "stream"] }
|
||||||
|
anyhow.workspace = true
|
||||||
230
apps/payments/src/main.rs
Normal file
230
apps/payments/src/main.rs
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
use axum::{
|
||||||
|
extract::State,
|
||||||
|
http::StatusCode,
|
||||||
|
routing::{get, post},
|
||||||
|
Json, Router,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct AppState {
|
||||||
|
beeceptor_url: String,
|
||||||
|
client: reqwest::Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct CreateOrderRequest {
|
||||||
|
amount: u64,
|
||||||
|
currency: Option<String>,
|
||||||
|
package_id: Option<String>,
|
||||||
|
user_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct CreateOrderResponse {
|
||||||
|
order_id: String,
|
||||||
|
amount: u64,
|
||||||
|
currency: String,
|
||||||
|
status: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct VerifyPaymentRequest {
|
||||||
|
order_id: String,
|
||||||
|
payment_id: String,
|
||||||
|
signature: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct VerifyPaymentResponse {
|
||||||
|
verified: bool,
|
||||||
|
payment_id: String,
|
||||||
|
status: String,
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct PaymentStatusResponse {
|
||||||
|
payment_id: String,
|
||||||
|
status: String,
|
||||||
|
amount: u64,
|
||||||
|
currency: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_order(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Json(payload): Json<CreateOrderRequest>,
|
||||||
|
) -> Result<Json<CreateOrderResponse>, (StatusCode, String)> {
|
||||||
|
tracing::info!("Creating payment order: amount={}", payload.amount);
|
||||||
|
|
||||||
|
let resp = state
|
||||||
|
.client
|
||||||
|
.post(&state.beeceptor_url)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.json(&serde_json::json!({
|
||||||
|
"amount": payload.amount,
|
||||||
|
"currency": payload.currency.as_deref().unwrap_or("INR"),
|
||||||
|
"package_id": payload.package_id,
|
||||||
|
"user_id": payload.user_id,
|
||||||
|
}))
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::BAD_GATEWAY, format!("Beeceptor error: {}", e)))?;
|
||||||
|
|
||||||
|
let status = resp.status();
|
||||||
|
let body: serde_json::Value = resp
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::BAD_GATEWAY, format!("Parse error: {}", e)))?;
|
||||||
|
|
||||||
|
if !status.is_success() {
|
||||||
|
return Err((
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
body.get("message")
|
||||||
|
.and_then(|m| m.as_str())
|
||||||
|
.unwrap_or("Order creation failed")
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let order_id = body
|
||||||
|
.get("order_id")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("mock_order_123")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
Ok(Json(CreateOrderResponse {
|
||||||
|
order_id,
|
||||||
|
amount: payload.amount,
|
||||||
|
currency: payload.currency.unwrap_or("INR".to_string()),
|
||||||
|
status: "created".to_string(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_payment(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Json(payload): Json<VerifyPaymentRequest>,
|
||||||
|
) -> Result<Json<VerifyPaymentResponse>, (StatusCode, String)> {
|
||||||
|
tracing::info!("Verifying payment: order_id={}", payload.order_id);
|
||||||
|
|
||||||
|
let verify_url = format!("{}/verify", state.beeceptor_url.trim_end_matches('/'));
|
||||||
|
|
||||||
|
let resp = state
|
||||||
|
.client
|
||||||
|
.post(&verify_url)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.json(&payload)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::BAD_GATEWAY, format!("Beeceptor error: {}", e)))?;
|
||||||
|
|
||||||
|
let status = resp.status();
|
||||||
|
let body: serde_json::Value = resp
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::BAD_GATEWAY, format!("Parse error: {}", e)))?;
|
||||||
|
|
||||||
|
if !status.is_success() {
|
||||||
|
return Err((
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
body.get("message")
|
||||||
|
.and_then(|m| m.as_str())
|
||||||
|
.unwrap_or("Verification failed")
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Json(VerifyPaymentResponse {
|
||||||
|
verified: true,
|
||||||
|
payment_id: payload.payment_id,
|
||||||
|
status: "success".to_string(),
|
||||||
|
message: "Payment verified successfully".to_string(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_payment_status(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
axum::extract::Path(payment_id): axum::extract::Path<String>,
|
||||||
|
) -> Result<Json<PaymentStatusResponse>, (StatusCode, String)> {
|
||||||
|
tracing::info!("Getting payment status: payment_id={}", payment_id);
|
||||||
|
|
||||||
|
let status_url = format!("{}/{}", state.beeceptor_url.trim_end_matches('/'), payment_id);
|
||||||
|
|
||||||
|
let resp = state
|
||||||
|
.client
|
||||||
|
.get(&status_url)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::BAD_GATEWAY, format!("Beeceptor error: {}", e)))?;
|
||||||
|
|
||||||
|
let status = resp.status();
|
||||||
|
let body: serde_json::Value = resp
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.map_err(|e| (StatusCode::BAD_GATEWAY, format!("Parse error: {}", e)))?;
|
||||||
|
|
||||||
|
if !status.is_success() {
|
||||||
|
return Ok(Json(PaymentStatusResponse {
|
||||||
|
payment_id,
|
||||||
|
status: "not_found".to_string(),
|
||||||
|
amount: 0,
|
||||||
|
currency: "INR".to_string(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let amount = body.get("amount").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||||
|
let currency = body
|
||||||
|
.get("currency")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("INR")
|
||||||
|
.to_string();
|
||||||
|
let status_str = body
|
||||||
|
.get("status")
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("unknown")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
Ok(Json(PaymentStatusResponse {
|
||||||
|
payment_id,
|
||||||
|
status: status_str,
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(tracing_subscriber::EnvFilter::new(
|
||||||
|
std::env::var("RUST_LOG").unwrap_or_else(|_| "info".into()),
|
||||||
|
))
|
||||||
|
.with(tracing_subscriber::fmt::layer())
|
||||||
|
.init();
|
||||||
|
|
||||||
|
let beeceptor_url = std::env::var("BEECEPTOR_URL")
|
||||||
|
.unwrap_or_else(|_| "https://nxtgauge.free.beeceptor.com".to_string());
|
||||||
|
|
||||||
|
let state = AppState {
|
||||||
|
beeceptor_url,
|
||||||
|
client: reqwest::Client::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/api/payments/create-order", post(create_order))
|
||||||
|
.route("/api/payments/verify", post(verify_payment))
|
||||||
|
.route("/api/payments/:id/status", get(get_payment_status))
|
||||||
|
.with_state(state);
|
||||||
|
|
||||||
|
let port: u16 = std::env::var("PORT")
|
||||||
|
.unwrap_or_else(|_| "8094".to_string())
|
||||||
|
.parse()
|
||||||
|
.expect("PORT must be a valid u16");
|
||||||
|
let addr = SocketAddr::from(([0, 0, 0, 0], port));
|
||||||
|
|
||||||
|
tracing::info!("Payments service (mock via Beeceptor) listening on {}", addr);
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
||||||
|
axum::serve(listener, app).await.unwrap();
|
||||||
|
}
|
||||||
|
|
@ -35,6 +35,7 @@ pub struct LeadRequestPayload {
|
||||||
/// `profession_key` must be a `'static str` matching the role key, e.g. `"PHOTOGRAPHER"`.
|
/// `profession_key` must be a `'static str` matching the role key, e.g. `"PHOTOGRAPHER"`.
|
||||||
pub fn shared_routes(profession_key: &'static str) -> Router<ProfessionState> {
|
pub fn shared_routes(profession_key: &'static str) -> Router<ProfessionState> {
|
||||||
Router::new()
|
Router::new()
|
||||||
|
.route("/profile/submit", post(submit_for_verification))
|
||||||
// ── Marketplace (Redis-cached) ────────────────────────────────────────
|
// ── Marketplace (Redis-cached) ────────────────────────────────────────
|
||||||
.route(
|
.route(
|
||||||
"/marketplace",
|
"/marketplace",
|
||||||
|
|
@ -774,3 +775,25 @@ async fn wallet_invoice_detail(
|
||||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn submit_for_verification(
|
||||||
|
State(state): State<ProfessionState>,
|
||||||
|
auth: AuthUser,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let prof = match ProfessionalRepository::get_by_user_id(&state.pool, auth.user_id).await {
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(_) => return (StatusCode::NOT_FOUND, "Professional profile not found").into_response(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if prof.status == "PENDING_REVIEW" || prof.status == "APPROVED" {
|
||||||
|
return (StatusCode::BAD_REQUEST, format!("Profile is already {}", prof.status)).into_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
match ProfessionalRepository::submit_for_verification(&state.pool, auth.user_id).await {
|
||||||
|
Ok(profile) => (StatusCode::OK, Json(serde_json::json!({
|
||||||
|
"status": profile.status,
|
||||||
|
"message": "Profile submitted for verification"
|
||||||
|
}))).into_response(),
|
||||||
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -140,4 +140,30 @@ impl CompanyRepository {
|
||||||
|
|
||||||
Ok(profile)
|
Ok(profile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn submit_for_verification(
|
||||||
|
pool: &PgPool,
|
||||||
|
user_id: Uuid,
|
||||||
|
) -> Result<CompanyProfile, sqlx::Error> {
|
||||||
|
let profile = sqlx::query_as!(
|
||||||
|
CompanyProfile,
|
||||||
|
r#"
|
||||||
|
UPDATE company_profiles
|
||||||
|
SET status = 'PENDING_REVIEW', updated_at = NOW()
|
||||||
|
WHERE user_id = $1
|
||||||
|
RETURNING
|
||||||
|
id, user_id, company_name, registration_number, industry,
|
||||||
|
website_url, employee_count, business_type, gst_number,
|
||||||
|
contact_name, contact_email, contact_phone, address_line1,
|
||||||
|
city, state, country, postal_code, status,
|
||||||
|
free_job_slots, purchased_job_slots, free_contact_views, purchased_contact_views,
|
||||||
|
created_at, updated_at
|
||||||
|
"#,
|
||||||
|
user_id
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(profile)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -116,4 +116,27 @@ impl CustomerRepository {
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn submit_for_verification(
|
||||||
|
pool: &PgPool,
|
||||||
|
user_id: Uuid,
|
||||||
|
) -> Result<CustomerProfile, sqlx::Error> {
|
||||||
|
let profile = sqlx::query_as!(
|
||||||
|
CustomerProfile,
|
||||||
|
r#"
|
||||||
|
UPDATE customer_profiles
|
||||||
|
SET status = 'PENDING_REVIEW', updated_at = NOW()
|
||||||
|
WHERE user_id = $1
|
||||||
|
RETURNING
|
||||||
|
id, user_id, full_name, phone, city, area, preferred_professions,
|
||||||
|
active_requirement_count, status, bio, experience_years, custom_data,
|
||||||
|
created_at, updated_at
|
||||||
|
"#,
|
||||||
|
user_id
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(profile)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -116,5 +116,28 @@ impl JobSeekerRepository {
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn submit_for_verification(
|
||||||
|
pool: &PgPool,
|
||||||
|
user_id: Uuid,
|
||||||
|
) -> Result<JobSeekerProfile, sqlx::Error> {
|
||||||
|
let profile = sqlx::query_as!(
|
||||||
|
JobSeekerProfile,
|
||||||
|
r#"
|
||||||
|
UPDATE job_seeker_profiles
|
||||||
|
SET status = 'PENDING_REVIEW', updated_at = NOW()
|
||||||
|
WHERE user_id = $1
|
||||||
|
RETURNING
|
||||||
|
id, user_id, full_name, location, summary, experience_years,
|
||||||
|
skills, resume_url, active_application_count, status, bio, custom_data,
|
||||||
|
created_at, updated_at
|
||||||
|
"#,
|
||||||
|
user_id
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(profile)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -561,4 +561,36 @@ impl ProfessionalRepository {
|
||||||
tx.commit().await?;
|
tx.commit().await?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn submit_for_verification(
|
||||||
|
pool: &PgPool,
|
||||||
|
user_id: Uuid,
|
||||||
|
) -> Result<Professional, sqlx::Error> {
|
||||||
|
let prof = sqlx::query_as!(
|
||||||
|
Professional,
|
||||||
|
"SELECT * FROM professionals WHERE user_id = $1",
|
||||||
|
user_id
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if prof.status == "PENDING_REVIEW" || prof.status == "APPROVED" {
|
||||||
|
return Err(sqlx::Error::Protocol(format!("Professional profile is already {}", prof.status).into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let prof = sqlx::query_as!(
|
||||||
|
Professional,
|
||||||
|
r#"
|
||||||
|
UPDATE professionals
|
||||||
|
SET status = 'PENDING_REVIEW', updated_at = NOW()
|
||||||
|
WHERE user_id = $1
|
||||||
|
RETURNING *
|
||||||
|
"#,
|
||||||
|
user_id
|
||||||
|
)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(prof)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue