diff --git a/VEZA_COMPLETE_MVP_TODOLIST.json b/VEZA_COMPLETE_MVP_TODOLIST.json index c2adacadd..6391ea9a1 100644 --- a/VEZA_COMPLETE_MVP_TODOLIST.json +++ b/VEZA_COMPLETE_MVP_TODOLIST.json @@ -1467,7 +1467,19 @@ "description": "GET /api/v1/users/search with query params (q, page, limit)", "owner": "backend", "estimated_hours": 3, - "status": "todo", + "status": "completed", + "completion": { + "completed_at": "2025-12-23T09:41:52Z", + "actual_hours": 0.75, + "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": "Created SearchUsers method in UserService (in separate file user_service_search.go) with pagination support. SearchUsers searches by username, email, first_name, and last_name using ILIKE. Added SearchUsers handler in ProfileHandler with query params (q, page, limit). Added GET /users/search route in setupUserRoutes. Returns paginated results with total count. Password hashes are excluded from results.", + "issues_encountered": [] + }, "files_involved": [], "implementation_steps": [ { diff --git a/veza-backend-api/internal/api/router.go b/veza-backend-api/internal/api/router.go index 1c268a4ff..280d91a5f 100644 --- a/veza-backend-api/internal/api/router.go +++ b/veza-backend-api/internal/api/router.go @@ -370,6 +370,7 @@ func (r *APIRouter) setupUserRoutes(router *gin.RouterGroup) { { users.GET("/:id", profileHandler.GetProfile) users.GET("/by-username/:username", profileHandler.GetProfileByUsername) + users.GET("/search", profileHandler.SearchUsers) // BE-API-008: User search endpoint // Protected routes if r.config.AuthMiddleware != nil { diff --git a/veza-backend-api/internal/handlers/profile_handler.go b/veza-backend-api/internal/handlers/profile_handler.go index 39f38d42e..210810e88 100644 --- a/veza-backend-api/internal/handlers/profile_handler.go +++ b/veza-backend-api/internal/handlers/profile_handler.go @@ -2,6 +2,7 @@ package handlers import ( "net/http" + "strconv" "time" apperrors "veza-backend-api/internal/errors" @@ -158,6 +159,44 @@ func (h *ProfileHandler) GetProfileCompletion(c *gin.Context) { RespondSuccess(c, http.StatusOK, completion) } +// SearchUsers gère la recherche d'utilisateurs +// BE-API-008: Implement user search endpoint +func (h *ProfileHandler) SearchUsers(c *gin.Context) { + // Récupérer les paramètres de recherche + query := c.Query("q") + pageParam := c.DefaultQuery("page", "1") + limitParam := c.DefaultQuery("limit", "20") + + // Parser les paramètres + page, err := strconv.Atoi(pageParam) + if err != nil || page < 1 { + page = 1 + } + + limit, err := strconv.Atoi(limitParam) + if err != nil || limit < 1 { + limit = 20 + } + + // Rechercher les utilisateurs + users, total, err := h.userService.SearchUsers(c.Request.Context(), services.SearchUsersParams{ + Query: query, + Page: page, + Limit: limit, + }) + if err != nil { + RespondWithAppError(c, apperrors.Wrap(apperrors.ErrCodeInternal, "Failed to search users", err)) + return + } + + RespondSuccess(c, http.StatusOK, gin.H{ + "users": users, + "total": total, + "page": page, + "limit": limit, + }) +} + // UpdateProfileRequest represents the request body for updating a user profile type UpdateProfileRequest struct { FirstName string `json:"first_name" binding:"omitempty,max=100" validate:"omitempty,max=100"` diff --git a/veza-backend-api/internal/handlers/role_handler.go b/veza-backend-api/internal/handlers/role_handler.go index 62bc23658..948c31af4 100644 --- a/veza-backend-api/internal/handlers/role_handler.go +++ b/veza-backend-api/internal/handlers/role_handler.go @@ -4,12 +4,13 @@ import ( "net/http" "time" - "github.com/gin-gonic/gin" - "github.com/google/uuid" - "go.uber.org/zap" apperrors "veza-backend-api/internal/errors" "veza-backend-api/internal/models" "veza-backend-api/internal/services" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" + "go.uber.org/zap" ) // RoleHandler gère les endpoints de gestion des rôles diff --git a/veza-backend-api/internal/services/user_service_search.go b/veza-backend-api/internal/services/user_service_search.go new file mode 100644 index 000000000..0df1c0c06 --- /dev/null +++ b/veza-backend-api/internal/services/user_service_search.go @@ -0,0 +1,69 @@ +package services + +import ( + "context" + "errors" + "fmt" + + "veza-backend-api/internal/models" +) + +// SearchUsersParams représente les paramètres de recherche d'utilisateurs +// BE-API-008: Implement user search endpoint +type SearchUsersParams struct { + Query string // Recherche par username, email, first_name, last_name + Page int // Numéro de page (défaut: 1) + Limit int // Nombre de résultats par page (défaut: 20, max: 100) +} + +// SearchUsers recherche des utilisateurs selon les critères fournis +// BE-API-008: Implement user search endpoint +func (s *UserService) SearchUsers(ctx context.Context, params SearchUsersParams) ([]*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 de recherche + query := s.db.WithContext(ctx).Model(&models.User{}) + + // Recherche par query (username, email, first_name, last_name) + if params.Query != "" { + searchPattern := "%" + params.Query + "%" + 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 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 search users: %w", err) + } + + // Exclure les mots de passe des résultats + for _, user := range users { + user.PasswordHash = "" + } + + return users, total, nil +} +