diff --git a/Services/Website/.env.local b/Services/Website/.env.local index 1987239..865548e 100644 --- a/Services/Website/.env.local +++ b/Services/Website/.env.local @@ -1,10 +1,4 @@ -# Copy this file to `.env` and adjust values as needed. -# cp .env.example .env -# -# Compose reads `.env` automatically to fill the ${VAR} placeholders in -# docker-compose.yml. The Website app ALSO reads a `.env` file at runtime via -# swift-configuration (allowMissing: true), so any extra app config keys placed -# here are picked up by the running service too. +# Local `.env` file used solely for Development purposes. # --- Image / deployment ------------------------------------------------------- @@ -39,7 +33,7 @@ IMAGE_TAG=latest HTTP_SERVER_NAME=LoudWebsite # Log verbosity: trace | debug | info | notice | warning | error | critical -LOG_LEVEL=info +LOG_LEVEL=debug # --- Persistence ---------------------------------------------------------------- diff --git a/Services/Website/Dockerfile b/Services/Website/Dockerfile index ec87365..b2ed87f 100644 --- a/Services/Website/Dockerfile +++ b/Services/Website/Dockerfile @@ -19,6 +19,7 @@ WORKDIR /build # a relative path, so its manifest must be present for resolution to succeed. COPY ./Packages/Localization/Package.swift ./Packages/Localization/ COPY ./Packages/Persistence/Package.swift ./Packages/Persistence/ +COPY ./Packages/Web/Package.swift ./Packages/Web/ COPY ./Services/Website/Package.swift ./Services/Website/Package.resolved ./Services/Website/ RUN swift package --package-path ./Services/Website resolve diff --git a/Services/Website/README.md b/Services/Website/README.md index 4293024..27d7fa6 100644 --- a/Services/Website/README.md +++ b/Services/Website/README.md @@ -5,7 +5,6 @@ The **Loud** public website service — a [Hummingbird](https://github.com/hummi The service: - Serves the landing page at `GET /` (rendered once per supported language with [Elementary](https://github.com/elementary-swift/elementary) and cached). - Negotiates each request's language from its `Accept-Language` header against the languages in the `WebsiteLibrary` String Catalog, falling back to the default (`en`); pages are served from the per-language cache with `Content-Language` and `Vary: Accept-Language` headers. -- Registers newsletter subscriptions at `POST /subscribe`: the landing page's form-encoded submission is validated and normalized, guarded by a hidden honeypot field against bots, and stored tagged with the request's negotiated language. - Answers a liveness check at `GET /health` with a static JSON payload, and a readiness check at `GET /health/ready` that reports whether the database is reachable (`200` ready / `503` unavailable). - Serves static files (CSS, JS, icons, manifest, `robots.txt`) from `Resources/Static` via Hummingbird's `FileMiddleware`, tagged with media-type-specific `Cache-Control`. - Returns a custom HTML 404 page, localized like the landing page, for any request that matches neither a route nor a static file. @@ -41,7 +40,6 @@ LogRequestsMiddleware → NotFoundMiddleware (renders the localized 404 page on .notFound) → FileMiddleware (serves Resources/Static) RootController (GET / → landing page) -SubscriptionController (POST /subscribe → newsletter subscription) HealthController (GET /health → liveness, GET /health/ready → readiness) ``` @@ -56,7 +54,7 @@ the following sources, **highest precedence first**: The two files play different roles: - **`.env`** (git-ignored) holds your deployment values — it is the file the Makefile and Compose read for the `${VAR}` placeholders, and typically selects the MySQL/MariaDB backend. -- **`.env.local`** (tracked) holds the local development overrides — the in-memory database and `debug` logging. Because it sits *above* `.env`, a direct launch (`swift run` or a debugger) runs against the local values even when `.env` points at a deployment, the same way `docker-compose.override.yml` overrides the base Compose file. Compose itself never reads it, and the production image does not ship it — only the executable, its resources, and the static files are staged into the final stage. +- **`.env.local`** (tracked) holds the local development values — the in-memory database and `debug` logging, plus the image/deployment placeholders the Makefile falls back to when no `.env` exists. Because it sits *above* `.env`, a direct launch (`swift run` or a debugger) runs against the local values even when `.env` points at a deployment, the same way `docker-compose.override.yml` overrides the base Compose file. Compose itself never reads it, and the production image does not ship it — only the executable, its resources, and the static files are staged into the final stage. ### Environment variable naming A dotted config key maps to an environment variable by upper-casing, splitting camelCase, and replacing separators with `_`. For example `http.serverName` → `HTTP_SERVER_NAME`, diff --git a/Services/Website/Sources/App/App.swift b/Services/Website/Sources/App/App.swift index 4736863..ee99ab9 100644 --- a/Services/Website/Sources/App/App.swift +++ b/Services/Website/Sources/App/App.swift @@ -12,12 +12,16 @@ struct App { /// Loads the configuration and runs the mode it selects. /// /// The configuration is read from the providers in precedence order: command-line arguments first, then process environment variables, then a - /// `.env` file when one is present, and finally the in-memory defaults (currently just the server name). + /// `.env.local` file when one is present, then a `.env` file when one is present, and finally the in-memory defaults. static func main() async throws { let reader = try await ConfigReader( providers: [ CommandLineArgumentsProvider(), EnvironmentVariablesProvider(), + EnvironmentVariablesProvider( + environmentFilePath: ".env.local", + allowMissing: true + ), EnvironmentVariablesProvider( environmentFilePath: ".env", allowMissing: true diff --git a/Services/Website/Tests/Library/Cases/Internal/Pages/ErrorPageTests.swift b/Services/Website/Tests/Library/Cases/Internal/Pages/ErrorPageTests.swift index 6a148e9..3268b64 100644 --- a/Services/Website/Tests/Library/Cases/Internal/Pages/ErrorPageTests.swift +++ b/Services/Website/Tests/Library/Cases/Internal/Pages/ErrorPageTests.swift @@ -4,7 +4,7 @@ import Testing @testable import WebsiteLibrary -@Suite("ErrorPage page") +@Suite("ErrorPage page", .tags(.page)) struct ErrorPageTests { // MARK: Functional tests diff --git a/Services/Website/Tests/Library/Cases/Internal/Pages/IndexPageTests.swift b/Services/Website/Tests/Library/Cases/Internal/Pages/IndexPageTests.swift index 89dd952..a754d77 100644 --- a/Services/Website/Tests/Library/Cases/Internal/Pages/IndexPageTests.swift +++ b/Services/Website/Tests/Library/Cases/Internal/Pages/IndexPageTests.swift @@ -4,7 +4,7 @@ import Testing @testable import WebsiteLibrary -@Suite("IndexPage page") +@Suite("IndexPage page", .tags(.page)) struct IndexPageTests { // MARK: Functional tests diff --git a/Services/Website/Tests/Library/Cases/Public/Controllers/HealthControllerTests.swift b/Services/Website/Tests/Library/Cases/Public/Controllers/HealthControllerTests.swift index f27ec95..9d565bf 100644 --- a/Services/Website/Tests/Library/Cases/Public/Controllers/HealthControllerTests.swift +++ b/Services/Website/Tests/Library/Cases/Public/Controllers/HealthControllerTests.swift @@ -7,7 +7,7 @@ import Testing @testable import WebsiteLibrary -@Suite("HealthController controller") +@Suite("HealthController controller", .tags(.controller)) struct HealthControllerTests { // MARK: Functional tests diff --git a/Services/Website/Tests/Library/Cases/Public/Controllers/RootControllerTests.swift b/Services/Website/Tests/Library/Cases/Public/Controllers/RootControllerTests.swift index 9fb321f..54c3a4f 100644 --- a/Services/Website/Tests/Library/Cases/Public/Controllers/RootControllerTests.swift +++ b/Services/Website/Tests/Library/Cases/Public/Controllers/RootControllerTests.swift @@ -5,7 +5,7 @@ import Testing @testable import WebsiteLibrary -@Suite("RootController controller") +@Suite("RootController controller", .tags(.controller)) struct RootControllerTests { // MARK: Constants diff --git a/Services/Website/Tests/Library/Cases/Public/Middlewares/LocalizationMiddlewareTests.swift b/Services/Website/Tests/Library/Cases/Public/Middlewares/LocalizationMiddlewareTests.swift index 607bc3a..9b71671 100644 --- a/Services/Website/Tests/Library/Cases/Public/Middlewares/LocalizationMiddlewareTests.swift +++ b/Services/Website/Tests/Library/Cases/Public/Middlewares/LocalizationMiddlewareTests.swift @@ -6,7 +6,7 @@ import Testing @testable import WebsiteLibrary -@Suite("LocalizationMiddleware middleware") +@Suite("LocalizationMiddleware middleware", .tags(.middleware)) struct LocalizationMiddlewareTests { // MARK: Constants diff --git a/Services/Website/Tests/Library/Cases/Public/Middlewares/NotFoundMiddlewareTests.swift b/Services/Website/Tests/Library/Cases/Public/Middlewares/NotFoundMiddlewareTests.swift index 5aadb6b..3cffe32 100644 --- a/Services/Website/Tests/Library/Cases/Public/Middlewares/NotFoundMiddlewareTests.swift +++ b/Services/Website/Tests/Library/Cases/Public/Middlewares/NotFoundMiddlewareTests.swift @@ -5,7 +5,7 @@ import Testing @testable import WebsiteLibrary -@Suite("NotFoundMiddleware middleware") +@Suite("NotFoundMiddleware middleware", .tags(.middleware)) struct NotFoundMiddlewareTests { // MARK: Constants diff --git a/Services/Website/Tests/Library/Cases/Public/Middlewares/SecurityHeadersMiddlewareTests.swift b/Services/Website/Tests/Library/Cases/Public/Middlewares/SecurityHeadersMiddlewareTests.swift index 224f303..e987adc 100644 --- a/Services/Website/Tests/Library/Cases/Public/Middlewares/SecurityHeadersMiddlewareTests.swift +++ b/Services/Website/Tests/Library/Cases/Public/Middlewares/SecurityHeadersMiddlewareTests.swift @@ -4,7 +4,7 @@ import Testing @testable import WebsiteLibrary -@Suite("SecurityHeadersMiddleware middleware") +@Suite("SecurityHeadersMiddleware middleware", .tags(.middleware)) struct SecurityHeadersMiddlewareTests { // MARK: Functional tests diff --git a/Services/Website/Tests/Library/Utils/Extensions/Tag+Constants.swift b/Services/Website/Tests/Library/Utils/Extensions/Tag+Constants.swift new file mode 100644 index 0000000..2dee5a2 --- /dev/null +++ b/Services/Website/Tests/Library/Utils/Extensions/Tag+Constants.swift @@ -0,0 +1,10 @@ +import Testing + +extension Tag { + /// Tests exercising a controller of the Website library. + @Tag static var controller: Tag + /// Tests exercising a middleware of the Website library. + @Tag static var middleware: Tag + /// Tests exercising a page of the Website library. + @Tag static var page: Tag +}