feat: v0.3.0 — standardize codebase, centralize infra, merge QR into CMS
- Enforce axum best practices across all 13 workspace crates (max 200 LOC/file, no comments, no unwrap, clean architecture) - Fix domain→infrastructure dependency inversions in imphnen-iam and imphnen-dimentorin - Extract imphnen-storage (MinIO) and imphnen-email (Lettre) as standalone crates - Centralize all config in ENV struct: CDN_URL, CORS_ALLOWED_ORIGINS - Centralize SMTP through imphnen-email; remove dead HackathonConfig - Centralize database: QR crate now shares main DB pool (single DATABASE_URL) - Rename QR users table to qr_users to avoid collision with main users table - Merge imphnen-qr into imphnen-cms/src/qr (13 crates, down from 14) - Restructure imphnen-hackathon flat modules into clean architecture - Remove all stale env vars from .env.example (SurrealDB, QR_JWT, Hackathon infra) - Fix Dockerfile to include all current workspace crates - Bump all crate versions 0.2.0 → 0.3.0 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
2ae43b3bcc
commit
331a4a4e88
@@ -1,306 +1,192 @@
|
||||
#![allow(clippy::field_reassign_with_default)]
|
||||
use std::sync::Arc;
|
||||
use async_trait::async_trait;
|
||||
use sea_orm::prelude::*;
|
||||
use sea_orm::{ActiveValue, Order, QueryOrder, PaginatorTrait};
|
||||
use paginator_rs::{PaginationParams, SortDirection};
|
||||
use paginator_utils::{PaginatorResponse, PaginatorResponseMeta};
|
||||
use uuid::Uuid;
|
||||
use chrono::Utc;
|
||||
use imphnen_utils::AppError;
|
||||
use imphnen_entities::{
|
||||
UsersDetailQueryDto, RolesDetailQueryDto, PermissionsQueryDto,
|
||||
seaorm::auth::users::{Entity as UsersEntity, ActiveModel as UserActiveModel, Column as UserColumn},
|
||||
seaorm::auth::roles::Entity as RolesEntity,
|
||||
use super::postgres_user_queries::{
|
||||
build_role_dto, build_user_dto, query_user_list,
|
||||
};
|
||||
use crate::users::domain::{UserEntity, UserListItem, UserRepository};
|
||||
use async_trait::async_trait;
|
||||
use chrono::Utc;
|
||||
use imphnen_entities::{
|
||||
UsersDetailQueryDto,
|
||||
seaorm::auth::roles::Entity as RolesEntity,
|
||||
seaorm::auth::users::{
|
||||
ActiveModel as UserActiveModel, Column as UserColumn, Entity as UsersEntity,
|
||||
},
|
||||
};
|
||||
use imphnen_utils::AppError;
|
||||
use paginator_rs::PaginationParams;
|
||||
use paginator_utils::PaginatorResponse;
|
||||
use sea_orm::ActiveValue;
|
||||
use sea_orm::prelude::*;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
fn user_detail_to_entity(dto: UsersDetailQueryDto) -> UserEntity {
|
||||
UserEntity {
|
||||
id: dto.id,
|
||||
email: dto.email,
|
||||
fullname: dto.fullname,
|
||||
legal_name: dto.legal_name,
|
||||
password: dto.password,
|
||||
avatar: dto.avatar,
|
||||
is_active: dto.is_active,
|
||||
is_deleted: dto.is_deleted,
|
||||
role: dto.role,
|
||||
profile_extension: dto.profile_extension,
|
||||
created_at: dto.created_at,
|
||||
updated_at: dto.updated_at,
|
||||
mentor_id: dto.mentor_id,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_role_dto(role: Option<imphnen_entities::seaorm::auth::roles::Model>) -> RolesDetailQueryDto {
|
||||
role.map_or_else(RolesDetailQueryDto::default, |r| RolesDetailQueryDto {
|
||||
id: r.id.to_string(),
|
||||
name: r.name,
|
||||
permissions: r.permissions.clone().and_then(|json| {
|
||||
serde_json::from_value::<Vec<String>>(json).ok().map(|list| {
|
||||
list.into_iter().map(|p| Some(PermissionsQueryDto {
|
||||
id: Some(p.clone()),
|
||||
name: Some(p),
|
||||
created_at: None,
|
||||
updated_at: None,
|
||||
})).collect()
|
||||
})
|
||||
}),
|
||||
is_deleted: r.deleted_at.is_some(),
|
||||
created_at: Some(r.created_at.to_rfc3339()),
|
||||
updated_at: Some(r.updated_at.to_rfc3339()),
|
||||
})
|
||||
UserEntity {
|
||||
id: dto.id,
|
||||
email: dto.email,
|
||||
fullname: dto.fullname,
|
||||
legal_name: dto.legal_name,
|
||||
password: dto.password,
|
||||
avatar: dto.avatar,
|
||||
is_active: dto.is_active,
|
||||
is_deleted: dto.is_deleted,
|
||||
role: dto.role,
|
||||
profile_extension: dto.profile_extension,
|
||||
created_at: dto.created_at,
|
||||
updated_at: dto.updated_at,
|
||||
mentor_id: dto.mentor_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PostgresUserRepository {
|
||||
db: Arc<DatabaseConnection>,
|
||||
db: Arc<DatabaseConnection>,
|
||||
}
|
||||
|
||||
impl PostgresUserRepository {
|
||||
pub fn new(db: DatabaseConnection) -> Self {
|
||||
Self { db: Arc::new(db) }
|
||||
}
|
||||
pub fn new(db: DatabaseConnection) -> Self {
|
||||
Self { db: Arc::new(db) }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl UserRepository for PostgresUserRepository {
|
||||
async fn find_all(&self, params: PaginationParams) -> Result<PaginatorResponse<UserListItem>, AppError> {
|
||||
let page = params.page.max(1);
|
||||
let per_page = params.per_page.clamp(1, 100);
|
||||
async fn find_all(
|
||||
&self,
|
||||
params: PaginationParams,
|
||||
) -> Result<PaginatorResponse<UserListItem>, AppError> {
|
||||
query_user_list(&self.db, params).await
|
||||
}
|
||||
|
||||
let mut query = UsersEntity::find()
|
||||
.filter(UserColumn::DeletedAt.is_null())
|
||||
.filter(UserColumn::IsActive.eq(true));
|
||||
async fn find_by_id(&self, id: &str) -> Result<UserEntity, AppError> {
|
||||
let user_id = Uuid::parse_str(id)
|
||||
.map_err(|_| AppError::BadRequestError("Invalid user ID".into()))?;
|
||||
let (user, role) = UsersEntity::find_by_id(user_id)
|
||||
.filter(UserColumn::DeletedAt.is_null())
|
||||
.find_also_related(RolesEntity)
|
||||
.one(self.db.as_ref())
|
||||
.await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?
|
||||
.ok_or_else(|| AppError::NotFoundError("User not found in database".into()))?;
|
||||
Ok(user_detail_to_entity(
|
||||
build_user_dto(user, build_role_dto(role)).from_profile_extension(),
|
||||
))
|
||||
}
|
||||
|
||||
if let Some(ref search) = params.search {
|
||||
query = query.filter(
|
||||
UserColumn::Email.contains(&search.query)
|
||||
.or(UserColumn::FirstName.contains(&search.query))
|
||||
.or(UserColumn::LastName.contains(&search.query))
|
||||
);
|
||||
}
|
||||
async fn find_by_email(&self, email: String) -> Result<UserEntity, AppError> {
|
||||
let (user, role) = UsersEntity::find()
|
||||
.filter(UserColumn::Email.eq(&email))
|
||||
.filter(UserColumn::DeletedAt.is_null())
|
||||
.find_also_related(RolesEntity)
|
||||
.one(self.db.as_ref())
|
||||
.await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?
|
||||
.ok_or_else(|| AppError::NotFoundError("User not found".into()))?;
|
||||
Ok(user_detail_to_entity(
|
||||
build_user_dto(user, build_role_dto(role)).from_profile_extension(),
|
||||
))
|
||||
}
|
||||
|
||||
let order = match params.sort_direction {
|
||||
Some(SortDirection::Desc) => Order::Desc,
|
||||
_ => Order::Asc,
|
||||
};
|
||||
query = match params.sort_by.as_deref() {
|
||||
Some("email") => query.order_by(UserColumn::Email, order),
|
||||
_ => query.order_by(UserColumn::CreatedAt, order),
|
||||
};
|
||||
async fn create(&self, entity: UserEntity) -> Result<String, AppError> {
|
||||
let existing = UsersEntity::find()
|
||||
.filter(UserColumn::Email.eq(entity.email.clone()))
|
||||
.one(self.db.as_ref())
|
||||
.await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
|
||||
if existing.is_some() {
|
||||
return Err(AppError::ConflictError(
|
||||
"User with this email already exists".into(),
|
||||
));
|
||||
}
|
||||
let full_name = entity.fullname.clone();
|
||||
let (first_name, last_name) =
|
||||
full_name.split_once(' ').unwrap_or((&full_name, ""));
|
||||
let role_id = entity
|
||||
.role
|
||||
.id
|
||||
.parse::<Uuid>()
|
||||
.ok()
|
||||
.or_else(|| entity.role.id.is_empty().then_some(Uuid::nil()));
|
||||
let active_model = UserActiveModel {
|
||||
id: ActiveValue::Set(Uuid::new_v4()),
|
||||
email: ActiveValue::Set(entity.email.clone()),
|
||||
password_hash: ActiveValue::Set(entity.password),
|
||||
username: ActiveValue::Set(entity.email.clone()),
|
||||
first_name: ActiveValue::Set(Some(first_name.to_string())),
|
||||
last_name: ActiveValue::Set(Some(last_name.to_string())),
|
||||
avatar_url: ActiveValue::Set(entity.avatar),
|
||||
is_verified: ActiveValue::Set(false),
|
||||
is_active: ActiveValue::Set(entity.is_active),
|
||||
metadata: ActiveValue::Set(
|
||||
entity
|
||||
.profile_extension
|
||||
.map(|p| serde_json::to_value(p).unwrap_or_default()),
|
||||
),
|
||||
created_at: ActiveValue::Set(Utc::now()),
|
||||
updated_at: ActiveValue::Set(Utc::now()),
|
||||
deleted_at: ActiveValue::Set(None),
|
||||
role_id: ActiveValue::Set(role_id.filter(|id| !id.is_nil())),
|
||||
};
|
||||
UsersEntity::insert(active_model)
|
||||
.exec(self.db.as_ref())
|
||||
.await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
|
||||
Ok("Successfully created user".into())
|
||||
}
|
||||
|
||||
let paginator = query.paginate(self.db.as_ref(), per_page as u64);
|
||||
let users = paginator.fetch_page((page - 1) as u64).await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
|
||||
async fn update(&self, entity: UserEntity) -> Result<String, AppError> {
|
||||
let user_id = Uuid::parse_str(&entity.id)
|
||||
.map_err(|_| AppError::BadRequestError("Invalid user ID".into()))?;
|
||||
let mut active_model: UserActiveModel = UsersEntity::find_by_id(user_id)
|
||||
.one(self.db.as_ref())
|
||||
.await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?
|
||||
.ok_or_else(|| AppError::NotFoundError("User not found".into()))?
|
||||
.into();
|
||||
let full_name = entity.fullname.clone();
|
||||
let (first_name, last_name) =
|
||||
full_name.split_once(' ').unwrap_or((&full_name, ""));
|
||||
active_model.email = ActiveValue::Set(entity.email);
|
||||
active_model.first_name = ActiveValue::Set(Some(first_name.to_string()));
|
||||
active_model.last_name = ActiveValue::Set(Some(last_name.to_string()));
|
||||
active_model.avatar_url = ActiveValue::Set(entity.avatar);
|
||||
active_model.is_active = ActiveValue::Set(entity.is_active);
|
||||
active_model.updated_at = ActiveValue::Set(Utc::now());
|
||||
if !entity.password.is_empty() {
|
||||
active_model.password_hash = ActiveValue::Set(entity.password);
|
||||
}
|
||||
let role_id = entity.role.id.parse::<Uuid>().ok();
|
||||
if role_id.is_some() {
|
||||
active_model.role_id = ActiveValue::Set(role_id);
|
||||
}
|
||||
if entity.profile_extension.is_some() {
|
||||
active_model.metadata = ActiveValue::Set(
|
||||
entity
|
||||
.profile_extension
|
||||
.map(|p| serde_json::to_value(p).unwrap_or_default()),
|
||||
);
|
||||
}
|
||||
active_model
|
||||
.update(self.db.as_ref())
|
||||
.await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
|
||||
Ok("Success update user".into())
|
||||
}
|
||||
|
||||
let role_ids: Vec<Uuid> = users.iter().filter_map(|u| u.role_id).collect();
|
||||
let roles = if !role_ids.is_empty() {
|
||||
RolesEntity::find()
|
||||
.filter(imphnen_entities::seaorm::auth::roles::Column::Id.is_in(role_ids))
|
||||
.all(self.db.as_ref())
|
||||
.await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?
|
||||
.into_iter()
|
||||
.map(|r| (r.id, r.name))
|
||||
.collect::<std::collections::HashMap<_, _>>()
|
||||
} else {
|
||||
std::collections::HashMap::new()
|
||||
};
|
||||
|
||||
let data: Vec<UserListItem> = users.into_iter().map(|user| {
|
||||
let role_name = user.role_id.and_then(|rid| roles.get(&rid).cloned()).unwrap_or_default();
|
||||
UserListItem {
|
||||
id: user.id.to_string(),
|
||||
role: role_name,
|
||||
fullname: format!("{} {}",
|
||||
user.first_name.as_deref().unwrap_or(""),
|
||||
user.last_name.as_deref().unwrap_or("")
|
||||
).trim().to_string(),
|
||||
email: user.email,
|
||||
avatar: user.avatar_url,
|
||||
is_active: user.is_active,
|
||||
created_at: user.created_at.to_rfc3339(),
|
||||
updated_at: user.updated_at.to_rfc3339(),
|
||||
}
|
||||
}).collect();
|
||||
|
||||
let total = paginator.num_items().await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
|
||||
let meta = PaginatorResponseMeta::new(page, per_page, total as u32);
|
||||
Ok(PaginatorResponse { data, meta })
|
||||
}
|
||||
|
||||
async fn find_by_id(&self, id: &str) -> Result<UserEntity, AppError> {
|
||||
let user_id = Uuid::parse_str(id)
|
||||
.map_err(|_| AppError::BadRequestError("Invalid user ID".into()))?;
|
||||
|
||||
let (user, role) = UsersEntity::find_by_id(user_id)
|
||||
.filter(UserColumn::DeletedAt.is_null())
|
||||
.find_also_related(RolesEntity)
|
||||
.one(self.db.as_ref())
|
||||
.await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?
|
||||
.ok_or_else(|| AppError::NotFoundError("User not found in database".into()))?;
|
||||
|
||||
let role_dto = build_role_dto(role);
|
||||
|
||||
let mut dto = UsersDetailQueryDto::default();
|
||||
dto.id = user.id.to_string();
|
||||
dto.fullname = format!("{} {}",
|
||||
user.first_name.as_deref().unwrap_or(""),
|
||||
user.last_name.as_deref().unwrap_or("")
|
||||
).trim().to_string();
|
||||
dto.legal_name = None;
|
||||
dto.email = user.email;
|
||||
dto.avatar = user.avatar_url;
|
||||
dto.is_active = user.is_active;
|
||||
dto.is_deleted = user.deleted_at.is_some();
|
||||
dto.profile_extension = user.metadata.and_then(|m| serde_json::from_value(m).ok());
|
||||
dto.password = user.password_hash;
|
||||
dto.role = role_dto;
|
||||
dto.created_at = user.created_at.to_rfc3339();
|
||||
dto.updated_at = user.updated_at.to_rfc3339();
|
||||
dto.mentor_id = None;
|
||||
|
||||
Ok(user_detail_to_entity(dto.from_profile_extension()))
|
||||
}
|
||||
|
||||
async fn find_by_email(&self, email: String) -> Result<UserEntity, AppError> {
|
||||
let (user, role) = UsersEntity::find()
|
||||
.filter(UserColumn::Email.eq(&email))
|
||||
.filter(UserColumn::DeletedAt.is_null())
|
||||
.find_also_related(RolesEntity)
|
||||
.one(self.db.as_ref())
|
||||
.await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?
|
||||
.ok_or_else(|| AppError::NotFoundError("User not found".into()))?;
|
||||
|
||||
let role_dto = build_role_dto(role);
|
||||
|
||||
let mut dto = UsersDetailQueryDto::default();
|
||||
dto.id = user.id.to_string();
|
||||
dto.fullname = format!("{} {}",
|
||||
user.first_name.as_deref().unwrap_or(""),
|
||||
user.last_name.as_deref().unwrap_or("")
|
||||
).trim().to_string();
|
||||
dto.legal_name = None;
|
||||
dto.email = user.email;
|
||||
dto.avatar = user.avatar_url;
|
||||
dto.is_active = user.is_active;
|
||||
dto.is_deleted = user.deleted_at.is_some();
|
||||
dto.profile_extension = user.metadata.and_then(|m| serde_json::from_value(m).ok());
|
||||
dto.password = user.password_hash;
|
||||
dto.role = role_dto;
|
||||
dto.created_at = user.created_at.to_rfc3339();
|
||||
dto.updated_at = user.updated_at.to_rfc3339();
|
||||
dto.mentor_id = None;
|
||||
|
||||
Ok(user_detail_to_entity(dto.from_profile_extension()))
|
||||
}
|
||||
|
||||
async fn create(&self, entity: UserEntity) -> Result<String, AppError> {
|
||||
// Check for existing user
|
||||
let existing = UsersEntity::find()
|
||||
.filter(UserColumn::Email.eq(entity.email.clone()))
|
||||
.one(self.db.as_ref())
|
||||
.await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
|
||||
|
||||
if existing.is_some() {
|
||||
return Err(AppError::ConflictError("User with this email already exists".into()));
|
||||
}
|
||||
|
||||
let full_name = entity.fullname.clone();
|
||||
let (first_name, last_name) = full_name.split_once(' ').unwrap_or((&full_name, ""));
|
||||
|
||||
let role_id = entity.role.id.parse::<Uuid>().ok()
|
||||
.or_else(|| entity.role.id.is_empty().then_some(Uuid::nil()));
|
||||
|
||||
let active_model = UserActiveModel {
|
||||
id: ActiveValue::Set(Uuid::new_v4()),
|
||||
email: ActiveValue::Set(entity.email.clone()),
|
||||
password_hash: ActiveValue::Set(entity.password),
|
||||
username: ActiveValue::Set(entity.email.clone()),
|
||||
first_name: ActiveValue::Set(Some(first_name.to_string())),
|
||||
last_name: ActiveValue::Set(Some(last_name.to_string())),
|
||||
avatar_url: ActiveValue::Set(entity.avatar),
|
||||
is_verified: ActiveValue::Set(false),
|
||||
is_active: ActiveValue::Set(entity.is_active),
|
||||
metadata: ActiveValue::Set(
|
||||
entity.profile_extension.map(|p| serde_json::to_value(p).unwrap_or_default())
|
||||
),
|
||||
created_at: ActiveValue::Set(Utc::now()),
|
||||
updated_at: ActiveValue::Set(Utc::now()),
|
||||
deleted_at: ActiveValue::Set(None),
|
||||
role_id: ActiveValue::Set(role_id.filter(|id| !id.is_nil())),
|
||||
};
|
||||
|
||||
UsersEntity::insert(active_model).exec(self.db.as_ref()).await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
|
||||
|
||||
Ok("Successfully created user".into())
|
||||
}
|
||||
|
||||
async fn update(&self, entity: UserEntity) -> Result<String, AppError> {
|
||||
let user_id = Uuid::parse_str(&entity.id)
|
||||
.map_err(|_| AppError::BadRequestError("Invalid user ID".into()))?;
|
||||
|
||||
let mut active_model: UserActiveModel = UsersEntity::find_by_id(user_id)
|
||||
.one(self.db.as_ref())
|
||||
.await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?
|
||||
.ok_or_else(|| AppError::NotFoundError("User not found".into()))?
|
||||
.into();
|
||||
|
||||
let full_name = entity.fullname.clone();
|
||||
let (first_name, last_name) = full_name.split_once(' ').unwrap_or((&full_name, ""));
|
||||
|
||||
active_model.email = ActiveValue::Set(entity.email);
|
||||
active_model.first_name = ActiveValue::Set(Some(first_name.to_string()));
|
||||
active_model.last_name = ActiveValue::Set(Some(last_name.to_string()));
|
||||
active_model.avatar_url = ActiveValue::Set(entity.avatar);
|
||||
active_model.is_active = ActiveValue::Set(entity.is_active);
|
||||
active_model.updated_at = ActiveValue::Set(Utc::now());
|
||||
|
||||
if !entity.password.is_empty() {
|
||||
active_model.password_hash = ActiveValue::Set(entity.password);
|
||||
}
|
||||
|
||||
let role_id = entity.role.id.parse::<Uuid>().ok();
|
||||
if role_id.is_some() {
|
||||
active_model.role_id = ActiveValue::Set(role_id);
|
||||
}
|
||||
|
||||
if entity.profile_extension.is_some() {
|
||||
active_model.metadata = ActiveValue::Set(
|
||||
entity.profile_extension.map(|p| serde_json::to_value(p).unwrap_or_default())
|
||||
);
|
||||
}
|
||||
|
||||
active_model.update(self.db.as_ref()).await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
|
||||
|
||||
Ok("Success update user".into())
|
||||
}
|
||||
|
||||
async fn delete(&self, id: String) -> Result<String, AppError> {
|
||||
let user_id = Uuid::parse_str(&id)
|
||||
.map_err(|_| AppError::BadRequestError("Invalid user ID".into()))?;
|
||||
|
||||
let mut active_model: UserActiveModel = UsersEntity::find_by_id(user_id)
|
||||
.one(self.db.as_ref())
|
||||
.await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?
|
||||
.ok_or_else(|| AppError::NotFoundError("User not found".into()))?
|
||||
.into();
|
||||
|
||||
active_model.deleted_at = ActiveValue::Set(Some(Utc::now()));
|
||||
active_model.updated_at = ActiveValue::Set(Utc::now());
|
||||
|
||||
active_model.update(self.db.as_ref()).await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
|
||||
|
||||
Ok("Success delete user".into())
|
||||
}
|
||||
async fn delete(&self, id: String) -> Result<String, AppError> {
|
||||
let user_id = Uuid::parse_str(&id)
|
||||
.map_err(|_| AppError::BadRequestError("Invalid user ID".into()))?;
|
||||
let mut active_model: UserActiveModel = UsersEntity::find_by_id(user_id)
|
||||
.one(self.db.as_ref())
|
||||
.await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?
|
||||
.ok_or_else(|| AppError::NotFoundError("User not found".into()))?
|
||||
.into();
|
||||
active_model.deleted_at = ActiveValue::Set(Some(Utc::now()));
|
||||
active_model.updated_at = ActiveValue::Set(Utc::now());
|
||||
active_model
|
||||
.update(self.db.as_ref())
|
||||
.await
|
||||
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
|
||||
Ok("Success delete user".into())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user