veza/veza-backend-api/internal/services/user_service_search.go

179 lines
5.1 KiB
Go

package services
import (
"context"
"errors"
"fmt"
"strings"
"veza-backend-api/internal/models"
)
// buildSearchClause returns the SQL fragment for case-insensitive search.
// SQLite: LIKE (case-insensitive for ASCII), PostgreSQL: ILIKE
func (s *UserService) buildSearchClause() string {
if s.db != nil && s.db.Dialector.Name() == "sqlite" {
return "username LIKE ? OR email LIKE ? OR first_name LIKE ? OR last_name LIKE ?"
}
return "username ILIKE ? OR email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?"
}
// 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(
s.buildSearchClause(),
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
}
// 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(
s.buildSearchClause(),
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
}