Skip to content

Strip Filters

Strip Admin Filter System Documentation

Overview

The Strip Admin filter system provides a unified, reusable filtering infrastructure for list pages (users, characters, media, etc.). This architecture eliminates code duplication while maintaining flexibility and extensibility.

Architecture Components

1. Filter Parser (parser.go)

The parser module handles extracting and validating filter parameters from HTTP requests.

Key Functions:

  • ParseCommonFilters() - Extracts standard parameters (search, cursor, limit, sort)
  • ParseDateRange() - Handles date range filters with automatic time adjustments
  • ParseNumberRange() - Parses numeric range filters with validation
  • ParseBooleanFilter() - Manages tri-state boolean filters (true/false/unset)
  • ParseEnumFilter() - Extracts enum values, ignoring defaults

Example Usage:

// In a handler
commonFilters := filters.ParseCommonFilters(c, 25) // default limit: 25
createdRange := filters.ParseDateRange(c, "created")
hasMedia := filters.ParseBooleanFilter(c, "has_media")

2. Filter Builder (builder.go)

The builder module provides configuration and UI generation for filters.

Key Types:

  • FilterConfig - Complete filter configuration for a page
  • FilterDefinition - Individual filter metadata
  • FilterValues - Current filter values map

Key Functions:

  • NewUsersFilterConfig() - Returns filter configuration for users page
  • NewCharactersFilterConfig() - Returns configuration for characters
  • NewMediaFilterConfig() - Returns configuration for media
  • BuildFilterDefinitions() - Converts configs to template-compatible filters
  • CountActiveFilters() - Counts currently active filters

Example Usage:

config := filters.NewUsersFilterConfig()
values := filters.FilterValues{
"status": "active",
"created": dateRange,
}
templateFilters := filters.BuildFilterDefinitions(config, values)

3. Storage Filters (storage/filters.go)

Provides reusable database query builders for common filter operations.

Key Functions:

  • ApplyDateRangeFilter() - Adds date range conditions to queries
  • ApplyNumberRangeFilter() - Adds numeric range conditions
  • ApplySearchFilter() - Implements multi-column search
  • ApplyBooleanFilter() - Handles boolean conditions
  • ApplyEnumFilter() - Filters by enum values
  • ParseCommonFilters() - Extracts cursor, limit, search, and sort parameters

Example Usage:

query := db.NewSelect().Model(&users)
query = storage.ApplyDateRangeFilter(query, "created_at", from, to)
query = storage.ApplySearchFilter(query, searchTerm, "email", "name", "id")
query = query.Limit(int(commonFilters.Limit))

Filter Types

1. Date Range Filters

  • Format: {field}_from and {field}_to
  • Automatically handles end-of-day adjustment for to dates
  • Supports both YYYY-MM-DD and RFC3339 formats

2. Number Range Filters

  • Format: {field}_min and {field}_max
  • Validates non-negative integers
  • Supports int32 range

3. Boolean Filters

  • Format: has_{field} or is_{field}
  • Tri-state: true, false, or unset (no filter)
  • Uses separate IsActive flag to distinguish false from unset

4. Enum/Select Filters

  • Format: {field}_filter
  • Ignores default values (empty string, “0”)
  • Maps display values to database values
  • Standard search parameter
  • Searches across multiple columns
  • Case-insensitive with pattern matching

Adding Filters to a New Page

Step 1: Define Filter Configuration

Create a new filter configuration function in builder.go:

func NewMyPageFilterConfig() FilterConfig {
return FilterConfig{
TargetURL: "/mypage",
TargetID: "mypage-table-container",
Definitions: []FilterDefinition{
{
ID: "status",
Label: "Status",
Type: FilterTypeSelect,
Placeholder: "All Statuses",
Options: []FilterOption{
{Value: "active", Label: "Active"},
{Value: "inactive", Label: "Inactive"},
},
},
{
ID: "created",
Label: "Created Date",
Type: FilterTypeDateRange,
},
},
}
}

Step 2: Update Handler

func (h *Handlers) HandleMyPage(c *fiber.Ctx) error {
// Parse filters
commonFilters := filters.ParseCommonFilters(c, 25)
statusFilter := filters.ParseEnumFilter(c, "status")
createdRange := filters.ParseDateRange(c, "created")
// Build request
req := &MyPageRequest{
Cursor: commonFilters.Cursor,
Limit: commonFilters.Limit,
Search: commonFilters.Search,
Status: statusFilter,
CreatedFrom: createdRange.From,
CreatedTo: createdRange.To,
}
// ... rest of handler logic
}

Step 3: Update Storage Query

func (s *Storage) ListMyItems(ctx context.Context, filters *MyFilters) ([]*Item, error) {
query := s.db.NewSelect().Model(&items)
// Apply filters
query = ApplySearchFilter(query, filters.Search, "name", "description")
query = ApplyEnumFilter(query, "status", filters.Status)
query = ApplyDateRangeFilter(query, "created_at", filters.CreatedFrom, filters.CreatedTo)
query = query.Limit(int(filters.Limit))
// Execute query
var results []*Item
err := query.Scan(ctx, &results)
return results, err
}

Step 4: Use in Template

The existing filter components in templates/components/filters work automatically with the configuration.

Naming Conventions

Consistent naming ensures filters work correctly across the system:

  • Date ranges: {field}_from, {field}_to
  • Number ranges: {field}_min, {field}_max
  • Boolean filters: has_{field}, is_{field}
  • Enum filters: {field}_filter or just {field}
  • Common parameters: search, cursor, limit, sort_by, sort_direction

URL Parameter Examples

/users?search=john&status=2&created_from=2024-01-01&created_to=2024-12-31
/characters?gender_filter=MALE&has_media=true&sort_by=name&sort_direction=asc
/media?type_filter=MEDIA_TYPE_IMAGE&generation_mode_filter=edit&limit=50

Benefits

  1. DRY Principle: Single implementation for common filter logic
  2. Type Safety: Strongly typed filter definitions and values
  3. Consistency: Uniform parameter naming and behavior
  4. Maintainability: Centralized filter logic easy to update
  5. Extensibility: Simple to add new filter types or pages
  6. Performance: Optimized query builders reduce database load
  7. Backward Compatibility: Maintains existing URL structures

Testing

The refactored filter system maintains backward compatibility with existing URLs and parameters. All existing filters continue to work without changes to frontend code or bookmarked URLs.

To test filters:

  1. Run make lint to check for code issues
  2. Run make run-air to start with hot reload
  3. Test each page’s filters through the UI
  4. Verify pagination works with active filters
  5. Confirm filter persistence across page refreshes