sources.skip_rules_file names a JSON file of filters, each with a
comment, an active flag, a logic mode ("or" by default, or "and") and
rules by buyer, title words or CPV codes. The fetcher applies them
before saving, so only new opportunities are marked; the marks of
stored ones are never touched. CPV rules match when the opportunity's
codes are all in the list, or any of them with exclusive false, and
by prefix with exact false.
The file is read again when its time or size changes: an invalid edit
is logged and the last good rules kept, and a deleted file means no
rules until it's back. fetcher service and serve refuse to start if it
is define but missing or invalid.
job-search skip-rules check validates a rules file, and skip-rules
apply marks the stored opportunities that match Skipped, or only lists
them with --dry-run.
job-search no longer has a fetch command. Its subcommands are now in
cmd/fetcher, which reads the same configuration: "fetcher service" runs
every enabled source's fetcher on its interval until stopped, and
"fetcher fts-once" (was "fetch once") fetches from Find a Tender once and
prints the releases as JSON. The new namea say what the commands do,
because service runs every source while fts-once fetches only from Find a
Tender.
The code stays in internal/cli/fetch, where NewCommands replaces the fetch
parent command. once.go is now fts_once.go and commandOnce is
commandFTSOnce. The README's usage, the config README and the find_a_tender
api_root comment now give the new command names.
The serve command runs internal/server: the API under /api (health, and
the CSRF token endpoint) and the WebSocket hub under /ws, over HTTP or TLS,
with an optional HTTP to HTTPS redirect, the PROXY protocol from trusted
sources and a graceful shutdown that closes WebSockets first. Its middleware
covers the client IP behind trusted proxies, request IDs, request logging,
panic recovery, global and API rate limits, Host and body size checks,
security response headers, compression, CSRF protection (cross-origin
checks and signed double-submit tokens) and JSON-only API bodies. Middleware
that logs takes a WithLogger option.
New server config sections: proxy, csrf, tls, headers and frontend (not
used yet), plus max_header_bytes, max_body_bytes, global and API rate limits
and HTTP read, write and idle timeouts. Every setting documents what 0 or
empty means, and internal/config/README.md is reorganised by section.
server.timeouts.read_header must be greater than 0, and a header or idle
timeout of 0 means no limit rather than the read timeout.
WebSocket upgrades are protected by the Origin check: the server's own
origin, or one of csrf.cross_origin_protection.trusted_origins, which the
hub now takes with WithTrustedOrigins.
wsserve becomes "ws serve". "ws dial" opens a WebSocket on /ws to test it:
--ping sends a ping and waits for the pong, --message sends messages and
prints the replies, --origin tests the Origin check, and with neither
--ping nor --message each line of stdin is sent until Ctrl+C.
Log messages are lower-case event names with their details as key/value
attributes ("error" for errors), in place of formatted strings.
Loggable package take a WithLogger option as part of their "New*" call,
defaulting to slog.Default(). Each tags its lines with its `component`.
Some debug loggers print directly rather than using slog when in
terminal logging mode.
gci is configured with the sections standard, default and localmodule, and
goimports with the module path as its local prefix, so both formatters
agree.
which runs a plain TCP server for receiving websocket hub clients, with
server config, CSRF and rate limit middleware.
- internal/websocket: Hub pushing messages to WebSocket clients (ServeHTTP,
example.com/ws upgrade with optional CSRF check) and plain TCP clients (ServeTCP,
newline-delimited JSON envelopes); ping/pong keepalive, per-client message
rate limit, slow consumer handling, graceful Shutdown, debug logging of
messages sent and received
- internal/websocket/tcpserver: standalone TCP server for a hub, with accept
back-off, a per-IP connection limit (server.websocket.connection_limit) and
graceful shutdown
- internal/cli/wsserve: `wsserve` command running the TCP server, with
--listen/--host/--port overrides
- internal/middleware: keyed token bucket rate limiter, real client IP from
trusted proxies
- internal/httpx: RFC 9457 problem responses, request IDs in context
- internal/config: server section (host, port, shutdown timeout, rate limiter
idle TTL, websocket); util.ByteSize settings (e.g. "32KiB"); lists of
sections as single keys; empty lists stored as nil; isExplicitlySet for
printLoadedConfig; JobSearchConfigSource renamed to JSCSourcesSource;
TestSections checks every section's methods
- internal/util: ByteSize, parsed and formatted with go-humanize
- Fixed function name typo
- Fetcher/Run now returns a combination of errors if any workers error out. All other workers are ordered to shutdown if any one does error out
- findATender now returns a pointer instead of an instance from New
- Convert OCDS releases to types.Opportunity (internal/types); FetchOnce
and FetchAll now return opportunities, built from each ocid's latest
release
- Include each item's main classification as well as its additional ones
- Return ErrUnexpectedStatus for non-200 responses and
ErrRateLimitExceeded for a repeated 429; close discarded bodies
- Stop fetchAll on a repeated next link and close each page's body once
read rather than deferring in the loop
- Default a zero Since to 24 hours ago
- Merge date repair and releaseIDs into api.go
- Filter --planning/--opportunities/--awards on each procedure's current
stage, using OCDS release tags; --opportunities previously matched a
nonexistent "opportunity" tag and returned nothing
- Reject conflicting filters before fetching (cobra flag group)