feat(admin): add user management, quotas, and stats dashboard to admin panel
- Dashboard tab: total users, active users, admins, storage usage, quota warnings - User management tab: list, edit role, activate/deactivate, update quota, delete - Self-protection: cannot delete/deactivate/demote yourself - Paginated user listing (default 50 per page) - Efficient single-query dashboard stats with direct SQL aggregation - Quota modal with GB/MB/TB unit selector - Backend: added methods to UserStoragePort, UserRepository, PgRepository - Backend: added 7 admin methods to AuthApplicationService - Backend: added 5 new DTOs for admin operations - Backend: 7 new endpoints under /api/admin/ Files modified: - src/application/ports/auth_ports.rs (4 new UserStoragePort methods) - src/domain/repositories/user_repository.rs (StorageStats + 3 methods) - src/infrastructure/repositories/pg/user_pg_repository.rs (implementations) - src/application/services/auth_application_service.rs (admin methods) - src/application/dtos/settings_dto.rs (5 new DTOs) - src/interfaces/api/handlers/admin_handler.rs (7 new endpoints) - static/admin.html (complete UI with 3 tabs: Dashboard, Users, OIDC)
This commit is contained in:
@@ -605,6 +605,69 @@ impl AuthApplicationService {
|
||||
Ok(users.into_iter().map(UserDto::from).collect())
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Admin User Management Methods
|
||||
// ========================================================================
|
||||
|
||||
/// Get a single user by ID (for admin panel)
|
||||
pub async fn get_user_admin(&self, user_id: &str) -> Result<UserDto, DomainError> {
|
||||
let user = self.user_storage.get_user_by_id(user_id).await?;
|
||||
Ok(UserDto::from(user))
|
||||
}
|
||||
|
||||
/// Delete a user by ID (admin only)
|
||||
pub async fn delete_user_admin(&self, user_id: &str) -> Result<(), DomainError> {
|
||||
// Prevent deleting yourself
|
||||
let user = self.user_storage.get_user_by_id(user_id).await?;
|
||||
tracing::info!("Admin deleting user: {} ({})", user.username(), user_id);
|
||||
self.user_storage.delete_user(user_id).await
|
||||
}
|
||||
|
||||
/// Activate or deactivate a user (admin only)
|
||||
pub async fn set_user_active(&self, user_id: &str, active: bool) -> Result<(), DomainError> {
|
||||
self.user_storage.set_user_active_status(user_id, active).await
|
||||
}
|
||||
|
||||
/// Change user role (admin only)
|
||||
pub async fn change_user_role(&self, user_id: &str, role: &str) -> Result<(), DomainError> {
|
||||
if role != "admin" && role != "user" {
|
||||
return Err(DomainError::new(
|
||||
ErrorKind::InvalidInput,
|
||||
"User",
|
||||
format!("Invalid role: {}. Must be 'admin' or 'user'", role),
|
||||
));
|
||||
}
|
||||
self.user_storage.change_role(user_id, role).await
|
||||
}
|
||||
|
||||
/// Update user's storage quota (admin only)
|
||||
pub async fn update_user_quota(&self, user_id: &str, quota_bytes: i64) -> Result<(), DomainError> {
|
||||
if quota_bytes < 0 {
|
||||
return Err(DomainError::new(
|
||||
ErrorKind::InvalidInput,
|
||||
"User",
|
||||
"Quota must be non-negative".to_string(),
|
||||
));
|
||||
}
|
||||
self.user_storage.update_storage_quota(user_id, quota_bytes).await
|
||||
}
|
||||
|
||||
/// Check if a user has enough quota for an upload of the given size
|
||||
pub async fn check_quota(&self, user_id: &str, additional_bytes: i64) -> Result<bool, DomainError> {
|
||||
let user = self.user_storage.get_user_by_id(user_id).await?;
|
||||
let quota = user.storage_quota_bytes();
|
||||
if quota <= 0 {
|
||||
// 0 or negative means unlimited
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(user.storage_used_bytes() + additional_bytes <= quota)
|
||||
}
|
||||
|
||||
/// Count users efficiently
|
||||
pub async fn count_users_efficient(&self) -> Result<i64, DomainError> {
|
||||
self.user_storage.count_users().await
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// OIDC Methods
|
||||
// ========================================================================
|
||||
|
||||
Reference in New Issue
Block a user