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, pub rejection_reason: Option, pub assigned_to: Option, pub created_at: DateTime, pub updated_at: DateTime, } #[derive(Debug, Serialize, Deserialize, FromRow)] pub struct VerificationLog { pub id: Uuid, pub verification_id: Uuid, pub action: String, pub actor_id: Option, pub old_status: Option, pub new_status: Option, pub message: Option, pub created_at: DateTime, } 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 { 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 get_by_id(pool: &PgPool, id: Uuid) -> Result, 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, 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"); // This simplified query string concatenation is for readability, handle properly in prod. // Actually implementing with sqlx properly: 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, notes: Option<&str>, rejection_reason: Option<&str>, ) -> Result { let mut tx = pool.begin().await?; 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_id, action, actor_id, old_status, new_status, message) VALUES ($1, 'STATUS_CHANGE', $2, $3, $4, $5) "# ) .bind(id) .bind(actor_id) .bind(&old.status) .bind(new_status) .bind(notes) .execute(&mut *tx) .await?; tx.commit().await?; Ok(updated) } }