- Auto-verifies emails for accounts ending with @demo.com - Auto-approves COMPANY role for demo accounts - Skips email verification and OTP for demo accounts - Auto-approves profile verification for demo accounts - Allows login without email verification for demo accounts This enables payment gateway companies to login directly and view packages.
189 lines
5.7 KiB
Rust
189 lines
5.7 KiB
Rust
use chrono::{DateTime, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
use sqlx::{FromRow, PgPool};
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
|
pub struct Verification {
|
|
pub id: Uuid,
|
|
pub user_id: Uuid,
|
|
pub role_key: String,
|
|
pub status: String,
|
|
pub priority: String,
|
|
pub case_type: String,
|
|
pub payload: serde_json::Value,
|
|
pub documents: serde_json::Value,
|
|
pub notes: Option<String>,
|
|
pub rejection_reason: Option<String>,
|
|
pub assigned_to: Option<Uuid>,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, FromRow)]
|
|
pub struct VerificationLog {
|
|
pub id: Uuid,
|
|
pub verification_request_id: Uuid,
|
|
pub action: String,
|
|
pub acted_by_user_id: Option<Uuid>,
|
|
pub old_status: Option<String>,
|
|
pub new_status: Option<String>,
|
|
pub remarks: Option<String>,
|
|
pub created_at: DateTime<Utc>,
|
|
}
|
|
|
|
pub struct VerificationRepository;
|
|
|
|
impl VerificationRepository {
|
|
pub async fn create(
|
|
pool: &PgPool,
|
|
user_id: Uuid,
|
|
role_key: &str,
|
|
case_type: &str,
|
|
priority: &str,
|
|
payload: serde_json::Value,
|
|
documents: serde_json::Value,
|
|
) -> Result<Verification, sqlx::Error> {
|
|
sqlx::query_as::<_, Verification>(
|
|
r#"
|
|
INSERT INTO verifications (user_id, role_key, case_type, priority, payload, documents)
|
|
VALUES ($1, $2, $3, $4, $5, $6)
|
|
RETURNING *
|
|
"#
|
|
)
|
|
.bind(user_id)
|
|
.bind(role_key)
|
|
.bind(case_type)
|
|
.bind(priority)
|
|
.bind(payload)
|
|
.bind(documents)
|
|
.fetch_one(pool)
|
|
.await
|
|
}
|
|
|
|
pub async fn create_approved(
|
|
pool: &PgPool,
|
|
user_id: Uuid,
|
|
role_key: &str,
|
|
case_type: &str,
|
|
payload: serde_json::Value,
|
|
documents: serde_json::Value,
|
|
) -> Result<Verification, sqlx::Error> {
|
|
sqlx::query_as::<_, Verification>(
|
|
r#"
|
|
INSERT INTO verifications (user_id, role_key, case_type, priority, status, payload, documents, reviewed_at, reviewer_notes)
|
|
VALUES ($1, $2, $3, 'MEDIUM', 'APPROVED', $4, $5, NOW(), 'Auto-approved for demo account')
|
|
RETURNING *
|
|
"#
|
|
)
|
|
.bind(user_id)
|
|
.bind(role_key)
|
|
.bind(case_type)
|
|
.bind(payload)
|
|
.bind(documents)
|
|
.fetch_one(pool)
|
|
.await
|
|
}
|
|
|
|
pub async fn get_by_id(pool: &PgPool, id: Uuid) -> Result<Option<Verification>, sqlx::Error> {
|
|
sqlx::query_as::<_, Verification>("SELECT * FROM verifications WHERE id = $1")
|
|
.bind(id)
|
|
.fetch_optional(pool)
|
|
.await
|
|
}
|
|
|
|
pub async fn list(
|
|
pool: &PgPool,
|
|
status: Option<&str>,
|
|
case_type: Option<&str>,
|
|
page: i64,
|
|
limit: i64,
|
|
) -> Result<Vec<Verification>, sqlx::Error> {
|
|
let offset = (page - 1) * limit;
|
|
let mut query = "SELECT * FROM verifications WHERE 1=1".to_string();
|
|
|
|
if status.is_some() { query.push_str(" AND status = $1"); }
|
|
if case_type.is_some() { query.push_str(if status.is_some() { " AND case_type = $2" } else { " AND case_type = $1" }); }
|
|
|
|
query.push_str(" ORDER BY created_at DESC LIMIT $3 OFFSET $4");
|
|
|
|
sqlx::query_as::<_, Verification>(
|
|
r#"
|
|
SELECT * FROM verifications
|
|
WHERE ($1::TEXT IS NULL OR status = $1)
|
|
AND ($2::TEXT IS NULL OR case_type = $2)
|
|
ORDER BY
|
|
CASE priority WHEN 'HIGH' THEN 1 WHEN 'MEDIUM' THEN 2 ELSE 3 END,
|
|
created_at DESC
|
|
LIMIT $3 OFFSET $4
|
|
"#
|
|
)
|
|
.bind(status)
|
|
.bind(case_type)
|
|
.bind(limit)
|
|
.bind(offset)
|
|
.fetch_all(pool)
|
|
.await
|
|
}
|
|
|
|
pub async fn update_status(
|
|
pool: &PgPool,
|
|
id: Uuid,
|
|
new_status: &str,
|
|
actor_id: Option<Uuid>,
|
|
notes: Option<&str>,
|
|
rejection_reason: Option<&str>,
|
|
) -> Result<Verification, sqlx::Error> {
|
|
let mut tx = pool.begin().await?;
|
|
|
|
// Validate actor_id exists in users table; if not, treat as NULL
|
|
// This handles cases where the token contains a user_id from an external auth system
|
|
let valid_actor_id = match actor_id {
|
|
Some(uid) => {
|
|
let exists = sqlx::query_scalar::<_, bool>("SELECT EXISTS(SELECT 1 FROM users WHERE id = $1)")
|
|
.bind(uid)
|
|
.fetch_one(&mut *tx)
|
|
.await?;
|
|
if exists { Some(uid) } else { None }
|
|
},
|
|
None => None,
|
|
};
|
|
|
|
let old = sqlx::query_as::<_, Verification>("SELECT * FROM verifications WHERE id = $1 FOR UPDATE")
|
|
.bind(id)
|
|
.fetch_one(&mut *tx)
|
|
.await?;
|
|
|
|
let updated = sqlx::query_as::<_, Verification>(
|
|
r#"
|
|
UPDATE verifications
|
|
SET status = $2, notes = COALESCE($3, notes), rejection_reason = COALESCE($4, rejection_reason), updated_at = NOW()
|
|
WHERE id = $1
|
|
RETURNING *
|
|
"#
|
|
)
|
|
.bind(id)
|
|
.bind(new_status)
|
|
.bind(notes)
|
|
.bind(rejection_reason)
|
|
.fetch_one(&mut *tx)
|
|
.await?;
|
|
|
|
sqlx::query(
|
|
r#"
|
|
INSERT INTO verification_logs (verification_request_id, action, acted_by_user_id, old_status, new_status, remarks)
|
|
VALUES ($1, 'STATUS_CHANGE', $2, $3, $4, $5)
|
|
"#
|
|
)
|
|
.bind(id)
|
|
.bind(valid_actor_id)
|
|
.bind(&old.status)
|
|
.bind(new_status)
|
|
.bind(notes)
|
|
.execute(&mut *tx)
|
|
.await?;
|
|
|
|
tx.commit().await?;
|
|
Ok(updated)
|
|
}
|
|
}
|