Project updates from Template #1
@@ -1,10 +1,4 @@
|
|||||||
# Copy this file to `.env` and adjust values as needed.
|
# Local `.env` file used solely for Development purposes.
|
||||||
# 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.
|
|
||||||
|
|
||||||
# --- Image / deployment -------------------------------------------------------
|
# --- Image / deployment -------------------------------------------------------
|
||||||
|
|
||||||
@@ -39,7 +33,7 @@ IMAGE_TAG=latest
|
|||||||
HTTP_SERVER_NAME=LoudWebsite
|
HTTP_SERVER_NAME=LoudWebsite
|
||||||
|
|
||||||
# Log verbosity: trace | debug | info | notice | warning | error | critical
|
# Log verbosity: trace | debug | info | notice | warning | error | critical
|
||||||
LOG_LEVEL=info
|
LOG_LEVEL=debug
|
||||||
|
|
||||||
# --- Persistence ----------------------------------------------------------------
|
# --- Persistence ----------------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ WORKDIR /build
|
|||||||
# a relative path, so its manifest must be present for resolution to succeed.
|
# a relative path, so its manifest must be present for resolution to succeed.
|
||||||
COPY ./Packages/Localization/Package.swift ./Packages/Localization/
|
COPY ./Packages/Localization/Package.swift ./Packages/Localization/
|
||||||
COPY ./Packages/Persistence/Package.swift ./Packages/Persistence/
|
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/
|
COPY ./Services/Website/Package.swift ./Services/Website/Package.resolved ./Services/Website/
|
||||||
RUN swift package --package-path ./Services/Website resolve
|
RUN swift package --package-path ./Services/Website resolve
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ The **Loud** public website service — a [Hummingbird](https://github.com/hummi
|
|||||||
The service:
|
The service:
|
||||||
- Serves the landing page at `GET /` (rendered once per supported language with [Elementary](https://github.com/elementary-swift/elementary) and cached).
|
- 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.
|
- 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).
|
- 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`.
|
- 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.
|
- 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)
|
→ NotFoundMiddleware (renders the localized 404 page on .notFound)
|
||||||
→ FileMiddleware (serves Resources/Static)
|
→ FileMiddleware (serves Resources/Static)
|
||||||
RootController (GET / → landing page)
|
RootController (GET / → landing page)
|
||||||
SubscriptionController (POST /subscribe → newsletter subscription)
|
|
||||||
HealthController (GET /health → liveness, GET /health/ready → readiness)
|
HealthController (GET /health → liveness, GET /health/ready → readiness)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -56,7 +54,7 @@ the following sources, **highest precedence first**:
|
|||||||
|
|
||||||
The two files play different roles:
|
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`** (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
|
### 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`,
|
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`,
|
||||||
|
|||||||
@@ -12,12 +12,16 @@ struct App {
|
|||||||
/// Loads the configuration and runs the mode it selects.
|
/// 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
|
/// 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 {
|
static func main() async throws {
|
||||||
let reader = try await ConfigReader(
|
let reader = try await ConfigReader(
|
||||||
providers: [
|
providers: [
|
||||||
CommandLineArgumentsProvider(),
|
CommandLineArgumentsProvider(),
|
||||||
EnvironmentVariablesProvider(),
|
EnvironmentVariablesProvider(),
|
||||||
|
EnvironmentVariablesProvider(
|
||||||
|
environmentFilePath: ".env.local",
|
||||||
|
allowMissing: true
|
||||||
|
),
|
||||||
EnvironmentVariablesProvider(
|
EnvironmentVariablesProvider(
|
||||||
environmentFilePath: ".env",
|
environmentFilePath: ".env",
|
||||||
allowMissing: true
|
allowMissing: true
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Testing
|
|||||||
|
|
||||||
@testable import WebsiteLibrary
|
@testable import WebsiteLibrary
|
||||||
|
|
||||||
@Suite("ErrorPage page")
|
@Suite("ErrorPage page", .tags(.page))
|
||||||
struct ErrorPageTests {
|
struct ErrorPageTests {
|
||||||
|
|
||||||
// MARK: Functional tests
|
// MARK: Functional tests
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import Testing
|
|||||||
|
|
||||||
@testable import WebsiteLibrary
|
@testable import WebsiteLibrary
|
||||||
|
|
||||||
@Suite("IndexPage page")
|
@Suite("IndexPage page", .tags(.page))
|
||||||
struct IndexPageTests {
|
struct IndexPageTests {
|
||||||
|
|
||||||
// MARK: Functional tests
|
// MARK: Functional tests
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Testing
|
|||||||
|
|
||||||
@testable import WebsiteLibrary
|
@testable import WebsiteLibrary
|
||||||
|
|
||||||
@Suite("HealthController controller")
|
@Suite("HealthController controller", .tags(.controller))
|
||||||
struct HealthControllerTests {
|
struct HealthControllerTests {
|
||||||
|
|
||||||
// MARK: Functional tests
|
// MARK: Functional tests
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Testing
|
|||||||
|
|
||||||
@testable import WebsiteLibrary
|
@testable import WebsiteLibrary
|
||||||
|
|
||||||
@Suite("RootController controller")
|
@Suite("RootController controller", .tags(.controller))
|
||||||
struct RootControllerTests {
|
struct RootControllerTests {
|
||||||
|
|
||||||
// MARK: Constants
|
// MARK: Constants
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ import Testing
|
|||||||
|
|
||||||
@testable import WebsiteLibrary
|
@testable import WebsiteLibrary
|
||||||
|
|
||||||
@Suite("LocalizationMiddleware middleware")
|
@Suite("LocalizationMiddleware middleware", .tags(.middleware))
|
||||||
struct LocalizationMiddlewareTests {
|
struct LocalizationMiddlewareTests {
|
||||||
|
|
||||||
// MARK: Constants
|
// MARK: Constants
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ import Testing
|
|||||||
|
|
||||||
@testable import WebsiteLibrary
|
@testable import WebsiteLibrary
|
||||||
|
|
||||||
@Suite("NotFoundMiddleware middleware")
|
@Suite("NotFoundMiddleware middleware", .tags(.middleware))
|
||||||
struct NotFoundMiddlewareTests {
|
struct NotFoundMiddlewareTests {
|
||||||
|
|
||||||
// MARK: Constants
|
// MARK: Constants
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ import Testing
|
|||||||
|
|
||||||
@testable import WebsiteLibrary
|
@testable import WebsiteLibrary
|
||||||
|
|
||||||
@Suite("SecurityHeadersMiddleware middleware")
|
@Suite("SecurityHeadersMiddleware middleware", .tags(.middleware))
|
||||||
struct SecurityHeadersMiddlewareTests {
|
struct SecurityHeadersMiddlewareTests {
|
||||||
|
|
||||||
// MARK: Functional tests
|
// MARK: Functional tests
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user