Implemented phase 1 for the POC prototype in the Website service.
This commit is contained in:
1 parent
6d7ab4bcba
commit
ea89ea892a
37 files changed
+4173
-43
No files matched your search
@@ -11,8 +11,8 @@ import WebsiteLibrary
|
||||
/// Reads the log level, server name, static files location, minimum response size to compress, and security headers from the configuration, then assembles
|
||||
/// the router, server configuration, and logger. It warns when the localization catalog cannot be read, since pages would serve raw localization keys.
|
||||
/// It also builds the persistence driver, registers its migrations, and attaches the `Fluent` service so it starts
|
||||
/// and stops alongside the HTTP server; the ephemeral in-memory backend is migrated on startup, while a PostgreSQL backend is migrated out of
|
||||
/// band (so a shared database is never migrated on boot).
|
||||
/// and stops alongside the HTTP server; the ephemeral in-memory backend is migrated here, before the landing page reads its listings, while a
|
||||
/// PostgreSQL backend is migrated out of band (so a shared database is never migrated on boot).
|
||||
/// - Parameter reader: the configuration reader the values are read from.
|
||||
/// - Returns: the configured application, ready to run as a service.
|
||||
/// - Throws: a ``ConfigError`` when a `database.*` key holds an unrecognized token, or an error when the persistence service cannot be built
|
||||
@@ -46,6 +46,15 @@ func application(
|
||||
|
||||
await prepareDB(for: fluent)
|
||||
|
||||
// The in-memory backend is recreated on every launch, so it is migrated — and seeded — before the landing page
|
||||
// reads its listings. The PostgreSQL backend is left untouched: a shared database is migrated out of band to avoid
|
||||
// multi-instance races.
|
||||
if case .inMemory = driver {
|
||||
try await fluent.migrate()
|
||||
}
|
||||
|
||||
let repository = CatalogueRepository(fluent: fluent)
|
||||
|
||||
var app = Application(
|
||||
router: router(
|
||||
staticFilesPath: reader.staticFilesPath,
|
||||
@@ -60,7 +69,12 @@ func application(
|
||||
// building both against an empty host.
|
||||
siteOrigin: reader.siteOrigin.isEmpty ? nil : reader.siteOrigin,
|
||||
logLevel: reader.logLevel,
|
||||
probe: Probe(fluent: fluent)
|
||||
probe: Probe(fluent: fluent),
|
||||
repository: repository,
|
||||
listings: await homeListings(
|
||||
from: repository,
|
||||
logger: logger
|
||||
)
|
||||
),
|
||||
configuration: ApplicationConfiguration(
|
||||
reader: reader.scoped(to: "http")
|
||||
@@ -70,14 +84,6 @@ func application(
|
||||
|
||||
app.addServices(fluent)
|
||||
|
||||
// The in-memory backend is recreated on every launch, so it is migrated on startup. The PostgreSQL backend is
|
||||
// left untouched here: a shared database is migrated out of band to avoid multi-instance races.
|
||||
if case .inMemory = driver {
|
||||
app.beforeServerStarts {
|
||||
try await fluent.migrate()
|
||||
}
|
||||
}
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
@@ -121,6 +127,26 @@ func migration(
|
||||
/// The request context type the application serves its routes with.
|
||||
private typealias AppRequestContext = WebsiteRequestContext
|
||||
|
||||
/// Reads the listings the landing page shows, once: the page is rendered at startup and cached.
|
||||
///
|
||||
/// A catalogue that cannot be read — a PostgreSQL backend not migrated yet — leaves the page without its rows rather than failing the launch.
|
||||
/// - Parameters:
|
||||
/// - repository: the repository the listings are read through.
|
||||
/// - logger: the logger the failure is reported to.
|
||||
/// - Returns: the landing page's listings, or none when the catalogue cannot be read.
|
||||
private func homeListings(
|
||||
from repository: CatalogueRepository,
|
||||
logger: Logger
|
||||
) async -> HomeListings {
|
||||
do {
|
||||
return .init(from: try await repository.listings())
|
||||
} catch {
|
||||
logger.warning("Catalogue is unreadable; the landing page will show no listings: \(error)")
|
||||
|
||||
return .init()
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds the application's logger.
|
||||
/// - Parameters:
|
||||
/// - serverName: the label applied to the logger.
|
||||
@@ -156,6 +182,8 @@ private func logger(
|
||||
/// - siteOrigin: the public origin the pages derive their canonical URLs and language alternates from, or `nil` to omit them.
|
||||
/// - logLevel: the level the request-logging middleware logs at.
|
||||
/// - probe: the probe consulted by the `HealthController` readiness route.
|
||||
/// - repository: the repository the `CatalogueController` reads the shops and listings through.
|
||||
/// - listings: the listings the landing page shows in its rows.
|
||||
/// - Returns: the configured router.
|
||||
private func router(
|
||||
staticFilesPath: String,
|
||||
@@ -168,7 +196,9 @@ private func router(
|
||||
securityHeaders: SecurityHeadersMiddleware<AppRequestContext>.Configuration,
|
||||
siteOrigin: String?,
|
||||
logLevel: Logger.Level,
|
||||
probe: Probe
|
||||
probe: Probe,
|
||||
repository: CatalogueRepository,
|
||||
listings: HomeListings
|
||||
) -> 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.
|
||||
@@ -204,6 +234,12 @@ private func router(
|
||||
RootController<AppRequestContext>(
|
||||
assetVersion: assetVersion,
|
||||
siteOrigin: siteOrigin,
|
||||
analytics: analytics,
|
||||
listings: listings
|
||||
)
|
||||
CatalogueController<AppRequestContext>(
|
||||
repository: repository,
|
||||
assetVersion: assetVersion,
|
||||
analytics: analytics
|
||||
)
|
||||
HealthController<AppRequestContext>(
|
||||
|
||||
Reference in new issue
Block a user