Commit Graph
10 Commits
Author SHA1 Message Date
bwilliams 5e61c11eb9 Split ws from jobsearch as a separate test utility rather than a feature of jobsearch 2026-10-16 08:58:25 +01:00
bwilliams a279405cae Added the HTTP server and serve command, with proxy, CSRF, TLS and security header config; wsserve is now ws serve, alongside a new ws dial
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.
2026-10-15 17:47:40 +01:00
bwilliams 043e307663 Logging: structured slog messages throughout, and WithLogger options so callers choose the logger
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.
2026-10-15 16:10:52 +01:00
bwilliams e19c8c2b69 Imports split into three groups: standard library, third-party, then this project's own packages
gci is configured with the sections standard, default and localmodule, and
goimports with the module path as its local prefix, so both formatters
agree.
2026-10-14 17:40:06 +01:00
bwilliams c9f42b62ad Added websocket hub for handling websocket clients, with a wsserve command
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
2026-10-14 15:55:25 +01:00
bwilliams 045492a148 First pass implementation of an SQLite backing store for fetched opportunities 2026-10-13 12:52:17 +01:00
bwilliams 005b335371 Minor fixes:
- 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
2026-10-09 18:26:10 +01:00
bwilliams 399eccf2de Find a Tender API client updates
- 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)
2026-10-09 17:56:55 +01:00
bwilliams e43b7a8370 Added fetch script for UK Gov FindATender service which is based on OCDS (with issues...) 2026-10-09 10:08:11 +01:00
bwilliams 6436253629 Refactored main.go into cmd/jobsearch/main.go (in case separate commands are introduced later) which has required the options mechanism to change slightly as well as the logging. 2026-10-09 09:29:42 +01:00