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:
Dionisio
2026-02-11 01:08:00 +01:00
parent 1a1dee9179
commit ccd071911d
7 changed files with 951 additions and 230 deletions
+12
View File
@@ -97,6 +97,18 @@ pub trait UserStoragePort: Send + Sync + 'static {
/// Finds a user by OIDC provider + subject pair
async fn get_user_by_oidc_subject(&self, provider: &str, subject: &str) -> Result<User, DomainError>;
/// Activa o desactiva un usuario
async fn set_user_active_status(&self, user_id: &str, active: bool) -> Result<(), DomainError>;
/// Cambia el rol de un usuario
async fn change_role(&self, user_id: &str, role: &str) -> Result<(), DomainError>;
/// Actualiza la cuota de almacenamiento de un usuario
async fn update_storage_quota(&self, user_id: &str, quota_bytes: i64) -> Result<(), DomainError>;
/// Cuenta el número total de usuarios
async fn count_users(&self) -> Result<i64, DomainError>;
}
// ============================================================================