Skip to content

Refactor repeated list Fetcher closures in UI handlers into shared adapter #174

Description

@cawalch

Problem

Multiple UI list handlers repeat nearly identical Fetcher / FilteredFetcher closures that:

  1. Compute limit, offset := pg.LimitAndOffset()
  2. Call the corresponding service List(ctx, limit, offset) or ListWithOptions(...)
  3. Log an error on failure with "error", err, "page", pg.Page, "page_size", pg.PageSize
  4. Return items, err

Affected handlers (Fetcher closures)

  • ui_secrets.go:28h.Sinks.List(ctx, limit, offset)
  • ui_alert_sinks.go:59h.Sinks.List(ctx, limit, offset)
  • ui_allowlist.go:17h.Allowlist.List(ctx, limit, offset)

Affected handlers (FilteredFetcher closures)

  • ui_sites.go:130 — filter parse + h.Sites.ListWithOptions(...)
  • ui_iocs.go, ui_sources.go, ui_jobs.go — similar patterns with filter structs

Impact

  • Maintenance burden: Adding a new list page means copy-pasting the same 5-line pattern. Changing pagination or error logging requires editing every handler.
  • Drift risk: Some use logger.Error(), others logger.ErrorContext(ctx, ...). Fields and messages vary slightly.

Proposed fix

Create a generic adapter in list_handler.go (or ui_base.go):

// WrapListFetcher adapts a service List method to the Fetcher signature.
func WrapListFetcher[T any](
  svc func(ctx context.Context, limit, offset int) ([]T, error),
  logger *slog.Logger,
  msg string,
) func(ctx context.Context, pg pageOpts) ([]any, error) {
  return func(ctx context.Context, pg pageOpts) ([]any, error) {
    limit, offset := pg.LimitAndOffset()
    items, err := svc(ctx, limit, offset)
    if err != nil {
      logger.ErrorContext(ctx, msg, "error", err, "page", pg.Page, "page_size", pg.PageSize)
    }
    return items, err
  }
}

Each handler would then use a one-liner:

Fetcher: WrapListFetcher(h.Sinks.List, h.logger(), "failed to load alert sinks for UI"),

Files to change

  • services/merrymaker-go/internal/http/list_handler.go — add adapter(s)
  • services/merrymaker-go/internal/http/ui_secrets.go
  • services/merrymaker-go/internal/http/ui_alert_sinks.go
  • services/merrymaker-go/internal/http/ui_allowlist.go
  • services/merrymaker-go/internal/http/ui_sites.go (FilteredFetcher variant)
  • services/merrymaker-go/internal/http/ui_iocs.go
  • services/merrymaker-go/internal/http/ui_sources.go
  • services/merrymaker-go/internal/http/ui_jobs.go

Acceptance criteria

  • All Fetcher/FilteredFetcher closures replaced with adapter calls
  • No behavior change (same log fields, same pagination, same error handling)
  • Lint + tests pass

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestgoPull requests that update go code

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions