[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:
parent
74196a4834
commit
839d7a043d
5 changed files with 126 additions and 4 deletions
|
|
@ -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": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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"`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
69
veza-backend-api/internal/services/user_service_search.go
Normal file
69
veza-backend-api/internal/services/user_service_search.go
Normal 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
|
||||
}
|
||||
|
||||
Loading…
Reference in a new issue