use db::conn::sea_orm::{ ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set, TransactionTrait, }; use db::user::user_deletion_requests::UserDeletionRequestStatus as DbDeletionStatus; use uuid::Uuid; use crate::error::{UserError, UserResult}; use crate::features::UserService; use jwt::session; use super::types::{DeletionRequestView, RequestDeletionCmd}; pub async fn request( svc: &UserService, caller: Uuid, cmd: RequestDeletionCmd, ) -> UserResult { let writer = svc.db.writer(); let existing = db::user::user_deletion_requests::Entity::find() .filter(db::user::user_deletion_requests::COLUMN.user_id.eq(caller)) .filter( db::user::user_deletion_requests::COLUMN.status.eq(DbDeletionStatus::Pending), ) .one(&writer) .await?; if let Some(row) = existing { return Ok(DeletionRequestView::from(row)); } let now = time::OffsetDateTime::now_utc(); let active: db::user::user_deletion_requests::ActiveModel = db::user::user_deletion_requests::ActiveModel::builder() .set_id(Uuid::new_v4()) .set_user_id(caller) .set_reason(cmd.reason) .set_status(DbDeletionStatus::Pending) .set_scheduled_for(None) .set_completed_at(None) .set_cancelled_at(None) .set_created_at(now) .set_updated_at(now) .into(); let saved = active.insert(&writer).await?; Ok(DeletionRequestView::from(saved)) } pub async fn cancel(svc: &UserService, caller: Uuid) -> UserResult<()> { let writer = svc.db.writer(); let row = db::user::user_deletion_requests::Entity::find() .filter(db::user::user_deletion_requests::COLUMN.user_id.eq(caller)) .filter( db::user::user_deletion_requests::COLUMN.status.is_in([ DbDeletionStatus::Pending, DbDeletionStatus::Scheduled, ]), ) .one(&writer) .await? .ok_or_else(|| UserError::NotFound("no active deletion request".into()))?; let mut am: db::user::user_deletion_requests::ActiveModel = row.into(); am.status = Set(DbDeletionStatus::Cancelled); am.cancelled_at = Set(Some(time::OffsetDateTime::now_utc())); am.updated_at = Set(time::OffsetDateTime::now_utc()); am.update(&writer).await?; Ok(()) } pub async fn confirm(svc: &UserService, caller: Uuid) -> UserResult { let writer = svc.db.writer(); let row = db::user::user_deletion_requests::Entity::find() .filter(db::user::user_deletion_requests::COLUMN.user_id.eq(caller)) .filter( db::user::user_deletion_requests::COLUMN.status.eq(DbDeletionStatus::Pending), ) .one(&writer) .await? .ok_or_else(|| UserError::NotFound("no pending deletion request".into()))?; let now = time::OffsetDateTime::now_utc(); let scheduled_for = now + time::Duration::days(svc.config.deletion_grace_days); let mut am: db::user::user_deletion_requests::ActiveModel = row.into(); am.status = Set(DbDeletionStatus::Scheduled); am.scheduled_for = Set(Some(scheduled_for)); am.updated_at = Set(now); let saved = am.update(&writer).await?; Ok(DeletionRequestView::from(saved)) } pub async fn list_pending(svc: &UserService) -> UserResult> { let db = svc.db.reader(); let rows = db::user::user_deletion_requests::Entity::find() .filter( db::user::user_deletion_requests::COLUMN.status.is_in([ DbDeletionStatus::Pending, DbDeletionStatus::Scheduled, ]), ) .all(&db) .await?; Ok(rows.into_iter().map(DeletionRequestView::from).collect()) } pub async fn complete(svc: &UserService, request_id: Uuid) -> UserResult<()> { let writer = svc.db.writer(); let txn = writer.begin().await?; let row = db::user::user_deletion_requests::Entity::find_by_id(request_id) .one(&txn) .await? .ok_or_else(|| UserError::NotFound(format!("deletion request {request_id}")))?; if !matches!(row.status, DbDeletionStatus::Scheduled) { return Err(UserError::BadRequest("request not in Scheduled state".into())); } let user_id = row.user_id; let mut am: db::user::user_deletion_requests::ActiveModel = row.into(); am.status = Set(DbDeletionStatus::Completed); am.completed_at = Set(Some(time::OffsetDateTime::now_utc())); am.updated_at = Set(time::OffsetDateTime::now_utc()); am.update(&txn).await?; session::delete_sessions_by_user(&txn, user_id) .await .map_err(|e| UserError::Internal(format!("jwt delete_sessions_by_user: {e}")))?; txn.commit().await?; Ok(()) }