package goatcounter import ( "context" "log/slog" "net/url" ) type SiteClient interface { URL() *url.URL Count(context.Context, ...Hit) (uint64, error) } type MultiSiteClient struct { siteToClient map[string]SiteClient ignoreSites map[string]struct{} } func NewMultiSiteClient(siteToClient map[string]SiteClient, ignoreGoatSites bool) *MultiSiteClient { ignoreSites := map[string]struct{}{} if ignoreGoatSites { for _, client := range siteToClient { ignoreSite := client.URL().Host slog.Debug("ignoring goat site", slog.String("site", ignoreSite)) ignoreSites[ignoreSite] = struct{}{} } } return &MultiSiteClient{ siteToClient: siteToClient, ignoreSites: ignoreSites, } } func (m *MultiSiteClient) Count(ctx context.Context, hits ...Hit) (counted uint64, err error) { siteHits := map[string][]Hit{} for _, hit := range hits { siteHits[hit.Host] = append(siteHits[hit.Host], hit) } for site, hits := range siteHits { hitCount := len(hits) logger := slog.With(slog.String("site", site), slog.Int("count", hitCount)) if _, ignore := m.ignoreSites[site]; ignore { logger.DebugContext(ctx, "ignoring hits for site") continue } client, ok := m.siteToClient[site] if !ok { logger.ErrorContext(ctx, "no client for site, skipping") continue } subCount, err := client.Count(ctx, hits...) if err != nil { logger.ErrorContext(ctx, "failed to count hits", slog.String("err", err.Error())) continue } logger.InfoContext(ctx, "counted hits", slog.Uint64("counted", subCount)) counted += subCount } return counted, nil }