HTML rendering support for the Website service (#5)

This PR contains the work done to replace the use of static _HTML_ files with type-safe HTML rendered server-side via **Elementary** through **Hummingbird**.

To provide further details about the work done:

* Added the **Elementary** dependencies.
* Added the `IndexPage` and `ErrorPage` pages, ported from the old HTML boilerplate; removed the static files.
* Added the `RootController` controller serving GET / using the `IndexPage` page, wired into the router.
* Reworked the `NotFoundMiddleware` middleare to render `ErrorPage` page directly; non-notFound errors still propagate.
* the `FileMiddleware` middleware no longer searches for any static `index.html` file.
* Simplified the `StaticFile` enumeration, dropped unused constants and now-unnecessary throws.

Reviewed-on: rock-n-code/loud-amsterdam#5
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-06-28 05:07:19 +00:00
committed by javier
parent f5f14eb97c
commit a621ada0bf
17 changed files with 460 additions and 244 deletions
@@ -1,5 +1,3 @@
import Foundation
/// A static file shipped with the website service.
///
/// Each case identifies a file stored under the static files root (the `Resources/Static`
@@ -7,16 +5,12 @@ import Foundation
enum StaticFile: CaseIterable, Sendable {
/// The `js/app.js` script.
case appJS
/// The `404.html` error page.
case errorHTML
/// The `favicon.ico` icon.
case faviconICO
/// The `icon.png` icon.
case iconPNG
/// The `icon.svg` icon.
case iconSVG
/// The `index.html` landing page.
case indexHTML
/// The `robots.txt` crawler directives.
case robotsTXT
/// The `site.webmanifest` web application manifest.
@@ -32,8 +26,6 @@ extension StaticFile {
enum Extension: String, Sendable {
/// A Cascading Style Sheets file.
case css
/// A HyperText Markup Language file.
case html
/// A JavaScript file.
case js
/// A Portable Network Graphics image.
@@ -59,7 +51,6 @@ extension StaticFile {
var contentType: String {
switch fileExtension {
case .css: "text/css"
case .html: "text/html"
case .js: "text/javascript"
case .png: "image/png"
case .ico: "image/vnd.microsoft.icon"
@@ -74,8 +65,6 @@ extension StaticFile {
switch self {
case .styleCSS: .css
case .appJS: .js
case .errorHTML,
.indexHTML: .html
case .faviconICO: .ico
case .iconPNG: .png
case .iconSVG: .svg
@@ -88,11 +77,9 @@ extension StaticFile {
var fileName: String {
switch self {
case .appJS: "app"
case .errorHTML: "404"
case .faviconICO: "favicon"
case .iconPNG,
.iconSVG: "icon"
case .indexHTML: "index"
case .robotsTXT: "robots"
case .siteWebmanifest: "site"
case .styleCSS: "style"
@@ -103,18 +90,10 @@ extension StaticFile {
///
/// This also matches the URL path the file is served at by `FileMiddleware`.
var relativePath: String {
let file = String(
format: Constant.Format.file,
fileName,
fileExtension.rawValue
)
let file = "\(fileName).\(fileExtension.rawValue)"
return subdirectory
.map { .init(
format: Constant.Format.path,
$0,
file
)} ?? file
.map { "\($0)/\(file)" } ?? file
}
// MARK: Methods
@@ -129,12 +108,8 @@ extension StaticFile {
guard !basePath.isEmpty else {
return relativePath
}
return .init(
format: Constant.Format.path,
basePath,
relativePath
)
return "\(basePath)/\(relativePath)"
}
}
@@ -155,12 +130,3 @@ private extension StaticFile {
}
}
// MARK: - Constants
private enum Constant {
enum Format {
static let file = "%@.%@"
static let path = "%@/%@"
}
}