use crate::auth::domain::otp::{OtpCacheRecord, OtpRepository}; use async_trait::async_trait; use chrono::Utc; use imphnen_entities::seaorm::common::otp_cache::{ ActiveModel as OtpCacheActiveModel, Column as OtpCacheColumn, Entity as OtpCacheEntity, }; use imphnen_utils::AppError; use sea_orm::ActiveValue::Set; use sea_orm::prelude::*; use std::sync::Arc; pub struct PostgresOtpRepository { db: Arc, } impl PostgresOtpRepository { pub fn new(db: DatabaseConnection) -> Self { Self { db: Arc::new(db) } } } #[async_trait] impl OtpRepository for PostgresOtpRepository { async fn save(&self, record: OtpCacheRecord) -> Result<(), AppError> { let now = Utc::now(); let otp_hash = record.otp_hash.clone(); let expires_at = record.expires_at; let active_model = OtpCacheActiveModel { id: Set(uuid::Uuid::new_v4()), email: Set(record.email.clone()), otp_hash: Set(otp_hash.clone()), expires_at: Set(expires_at), created_at: Set(now), updated_at: Set(now), }; // Upsert: replace any existing (possibly expired) OTP for the same email. let existing = OtpCacheEntity::find() .filter(OtpCacheColumn::Email.eq(record.email.clone())) .one(self.db.as_ref()) .await .map_err(|e| AppError::InternalServerError(e.to_string()))?; if let Some(existing) = existing { let mut update: OtpCacheActiveModel = existing.into(); update.otp_hash = Set(otp_hash); update.expires_at = Set(expires_at); update.updated_at = Set(now); update .update(self.db.as_ref()) .await .map_err(|e| AppError::InternalServerError(e.to_string()))?; } else { OtpCacheEntity::insert(active_model) .exec(self.db.as_ref()) .await .map_err(|e| AppError::InternalServerError(e.to_string()))?; } Ok(()) } async fn find_by_email(&self, email: &str) -> Result { let row = OtpCacheEntity::find() .filter(OtpCacheColumn::Email.eq(email)) .one(self.db.as_ref()) .await .map_err(|e| AppError::InternalServerError(e.to_string()))? .ok_or_else(|| AppError::NotFoundError("No OTP issued for this email".into()))?; Ok(OtpCacheRecord { email: row.email, otp_hash: row.otp_hash, expires_at: row.expires_at, }) } async fn delete_by_email(&self, email: &str) -> Result<(), AppError> { OtpCacheEntity::delete_many() .filter(OtpCacheColumn::Email.eq(email)) .exec(self.db.as_ref()) .await .map_err(|e| AppError::InternalServerError(e.to_string()))?; Ok(()) } }