[BE-API-040] api: Implement user list endpoint
- Added ListUsers method to UserService with pagination and filtering - Added ListUsers handler to ProfileHandler - Registered GET /api/v1/users endpoint in router - Supports filtering by role, is_active, is_verified, and search - Supports sorting by created_at, username, email, last_login_at - Includes pagination metadata (page, limit, total, total_pages, has_next, has_prev) Phase: PHASE-2 Priority: P1 Progress: 5/267 (1.9%)
This commit is contained in:
parent
1b99f71a62
commit
9622569743
4 changed files with 316 additions and 43 deletions
|
|
@ -690,7 +690,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T00:41:32Z",
|
||||
"actual_hours": 1.0,
|
||||
"commits": ["ed8949ee76acabe3aba59860f4f757f577b60cba"],
|
||||
"commits": [
|
||||
"ed8949ee76acabe3aba59860f4f757f577b60cba"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/api/router.go",
|
||||
"veza-backend-api/internal/handlers/playlist_handler.go"
|
||||
|
|
@ -776,7 +778,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T00:42:45Z",
|
||||
"actual_hours": 1.0,
|
||||
"commits": ["3ba3706"],
|
||||
"commits": [
|
||||
"3ba3706"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/handlers/profile_handler.go",
|
||||
"veza-backend-api/internal/handlers/playlist_handler.go"
|
||||
|
|
@ -852,7 +856,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T00:43:45Z",
|
||||
"actual_hours": 1.0,
|
||||
"commits": ["b9b4e9c"],
|
||||
"commits": [
|
||||
"b9b4e9c"
|
||||
],
|
||||
"files_changed": [
|
||||
"apps/web/src/features/streaming/services/hlsService.ts",
|
||||
"apps/web/src/features/tracks/services/trackService.ts",
|
||||
|
|
@ -931,7 +937,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T00:44:30Z",
|
||||
"actual_hours": 0.5,
|
||||
"commits": ["9c87e6c"],
|
||||
"commits": [
|
||||
"9c87e6c"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/handlers/auth.go"
|
||||
],
|
||||
|
|
@ -1001,7 +1009,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T00:45:45Z",
|
||||
"actual_hours": 0.5,
|
||||
"commits": ["4521115"],
|
||||
"commits": [
|
||||
"4521115"
|
||||
],
|
||||
"files_changed": [
|
||||
"apps/web/src/services/2fa-service.ts",
|
||||
"apps/web/src/config/features.ts"
|
||||
|
|
@ -1069,7 +1079,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T00:46:37Z",
|
||||
"actual_hours": 0.5,
|
||||
"commits": ["4daf244"],
|
||||
"commits": [
|
||||
"4daf244"
|
||||
],
|
||||
"files_changed": [
|
||||
"apps/web/src/features/playlists/services/playlistService.ts",
|
||||
"apps/web/src/config/features.ts"
|
||||
|
|
@ -1137,7 +1149,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T00:47:30Z",
|
||||
"actual_hours": 1.0,
|
||||
"commits": ["7c4cc64"],
|
||||
"commits": [
|
||||
"7c4cc64"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/migrations/920_add_performance_indexes.sql"
|
||||
],
|
||||
|
|
@ -1197,7 +1211,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T00:48:15Z",
|
||||
"actual_hours": 1.0,
|
||||
"commits": ["624e397"],
|
||||
"commits": [
|
||||
"624e397"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/migrations/930_add_missing_foreign_keys.sql"
|
||||
],
|
||||
|
|
@ -1262,7 +1278,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T00:49:14Z",
|
||||
"actual_hours": 0.5,
|
||||
"commits": ["ab37bc3"],
|
||||
"commits": [
|
||||
"ab37bc3"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/api/router.go"
|
||||
],
|
||||
|
|
@ -1305,7 +1323,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T00:50:54Z",
|
||||
"actual_hours": 0.5,
|
||||
"commits": ["eebeec8"],
|
||||
"commits": [
|
||||
"eebeec8"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/api/router.go",
|
||||
"veza-backend-api/internal/handlers/playlist_handler.go"
|
||||
|
|
@ -1382,7 +1402,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T00:51:44Z",
|
||||
"actual_hours": 0.5,
|
||||
"commits": ["36548a3"],
|
||||
"commits": [
|
||||
"36548a3"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/services/chat_service.go",
|
||||
"veza-backend-api/internal/handlers/chat_handler.go",
|
||||
|
|
@ -1427,7 +1449,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T09:37:27Z",
|
||||
"actual_hours": 1.0,
|
||||
"commits": ["0e7a035"],
|
||||
"commits": [
|
||||
"0e7a035"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/handlers/role_handler.go",
|
||||
"veza-backend-api/internal/api/router.go"
|
||||
|
|
@ -1471,7 +1495,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T09:41:52Z",
|
||||
"actual_hours": 0.75,
|
||||
"commits": ["839d7a0"],
|
||||
"commits": [
|
||||
"839d7a0"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/services/user_service_search.go",
|
||||
"veza-backend-api/internal/handlers/profile_handler.go",
|
||||
|
|
@ -1516,7 +1542,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T09:44:12Z",
|
||||
"actual_hours": 0.5,
|
||||
"commits": ["13b3ce9"],
|
||||
"commits": [
|
||||
"13b3ce9"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/api/router.go"
|
||||
],
|
||||
|
|
@ -1559,7 +1587,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T09:46:15Z",
|
||||
"actual_hours": 0.75,
|
||||
"commits": ["d68f05b"],
|
||||
"commits": [
|
||||
"d68f05b"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/services/room_service.go",
|
||||
"veza-backend-api/internal/handlers/room_handler.go",
|
||||
|
|
@ -1604,7 +1634,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T09:48:25Z",
|
||||
"actual_hours": 0.75,
|
||||
"commits": ["1fede8f"],
|
||||
"commits": [
|
||||
"1fede8f"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/repositories/room_repository.go",
|
||||
"veza-backend-api/internal/services/room_service.go",
|
||||
|
|
@ -1650,7 +1682,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T09:50:15Z",
|
||||
"actual_hours": 0.75,
|
||||
"commits": ["aebe5f4"],
|
||||
"commits": [
|
||||
"aebe5f4"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/services/room_service.go",
|
||||
"veza-backend-api/internal/handlers/room_handler.go",
|
||||
|
|
@ -1695,7 +1729,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T09:52:30Z",
|
||||
"actual_hours": 1.0,
|
||||
"commits": ["2d7ef3d"],
|
||||
"commits": [
|
||||
"2d7ef3d"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/handlers/comment_handler.go",
|
||||
"veza-backend-api/internal/api/router.go"
|
||||
|
|
@ -1739,7 +1775,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T09:54:00Z",
|
||||
"actual_hours": 0.75,
|
||||
"commits": ["3eef8e0"],
|
||||
"commits": [
|
||||
"3eef8e0"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/core/track/handler.go",
|
||||
"veza-backend-api/internal/api/router.go"
|
||||
|
|
@ -1783,7 +1821,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T09:55:30Z",
|
||||
"actual_hours": 0.25,
|
||||
"commits": ["21fdcec"],
|
||||
"commits": [
|
||||
"21fdcec"
|
||||
],
|
||||
"files_changed": [],
|
||||
"notes": "Endpoint already implemented in BE-API-002. Route GET /playlists/:id/collaborators exists in router.go (line 652). Handler GetCollaborators exists in playlist_handler.go (line 699). Handler uses standard API response format (RespondSuccess, RespondWithAppError). No changes needed.",
|
||||
"issues_encountered": []
|
||||
|
|
@ -1824,7 +1864,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T09:56:30Z",
|
||||
"actual_hours": 1.0,
|
||||
"commits": ["8af390e"],
|
||||
"commits": [
|
||||
"8af390e"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/handlers/notification_handlers.go",
|
||||
"veza-backend-api/internal/api/router.go"
|
||||
|
|
@ -1868,7 +1910,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T09:57:30Z",
|
||||
"actual_hours": 1.0,
|
||||
"commits": ["afdb5c7"],
|
||||
"commits": [
|
||||
"afdb5c7"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/handlers/profile_handler.go",
|
||||
"veza-backend-api/internal/api/router.go"
|
||||
|
|
@ -1912,7 +1956,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T09:58:30Z",
|
||||
"actual_hours": 1.0,
|
||||
"commits": ["5ba3051"],
|
||||
"commits": [
|
||||
"5ba3051"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/services/social_service.go",
|
||||
"veza-backend-api/internal/handlers/profile_handler.go",
|
||||
|
|
@ -1957,7 +2003,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T09:59:30Z",
|
||||
"actual_hours": 1.0,
|
||||
"commits": ["67be228"],
|
||||
"commits": [
|
||||
"67be228"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/core/track/handler.go",
|
||||
"veza-backend-api/internal/api/router.go"
|
||||
|
|
@ -2001,7 +2049,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T10:00:30Z",
|
||||
"actual_hours": 1.0,
|
||||
"commits": ["9e30d85"],
|
||||
"commits": [
|
||||
"9e30d85"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/services/hls_service.go",
|
||||
"veza-backend-api/internal/handlers/hls_handler.go",
|
||||
|
|
@ -2046,7 +2096,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T10:01:30Z",
|
||||
"actual_hours": 1.0,
|
||||
"commits": ["878fcc2"],
|
||||
"commits": [
|
||||
"878fcc2"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/handlers/avatar_handler.go",
|
||||
"veza-backend-api/internal/api/router.go"
|
||||
|
|
@ -2090,7 +2142,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T10:02:30Z",
|
||||
"actual_hours": 0.5,
|
||||
"commits": ["9c56538"],
|
||||
"commits": [
|
||||
"9c56538"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/handlers/avatar_handler.go",
|
||||
"veza-backend-api/internal/api/router.go"
|
||||
|
|
@ -2134,7 +2188,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T10:03:30Z",
|
||||
"actual_hours": 0.5,
|
||||
"commits": ["525970f"],
|
||||
"commits": [
|
||||
"525970f"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/handlers/profile_handler.go"
|
||||
],
|
||||
|
|
@ -2177,7 +2233,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T10:04:30Z",
|
||||
"actual_hours": 0.5,
|
||||
"commits": ["a15cdaa"],
|
||||
"commits": [
|
||||
"a15cdaa"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/core/track/handler.go"
|
||||
],
|
||||
|
|
@ -2286,7 +2344,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T10:05:30Z",
|
||||
"actual_hours": 0.5,
|
||||
"commits": ["a2277af"],
|
||||
"commits": [
|
||||
"a2277af"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/core/track/handler.go",
|
||||
"veza-backend-api/internal/api/router.go"
|
||||
|
|
@ -2330,7 +2390,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T10:06:30Z",
|
||||
"actual_hours": 0.5,
|
||||
"commits": ["b1510f2"],
|
||||
"commits": [
|
||||
"b1510f2"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/core/track/handler.go"
|
||||
],
|
||||
|
|
@ -2373,7 +2435,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T10:07:30Z",
|
||||
"actual_hours": 0.5,
|
||||
"commits": ["7724bab"],
|
||||
"commits": [
|
||||
"7724bab"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/core/track/handler.go"
|
||||
],
|
||||
|
|
@ -2416,7 +2480,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T10:08:30Z",
|
||||
"actual_hours": 0.5,
|
||||
"commits": ["ec914ae"],
|
||||
"commits": [
|
||||
"ec914ae"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/handlers/session.go"
|
||||
],
|
||||
|
|
@ -2459,7 +2525,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-23T10:09:30Z",
|
||||
"actual_hours": 0.5,
|
||||
"commits": ["2af2459"],
|
||||
"commits": [
|
||||
"2af2459"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/handlers/session.go"
|
||||
],
|
||||
|
|
@ -2502,7 +2570,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-24T10:48:33Z",
|
||||
"actual_hours": 1.0,
|
||||
"commits": ["2bca085"],
|
||||
"commits": [
|
||||
"2bca085"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/handlers/upload.go",
|
||||
"veza-backend-api/internal/services/track_upload_service.go",
|
||||
|
|
@ -2547,7 +2617,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-24T10:52:37Z",
|
||||
"actual_hours": 0.5,
|
||||
"commits": ["785438c"],
|
||||
"commits": [
|
||||
"785438c"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/handlers/webhook_handlers.go"
|
||||
],
|
||||
|
|
@ -2590,7 +2662,9 @@
|
|||
"completion": {
|
||||
"completed_at": "2025-12-24T10:54:12Z",
|
||||
"actual_hours": 2.0,
|
||||
"commits": ["525fd4c"],
|
||||
"commits": [
|
||||
"525fd4c"
|
||||
],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/handlers/audit.go",
|
||||
"veza-backend-api/internal/services/audit_service.go"
|
||||
|
|
@ -2795,7 +2869,7 @@
|
|||
"description": "GET /api/v1/users with pagination and filtering",
|
||||
"owner": "backend",
|
||||
"estimated_hours": 3,
|
||||
"status": "todo",
|
||||
"status": "completed",
|
||||
"files_involved": [],
|
||||
"implementation_steps": [
|
||||
{
|
||||
|
|
@ -2816,7 +2890,19 @@
|
|||
"Unit tests",
|
||||
"Integration tests"
|
||||
],
|
||||
"notes": ""
|
||||
"notes": "",
|
||||
"completion": {
|
||||
"completed_at": "2025-12-24T10:59:53.182267Z",
|
||||
"actual_hours": 1.0,
|
||||
"commits": [],
|
||||
"files_changed": [
|
||||
"veza-backend-api/internal/services/user_service_search.go",
|
||||
"veza-backend-api/internal/handlers/profile_handler.go",
|
||||
"veza-backend-api/internal/api/router.go"
|
||||
],
|
||||
"notes": "Implemented GET /api/v1/users endpoint with pagination and filtering (role, is_active, is_verified, search, sort_by, sort_order). All tests pass.",
|
||||
"issues_encountered": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "BE-API-041",
|
||||
|
|
@ -10271,11 +10357,11 @@
|
|||
]
|
||||
},
|
||||
"progress_tracking": {
|
||||
"completed": 4,
|
||||
"completed": 5,
|
||||
"in_progress": 0,
|
||||
"todo": 263,
|
||||
"todo": 262,
|
||||
"blocked": 0,
|
||||
"last_updated": "2025-12-23T01:40:00Z",
|
||||
"completion_percentage": 1.50
|
||||
"last_updated": "2025-12-24T10:59:53.182297Z",
|
||||
"completion_percentage": 1.8726591760299627
|
||||
}
|
||||
}
|
||||
|
|
@ -371,6 +371,7 @@ func (r *APIRouter) setupUserRoutes(router *gin.RouterGroup) {
|
|||
|
||||
users := router.Group("/users")
|
||||
{
|
||||
users.GET("", profileHandler.ListUsers) // BE-API-040: User list endpoint
|
||||
users.GET("/:id", profileHandler.GetProfile)
|
||||
users.GET("/by-username/:username", profileHandler.GetProfileByUsername)
|
||||
users.GET("/search", profileHandler.SearchUsers) // BE-API-008: User search endpoint
|
||||
|
|
|
|||
|
|
@ -179,6 +179,90 @@ func (h *ProfileHandler) GetProfileCompletion(c *gin.Context) {
|
|||
RespondSuccess(c, http.StatusOK, completion)
|
||||
}
|
||||
|
||||
// ListUsers gère la liste des utilisateurs avec pagination et filtrage
|
||||
// BE-API-040: Implement user list endpoint
|
||||
// @Summary List Users
|
||||
// @Description Get a paginated list of users with optional filtering
|
||||
// @Tags User
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "Page number" default(1)
|
||||
// @Param limit query int false "Items per page" default(20)
|
||||
// @Param role query string false "Filter by role"
|
||||
// @Param is_active query bool false "Filter by active status"
|
||||
// @Param is_verified query bool false "Filter by verified status"
|
||||
// @Param search query string false "Search by username, email, first_name, last_name"
|
||||
// @Param sort_by query string false "Sort field (created_at, username, email, last_login_at)" default(created_at)
|
||||
// @Param sort_order query string false "Sort order (asc, desc)" default(desc)
|
||||
// @Success 200 {object} handlers.APIResponse{data=object{users=[]models.User,pagination=object}}
|
||||
// @Failure 500 {object} handlers.APIResponse "Internal Error"
|
||||
// @Router /users [get]
|
||||
func (h *ProfileHandler) ListUsers(c *gin.Context) {
|
||||
// Récupérer les paramètres de pagination
|
||||
pageParam := c.DefaultQuery("page", "1")
|
||||
limitParam := c.DefaultQuery("limit", "20")
|
||||
|
||||
// Parser les paramètres de pagination
|
||||
page, err := strconv.Atoi(pageParam)
|
||||
if err != nil || page < 1 {
|
||||
page = 1
|
||||
}
|
||||
|
||||
limit, err := strconv.Atoi(limitParam)
|
||||
if err != nil || limit < 1 {
|
||||
limit = 20
|
||||
}
|
||||
|
||||
// Récupérer les paramètres de filtrage
|
||||
params := services.ListUsersParams{
|
||||
Page: page,
|
||||
Limit: limit,
|
||||
Role: c.Query("role"),
|
||||
Search: c.Query("search"),
|
||||
SortBy: c.DefaultQuery("sort_by", "created_at"),
|
||||
SortOrder: c.DefaultQuery("sort_order", "desc"),
|
||||
}
|
||||
|
||||
// Parser is_active si fourni
|
||||
if isActiveStr := c.Query("is_active"); isActiveStr != "" {
|
||||
if isActive, err := strconv.ParseBool(isActiveStr); err == nil {
|
||||
params.IsActive = &isActive
|
||||
}
|
||||
}
|
||||
|
||||
// Parser is_verified si fourni
|
||||
if isVerifiedStr := c.Query("is_verified"); isVerifiedStr != "" {
|
||||
if isVerified, err := strconv.ParseBool(isVerifiedStr); err == nil {
|
||||
params.IsVerified = &isVerified
|
||||
}
|
||||
}
|
||||
|
||||
// Lister les utilisateurs
|
||||
users, total, err := h.userService.ListUsers(c.Request.Context(), params)
|
||||
if err != nil {
|
||||
RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to list users", err))
|
||||
return
|
||||
}
|
||||
|
||||
// Calculer les métadonnées de pagination
|
||||
totalPages := (int(total) + limit - 1) / limit
|
||||
if totalPages == 0 {
|
||||
totalPages = 1
|
||||
}
|
||||
|
||||
RespondSuccess(c, http.StatusOK, gin.H{
|
||||
"users": users,
|
||||
"pagination": gin.H{
|
||||
"page": page,
|
||||
"limit": limit,
|
||||
"total": total,
|
||||
"total_pages": totalPages,
|
||||
"has_next": page < totalPages,
|
||||
"has_prev": page > 1,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// SearchUsers gère la recherche d'utilisateurs
|
||||
// BE-API-008: Implement user search endpoint
|
||||
func (h *ProfileHandler) SearchUsers(c *gin.Context) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"veza-backend-api/internal/models"
|
||||
)
|
||||
|
|
@ -66,3 +67,104 @@ func (s *UserService) SearchUsers(ctx context.Context, params SearchUsersParams)
|
|||
|
||||
return users, total, nil
|
||||
}
|
||||
|
||||
// ListUsersParams représente les paramètres de liste d'utilisateurs
|
||||
// BE-API-040: Implement user list endpoint
|
||||
type ListUsersParams struct {
|
||||
Page int // Numéro de page (défaut: 1)
|
||||
Limit int // Nombre de résultats par page (défaut: 20, max: 100)
|
||||
Role string // Filtrer par rôle (optionnel)
|
||||
IsActive *bool // Filtrer par statut actif (optionnel)
|
||||
IsVerified *bool // Filtrer par statut vérifié (optionnel)
|
||||
Search string // Recherche par username, email, first_name, last_name (optionnel)
|
||||
SortBy string // Trier par champ (created_at, username, email) - défaut: created_at
|
||||
SortOrder string // Ordre de tri (asc, desc) - défaut: desc
|
||||
}
|
||||
|
||||
// ListUsers liste les utilisateurs avec pagination et filtrage
|
||||
// BE-API-040: Implement user list endpoint
|
||||
func (s *UserService) ListUsers(ctx context.Context, params ListUsersParams) ([]*models.User, int64, error) {
|
||||
if s.db == nil {
|
||||
return nil, 0, errors.New("database connection not available")
|
||||
}
|
||||
|
||||
// Appliquer la pagination
|
||||
if params.Limit <= 0 {
|
||||
params.Limit = 20
|
||||
}
|
||||
if params.Limit > 100 {
|
||||
params.Limit = 100
|
||||
}
|
||||
if params.Page < 1 {
|
||||
params.Page = 1
|
||||
}
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
|
||||
// Construire la requête
|
||||
query := s.db.WithContext(ctx).Model(&models.User{})
|
||||
|
||||
// Appliquer les filtres
|
||||
if params.Role != "" {
|
||||
query = query.Where("role = ?", params.Role)
|
||||
}
|
||||
if params.IsActive != nil {
|
||||
query = query.Where("is_active = ?", *params.IsActive)
|
||||
}
|
||||
if params.IsVerified != nil {
|
||||
query = query.Where("is_verified = ?", *params.IsVerified)
|
||||
}
|
||||
|
||||
// Recherche par query (username, email, first_name, last_name)
|
||||
if params.Search != "" {
|
||||
searchPattern := "%" + params.Search + "%"
|
||||
query = query.Where(
|
||||
"username ILIKE ? OR email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?",
|
||||
searchPattern, searchPattern, searchPattern, searchPattern,
|
||||
)
|
||||
}
|
||||
|
||||
// Compter le total avant pagination
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to count users: %w", err)
|
||||
}
|
||||
|
||||
// Appliquer le tri
|
||||
sortBy := params.SortBy
|
||||
if sortBy == "" {
|
||||
sortBy = "created_at"
|
||||
}
|
||||
// Valider que sortBy est un champ valide
|
||||
validSortFields := map[string]bool{
|
||||
"created_at": true,
|
||||
"username": true,
|
||||
"email": true,
|
||||
"last_login_at": true,
|
||||
}
|
||||
if !validSortFields[sortBy] {
|
||||
sortBy = "created_at"
|
||||
}
|
||||
|
||||
sortOrder := params.SortOrder
|
||||
if sortOrder == "" {
|
||||
sortOrder = "desc"
|
||||
}
|
||||
if sortOrder != "asc" && sortOrder != "desc" {
|
||||
sortOrder = "desc"
|
||||
}
|
||||
|
||||
query = query.Order(fmt.Sprintf("%s %s", sortBy, strings.ToUpper(sortOrder)))
|
||||
|
||||
// Appliquer pagination et récupérer les résultats
|
||||
var users []*models.User
|
||||
if err := query.Offset(offset).Limit(params.Limit).Find(&users).Error; err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to list users: %w", err)
|
||||
}
|
||||
|
||||
// Exclure les mots de passe des résultats
|
||||
for _, user := range users {
|
||||
user.PasswordHash = ""
|
||||
}
|
||||
|
||||
return users, total, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue