85 lines
2.4 KiB
Rust
85 lines
2.4 KiB
Rust
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<DatabaseConnection>,
|
||
|
|
}
|
||
|
|
|
||
|
|
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<OtpCacheRecord, AppError> {
|
||
|
|
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(())
|
||
|
|
}
|
||
|
|
}
|