package controllers import ( "fmt" "strings" "github.com/gin-gonic/gin" "gorm.io/gorm" ) // QueryHelper provides query parameter parsing and filtering utilities type QueryHelper struct{} // SortParams represents sorting parameters type SortParams struct { Field string Order string // "asc" or "desc" } // FilterParams represents generic filter parameters type FilterParams map[string]interface{} // NewQueryHelper creates a new QueryHelper instance func NewQueryHelper() *QueryHelper { return &QueryHelper{} } // GetSortParams extracts sort parameters from the request // Expects: ?sort=field:order (e.g., ?sort=created_at:desc) func (qh *QueryHelper) GetSortParams(c *gin.Context, defaultField, defaultOrder string) SortParams { sortQuery := c.Query("sort") if sortQuery == "" { return SortParams{ Field: defaultField, Order: defaultOrder, } } parts := strings.Split(sortQuery, ":") field := parts[0] order := defaultOrder if len(parts) > 1 { order = strings.ToLower(parts[1]) if order != "asc" && order != "desc" { order = defaultOrder } } return SortParams{ Field: field, Order: order, } } // ApplySort applies sorting to a GORM query func (qh *QueryHelper) ApplySort(db *gorm.DB, params SortParams) *gorm.DB { if params.Field != "" { return db.Order(params.Field + " " + params.Order) } return db } // ApplySortFromContext extracts sort params from context and applies them func (qh *QueryHelper) ApplySortFromContext(c *gin.Context, db *gorm.DB, defaultField, defaultOrder string) *gorm.DB { params := qh.GetSortParams(c, defaultField, defaultOrder) return qh.ApplySort(db, params) } // GetSearchQuery extracts search query from request // Expects: ?search=term or ?q=term func (qh *QueryHelper) GetSearchQuery(c *gin.Context) string { search := c.Query("search") if search == "" { search = c.Query("q") } return strings.TrimSpace(search) } // ApplySearch applies search to specified fields using LIKE func (qh *QueryHelper) ApplySearch(db *gorm.DB, searchTerm string, fields ...string) *gorm.DB { if searchTerm == "" || len(fields) == 0 { return db } searchPattern := "%" + searchTerm + "%" query := db // Build OR conditions for each field for i, field := range fields { if i == 0 { query = query.Where(field+" LIKE ?", searchPattern) } else { query = query.Or(field+" LIKE ?", searchPattern) } } return query } // ApplySearchFromContext extracts search query and applies it func (qh *QueryHelper) ApplySearchFromContext(c *gin.Context, db *gorm.DB, fields ...string) *gorm.DB { searchTerm := qh.GetSearchQuery(c) return qh.ApplySearch(db, searchTerm, fields...) } // GetBoolQuery extracts a boolean query parameter func (qh *QueryHelper) GetBoolQuery(c *gin.Context, key string) *bool { value := c.Query(key) if value == "" { return nil } boolValue := strings.ToLower(value) == "true" || value == "1" return &boolValue } // ApplyBoolFilter applies a boolean filter if the parameter exists func (qh *QueryHelper) ApplyBoolFilter(db *gorm.DB, c *gin.Context, paramKey, dbField string) *gorm.DB { value := qh.GetBoolQuery(c, paramKey) if value != nil { return db.Where(dbField+" = ?", *value) } return db } // GetIDsFromQuery extracts comma-separated IDs from query parameter // Expects: ?ids=1,2,3,4 func (qh *QueryHelper) GetIDsFromQuery(c *gin.Context, key string) []uint { idsStr := c.Query(key) if idsStr == "" { return nil } parts := strings.Split(idsStr, ",") ids := make([]uint, 0, len(parts)) for _, part := range parts { part = strings.TrimSpace(part) if part == "" { continue } // Parse as uint var id uint if _, err := fmt.Sscanf(part, "%d", &id); err == nil { ids = append(ids, id) } } return ids } // ApplyIDsFilter applies IN filter for IDs func (qh *QueryHelper) ApplyIDsFilter(db *gorm.DB, c *gin.Context, paramKey, dbField string) *gorm.DB { ids := qh.GetIDsFromQuery(c, paramKey) if len(ids) > 0 { return db.Where(dbField+" IN ?", ids) } return db } // GetDateRange extracts date range from query parameters // Expects: ?from=2024-01-01&to=2024-12-31 func (qh *QueryHelper) GetDateRange(c *gin.Context) (from, to string) { from = c.Query("from") to = c.Query("to") return } // ApplyDateRangeFilter applies date range filter func (qh *QueryHelper) ApplyDateRangeFilter(db *gorm.DB, c *gin.Context, dbField string) *gorm.DB { from, to := qh.GetDateRange(c) if from != "" { db = db.Where(dbField+" >= ?", from) } if to != "" { db = db.Where(dbField+" <= ?", to) } return db } // BuildQueryChain combines multiple query operations func (qh *QueryHelper) BuildQueryChain(c *gin.Context, db *gorm.DB) *QueryChainBuilder { return &QueryChainBuilder{ ctx: c, query: db, qh: qh, } } // QueryChainBuilder provides a fluent interface for building queries type QueryChainBuilder struct { ctx *gin.Context query *gorm.DB qh *QueryHelper } // WithSort adds sorting func (qcb *QueryChainBuilder) WithSort(defaultField, defaultOrder string) *QueryChainBuilder { qcb.query = qcb.qh.ApplySortFromContext(qcb.ctx, qcb.query, defaultField, defaultOrder) return qcb } // WithSearch adds search across fields func (qcb *QueryChainBuilder) WithSearch(fields ...string) *QueryChainBuilder { qcb.query = qcb.qh.ApplySearchFromContext(qcb.ctx, qcb.query, fields...) return qcb } // WithBoolFilter adds a boolean filter func (qcb *QueryChainBuilder) WithBoolFilter(paramKey, dbField string) *QueryChainBuilder { qcb.query = qcb.qh.ApplyBoolFilter(qcb.query, qcb.ctx, paramKey, dbField) return qcb } // WithIDsFilter adds an IDs filter func (qcb *QueryChainBuilder) WithIDsFilter(paramKey, dbField string) *QueryChainBuilder { qcb.query = qcb.qh.ApplyIDsFilter(qcb.query, qcb.ctx, paramKey, dbField) return qcb } // WithDateRange adds date range filter func (qcb *QueryChainBuilder) WithDateRange(dbField string) *QueryChainBuilder { qcb.query = qcb.qh.ApplyDateRangeFilter(qcb.query, qcb.ctx, dbField) return qcb } // Build returns the final query func (qcb *QueryChainBuilder) Build() *gorm.DB { return qcb.query } // Global QueryHelper instance for convenience var QueryParser = NewQueryHelper()