Documentation
¶
Overview ¶
Package saasfeed is the SaaS category feed syncer: it periodically fetches a curated JSON file of SaaS URL categories (AI, Marketing, Messaging, …) from a remote URL and hands the parsed categories to an injected merge callback. Extracted from package main per ADR-0002 (five-seam design recorded there): the category store lives in main's policy engine, so the MERGE is main's closure over catStore; the LIFECYCLE context provider is injected (the sync loop must die with the process, P6.1 UC-3); the CLIENT is injected (main builds it on ssrf.SafeDialContext); the sync-failure COUNTER is package-owned (SyncFailures, read by urlcat_metrics.go); and test construction goes through New/SeedStats/SetFeedURLForTest instead of struct-literal field pokes.
Sync interval defaults to 24h. Admin can change the URL via the GUI to point at a private fork or internal mirror. Merge strategy is additive only (implemented by the injected callback).
Index ¶
- Constants
- func SyncFailures() int64
- type Category
- type Deps
- type Syncer
- func (s *Syncer) Configure(feedURL string, interval time.Duration)
- func (s *Syncer) Done() <-chan struct{}
- func (s *Syncer) Enabled() bool
- func (s *Syncer) FeedURL() string
- func (s *Syncer) SeedStats(lastSync time.Time, count int64)
- func (s *Syncer) SetFeedURLForTest(feedURL string)
- func (s *Syncer) Stats() (url string, lastSync time.Time, count int64, interval time.Duration)
- func (s *Syncer) Stop()
- func (s *Syncer) Sync(ctx context.Context)
Constants ¶
const DefaultFeedURL = "https://raw.githubusercontent.com/KidCarmi/Culvert/main/internal/urlcat/default_categories.json"
DefaultFeedURL is the built-in feed location (the Culvert GitHub repo); package main's urlcategories startup slice uses it as the config default. The path tracks the file's in-repo home (internal/urlcat since the ADR-0002 urlcat extraction — a stale root path here would 404 every default-URL deployment).
Variables ¶
This section is empty.
Functions ¶
func SyncFailures ¶
func SyncFailures() int64
SyncFailures returns the cumulative SaaS feed sync failure count.
Types ¶
type Category ¶
Category is the feed's wire type: one named URL category with its host list. JSON-compatible with main's CategoryEntry (whose builtIn field the feed does not carry).
type Deps ¶
type Deps struct {
// Client performs the feed fetches. nil → a 30s-timeout default client
// WITHOUT the SSRF-safe dialer; production (package main) always passes
// a client built on ssrf.SafeDialContext.
Client *http.Client
// Merge folds parsed categories into the category store and returns the
// number of hosts added (it owns persistence of the store). nil → the
// fetch/parse result is dropped with added=0 (test convenience).
Merge func(categories []Category) (added int)
// Lifecycle returns the context the sync loop is parented to, resolved
// at Configure time (P6.1 UC-3: process shutdown must cancel the loop).
// nil → context.Background().
Lifecycle func() context.Context
}
Deps carries the injectable collaborators for New.
type Syncer ¶
type Syncer struct {
// contains filtered or unexported fields
}
Syncer periodically fetches the feed and merges it via the injected callback.
func New ¶
New builds a Syncer from injected collaborators. The syncer starts idle (Configure enables it); interval defaults to 24h.
func (*Syncer) Done ¶
func (s *Syncer) Done() <-chan struct{}
Done returns the channel that closes when the current syncLoop goroutine exits, or nil when the syncer is not running. Provided as a test seam so the P6.1 UC-3 regression can wait deterministically for the goroutine to actually finish after the lifecycle context is cancelled; production code does not call this today.
func (*Syncer) SeedStats ¶
SeedStats sets the last-sync timestamp and added-count directly. Test support for the metrics surface (production values are set by Sync).
func (*Syncer) SetFeedURLForTest ¶
SetFeedURLForTest sets the feed URL without starting the sync loop, so a test can drive Sync synchronously (e.g. the invalid-URL failure-counter regression). Production code configures via Configure.