[BE-API-008] be-api: Implement user search endpoint

- Created SearchUsers method in UserService 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

Phase: PHASE-2
Priority: P1
Progress: 17/267 (6.4%)
This commit is contained in:
senke 2025-12-23 10:42:26 +01:00
parent 74196a4834
commit 839d7a043d
5 changed files with 126 additions and 4 deletions

View file

@ -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": [
{

View file

@ -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 {

View file

@ -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"`

View file

@ -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

View file

@ -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
}