Add filtering support to GET /api/v1/habits endpoint
Implemented comprehensive filtering capabilities for the habits list endpoint: - Filter by type (BOOLEAN, COUNTER, VALUE) - Filter by frequency (DAILY, WEEKLY, MONTHLY) - Filter by archived status - Text search in habit name and description - All filters can be combined - Filters work with pagination Technical changes: - Added FilterParams to GetUserHabitsQuery - Created HabitFilter struct in repository interface - Implemented dynamic SQL query building in SQLite repository - Updated HTTP handler to parse filter query parameters - Added comprehensive tests for repository and handler filtering - Updated all test mocks with new filter methods
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"apocapoc-api/internal/domain/entities"
|
||||
"apocapoc-api/internal/domain/repositories"
|
||||
"apocapoc-api/internal/shared/errors"
|
||||
"apocapoc-api/internal/shared/pagination"
|
||||
|
||||
@@ -282,3 +283,93 @@ func (r *HabitRepository) CountActiveByUserID(ctx context.Context, userID string
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
func (r *HabitRepository) FindByUserIDFiltered(ctx context.Context, userID string, filter repositories.HabitFilter, paginationParams *pagination.Params) ([]*entities.Habit, error) {
|
||||
baseQuery := `
|
||||
SELECT id, user_id, name, description, type, frequency,
|
||||
specific_days, specific_dates, carry_over, is_negative, target_value,
|
||||
created_at, archived_at
|
||||
FROM habits
|
||||
WHERE user_id = ?`
|
||||
|
||||
args := []interface{}{userID}
|
||||
conditions := []string{}
|
||||
|
||||
if !filter.IncludeArchived {
|
||||
conditions = append(conditions, "archived_at IS NULL")
|
||||
}
|
||||
|
||||
if filter.Type != nil {
|
||||
conditions = append(conditions, "type = ?")
|
||||
args = append(args, string(*filter.Type))
|
||||
}
|
||||
|
||||
if filter.Frequency != nil {
|
||||
conditions = append(conditions, "frequency = ?")
|
||||
args = append(args, string(*filter.Frequency))
|
||||
}
|
||||
|
||||
if filter.Search != "" {
|
||||
conditions = append(conditions, "(name LIKE ? OR description LIKE ?)")
|
||||
searchPattern := "%" + filter.Search + "%"
|
||||
args = append(args, searchPattern, searchPattern)
|
||||
}
|
||||
|
||||
for _, condition := range conditions {
|
||||
baseQuery += " AND " + condition
|
||||
}
|
||||
|
||||
baseQuery += " ORDER BY created_at DESC"
|
||||
|
||||
if paginationParams != nil {
|
||||
baseQuery += " LIMIT ? OFFSET ?"
|
||||
args = append(args, paginationParams.Limit(), paginationParams.Offset())
|
||||
}
|
||||
|
||||
rows, err := r.db.QueryContext(ctx, baseQuery, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find habits: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
return r.scanHabits(rows)
|
||||
}
|
||||
|
||||
func (r *HabitRepository) CountByUserIDFiltered(ctx context.Context, userID string, filter repositories.HabitFilter) (int, error) {
|
||||
baseQuery := `SELECT COUNT(*) FROM habits WHERE user_id = ?`
|
||||
|
||||
args := []interface{}{userID}
|
||||
conditions := []string{}
|
||||
|
||||
if !filter.IncludeArchived {
|
||||
conditions = append(conditions, "archived_at IS NULL")
|
||||
}
|
||||
|
||||
if filter.Type != nil {
|
||||
conditions = append(conditions, "type = ?")
|
||||
args = append(args, string(*filter.Type))
|
||||
}
|
||||
|
||||
if filter.Frequency != nil {
|
||||
conditions = append(conditions, "frequency = ?")
|
||||
args = append(args, string(*filter.Frequency))
|
||||
}
|
||||
|
||||
if filter.Search != "" {
|
||||
conditions = append(conditions, "(name LIKE ? OR description LIKE ?)")
|
||||
searchPattern := "%" + filter.Search + "%"
|
||||
args = append(args, searchPattern, searchPattern)
|
||||
}
|
||||
|
||||
for _, condition := range conditions {
|
||||
baseQuery += " AND " + condition
|
||||
}
|
||||
|
||||
var count int
|
||||
err := r.db.QueryRowContext(ctx, baseQuery, args...).Scan(&count)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to count habits: %w", err)
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user