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 }