Tweaks and fixes throughout the project (#27)

This PR contains the work done to do a little bit of housekeeping pass across all packages and the Website service.

To provide further details about the work:
* Refreshed the READMEs and source documentation to match the current code;
* Tagged every test case consistently across the Infrastructure, Localization, Persistence, and Website test targets;
* Removed Website middleware tests now covered by Infrastructure's own suite;
* Conformed the `PrepareDB` method to Sendable;
* Relaxes the production Compose DATABASE_TLS default from require to prefer;
* Added Persistence test verifying the prefer posture falls back to plaintext connections.

Reviewed-on: rock-n-code/loud-amsterdam#27
Co-authored-by: Javier Cicchelli <javier@rock-n-code.com>
Co-committed-by: Javier Cicchelli <javier@rock-n-code.com>
This commit is contained in:
2026-07-30 06:33:57 +00:00
committed by javier
parent 0887135328
commit ea00b94841
71 changed files with 633 additions and 512 deletions
+3 -3
View File
@@ -31,9 +31,9 @@ struct App {
]
)
// Migrate-and-exit mode runs the registered migrations against the configured backend and returns,
// so a shared database is migrated by a single deliberate invocation (`--database-migrate`) rather
// than by every booting instance.
// Migrate-and-exit mode runs the registered migrations against the configured backend and returns, so a shared
// database is migrated by a single deliberate invocation (`--database-migrate`) rather than by every booting
// instance.
guard !reader.migrate else {
try await migration(
reader: reader
@@ -25,8 +25,8 @@ func application(
logLevel: reader.logLevel
)
// A broken catalog degrades to serving raw localization keys rather than failing, so it is
// only ever visible to visitors surface it here instead.
// A broken catalog degrades to serving raw localization keys rather than failing, so it is only ever visible to
// visitors surface it here instead.
if languages.catalogState != .loaded {
let isCatalogMissing = languages.catalogState == .missing
@@ -63,8 +63,8 @@ func application(
app.addServices(fluent)
// The in-memory backend is recreated on every launch, so it is migrated on startup. The MySQL/MariaDB
// backend is left untouched here: a shared database is migrated out of band to avoid multi-instance races.
// The in-memory backend is recreated on every launch, so it is migrated on startup. The MySQL/MariaDB backend is
// left untouched here: a shared database is migrated out of band to avoid multi-instance races.
if case .inMemory = reader.driver {
app.beforeServerStarts {
try await fluent.migrate()
@@ -137,8 +137,7 @@ private func logger(
/// larger than `minimumResponseSizeToCompress` when the client advertises support, the localization middleware that negotiates the request's
/// language from its `Accept-Language` header, the not-found middleware that serves the error page, and the static file middleware that serves the
/// contents of `staticFilesPath` (tagging responses with the given `cacheControl` directives), then adds the `RootController` routes that
/// render the landing page, the `SubscriptionController` routes that register newsletter subscriptions, and the `HealthController` routes
/// that serve the health check.
/// render the landing page, and the `HealthController` routes that serve the health check.
///
/// The security-headers middleware sits just inside request logging so it covers every response that reaches a client the landing page, the compressed
/// responses, the rendered error page, and the served static files.
@@ -162,8 +161,8 @@ private func router(
logLevel: Logger.Level,
probe: Probe
) -> Router<AppRequestContext> {
// HEAD siblings are generated for every GET route, so uptime monitors and crawlers probing
// with HEAD requests get the page's status and headers instead of a 404.
// HEAD siblings are generated for every GET route, so uptime monitors and crawlers probing with HEAD requests get
// the page's status and headers instead of a 404.
let router = Router(
context: AppRequestContext.self,
options: .autoGenerateHeadEndpoints
@@ -123,9 +123,9 @@ package extension ConfigReader {
/// The rate limit applied to the subscription endpoint, built from the `rateLimit.*` keys.
///
/// `rateLimit.limit` requests are admitted per client per `rateLimit.window` seconds. When
/// `rateLimit.trustForwardedFor` is set, clients are keyed by the first `X-Forwarded-For` entry
/// enable it only behind a reverse proxy that sets the header, since clients can forge it otherwise.
/// `rateLimit.limit` requests are admitted per client per `rateLimit.window` seconds. When `rateLimit.trustForwardedFor` is set,
/// clients are keyed by the first `X-Forwarded-For` entry enable it only behind a reverse proxy that sets the header, since clients can forge it
/// otherwise.
var rateLimit: RateLimitMiddleware<AppRequestContext>.Configuration {
.init(
limit: int(
@@ -2,9 +2,8 @@ import Infrastructure
/// A static file shipped with the website service.
///
/// Each case identifies a file name stored under the static files root (the `Resources/Static`
/// directory) and served by Hummingbird's `FileMiddleware` middleware. A name can be available
/// with more than one extension (see ``fileExtensions``), each resolving to its own file.
/// Each case identifies a file name stored under the static files root (the `Resources/Static` directory) and served by Hummingbird's
/// `FileMiddleware` middleware. A name can be available with more than one extension (see ``fileExtensions``), each resolving to its own file.
enum StaticFile: Asset, CaseIterable {
/// The `apple-touch-icon.png` icon.
case appleTouchIcon
@@ -22,8 +22,7 @@ struct IndexPage {
/// Creates a landing page localized to the given locale.
/// - Parameters:
/// - locale: the locale the page content is localized to.
/// - assetVersion: the version token appended to the page's asset URLs, or `nil` (the
/// default) to leave them unversioned.
/// - assetVersion: the version token appended to the page's asset URLs, or `nil` (the default) to leave them unversioned.
init(
locale: Locale,
assetVersion: String? = nil
@@ -4,9 +4,8 @@ import NIOCore
/// The website's request context.
///
/// Extends the core request storage with the negotiated language, defaulting to the default
/// supported language until ``LocalizationMiddleware`` resolves it from the request, and with
/// the connected client's address, so ``RateLimitMiddleware`` can key its budgets per client.
/// Extends the core request storage with the negotiated language, defaulting to the default supported language until ``LocalizationMiddleware``
/// resolves it from the request, and with the connected client's address, so ``RateLimitMiddleware`` can key its budgets per client.
public struct WebsiteRequestContext: LocalizedRequestContext, RemoteAddressRequestContext {
// MARK: Properties
@@ -28,8 +28,7 @@ public struct HealthController<Context: RequestContext> {
// MARK: Initializers
/// Creates a health controller.
/// - Parameter probe: the probe consulted by the readiness route; when `nil`, only the liveness
/// route is served.
/// - Parameter probe: the probe consulted by the readiness route; when `nil`, only the liveness route is served.
public init(
probe: Probe? = nil
) {
@@ -72,9 +71,8 @@ private extension HealthController {
/// Handles a request for the liveness check.
///
/// Returns a constant JSON body built directly per request the payload is a tiny literal with no
/// rendering step, so there is nothing to pre-render or cache. It reports only that the process is up,
/// with no dependency check, so an orchestrator restarts the process only when the process itself is
/// Returns a constant JSON body built directly per request the payload is a tiny literal with no rendering step, so there is nothing to pre-render or
/// cache. It reports only that the process is up, with no dependency check, so an orchestrator restarts the process only when the process itself is
/// unresponsive.
/// - Parameters:
/// - request: the incoming request.
@@ -93,9 +91,8 @@ private extension HealthController {
/// Handles a request for the readiness check.
///
/// Consults the `Probe` supplied at initialization and reports `200 OK` when the service's
/// database is reachable, or `503 Service Unavailable` otherwise, so a load balancer withholds
/// traffic from an instance that cannot yet serve it without restarting the process.
/// Consults the `Probe` supplied at initialization and reports `200 OK` when the service's database is reachable, or `503 Service Unavailable`
/// otherwise, so a load balancer withholds traffic from an instance that cannot yet serve it without restarting the process.
/// - Parameters:
/// - request: the incoming request.
/// - context: the context the request is resolved against.
@@ -4,8 +4,7 @@ import Infrastructure
/// Serves the website's root routes.
///
/// The controller exposes its routes through its `RouterController` conformance, so the
/// application that composes it registers them declaratively:
/// The controller exposes its routes through its `RouterController` conformance, so the application that composes it registers them declaratively:
///
/// ```swift
/// router.addController {
@@ -13,8 +12,7 @@ import Infrastructure
/// }
/// ```
///
/// - Note: `Context` is the request context the routes are resolved against, and must match the
/// context of the router the routes are added to.
/// - Note: `Context` is the request context the routes are resolved against, and must match the context of the router the routes are added to.
public struct RootController<Context: LocalizedRequestContext> {
// MARK: Properties
@@ -25,8 +23,7 @@ public struct RootController<Context: LocalizedRequestContext> {
// MARK: Initializers
/// Creates a root controller.
/// - Parameter assetVersion: the version token appended to the page's asset URLs, or `nil`
/// (the default) to leave them unversioned.
/// - Parameter assetVersion: the version token appended to the page's asset URLs, or `nil` (the default) to leave them unversioned.
public init(
assetVersion: String? = nil
) {
@@ -67,8 +64,7 @@ private extension RootController {
/// Handles a request for the landing page.
///
/// Renders the ``IndexPage`` in the language stored on the context by ``LocalizationMiddleware``,
/// falling back to the default language.
/// Renders the ``IndexPage`` in the language stored on the context by ``LocalizationMiddleware``, falling back to the default language.
/// - Parameters:
/// - request: the incoming request.
/// - context: the context the request is resolved against.