This PR contains the work done to rename the _Website_ service's library SwiftPM target from `WebsiteCore` to `WebsiteLibrary`, updating every reference across the manifest, source, tests, Xcode schemes, and documentations. Reviewed-on: rock-n-code/loud-amsterdam#14 Co-authored-by: Javier Cicchelli <javier@rock-n-code.com> Co-committed-by: Javier Cicchelli <javier@rock-n-code.com>
167 lines
6.0 KiB
Swift
167 lines
6.0 KiB
Swift
import Configuration
|
|
import Hummingbird
|
|
import HummingbirdCompression
|
|
import Logging
|
|
import Persistence
|
|
import WebsiteLibrary
|
|
|
|
/// Builds the website application.
|
|
///
|
|
/// 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 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 MySQL/MariaDB 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.
|
|
func application(
|
|
reader: ConfigReader
|
|
) async -> some ApplicationProtocol {
|
|
let logger = logger(
|
|
serverName: reader.serverName,
|
|
logLevel: reader.logLevel
|
|
)
|
|
let persistence = Service(
|
|
driver: reader.driver,
|
|
logger: logger
|
|
)
|
|
let fluent = persistence()
|
|
let prepareDB = PrepareDB()
|
|
|
|
await prepareDB(for: fluent)
|
|
|
|
var app = Application(
|
|
router: router(
|
|
staticFilesPath: reader.staticFilesPath,
|
|
cacheControl: reader.cacheControl,
|
|
compressionMinResponseSize: reader.compressionMinResponseSize,
|
|
securityHeaders: reader.securityHeaders,
|
|
logLevel: reader.logLevel,
|
|
probe: Probe(fluent: fluent)
|
|
),
|
|
configuration: ApplicationConfiguration(
|
|
reader: reader.scoped(to: "http")
|
|
),
|
|
logger: logger
|
|
)
|
|
|
|
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.
|
|
if case .inMemory = reader.driver {
|
|
app.beforeServerStarts {
|
|
try await fluent.migrate()
|
|
}
|
|
}
|
|
|
|
return app
|
|
}
|
|
|
|
/// Runs every registered migration against the configured backend, then exits.
|
|
///
|
|
/// This is the out-of-band migration path selected by the `database.migrate` flag: it builds the same driver the service would run against, applies the
|
|
/// migrations, and shuts the database down — so a shared MySQL/MariaDB database is migrated by a single deliberate invocation rather than by every
|
|
/// booting instance.
|
|
/// - Parameter reader: the configuration reader the values are read from.
|
|
func migration(
|
|
reader: ConfigReader
|
|
) async throws {
|
|
let logger = logger(
|
|
serverName: reader.serverName,
|
|
logLevel: reader.logLevel
|
|
)
|
|
let service = Service(
|
|
driver: reader.driver,
|
|
logger: logger
|
|
)
|
|
|
|
let fluent = service()
|
|
let prepareDB = PrepareDB()
|
|
|
|
await prepareDB(for: fluent)
|
|
|
|
do {
|
|
try await fluent.migrate()
|
|
}
|
|
catch {
|
|
try? await fluent.shutdown()
|
|
|
|
throw error
|
|
}
|
|
|
|
try await fluent.shutdown()
|
|
}
|
|
|
|
// MARK: - Helpers
|
|
|
|
/// The request context type the application serves its routes with.
|
|
private typealias AppRequestContext = WebsiteRequestContext
|
|
|
|
/// Builds the application's logger.
|
|
/// - Parameters:
|
|
/// - serverName: the label applied to the logger.
|
|
/// - logLevel: the minimum level the logger emits.
|
|
/// - Returns: the configured logger.
|
|
private func logger(
|
|
serverName: String,
|
|
logLevel: Logger.Level
|
|
) -> Logger {
|
|
var logger = Logger(label: serverName)
|
|
|
|
logger.logLevel = logLevel
|
|
|
|
return logger
|
|
}
|
|
|
|
/// Builds the application's router.
|
|
///
|
|
/// Registers the request-logging middleware, the security-headers middleware that stamps the given `securityHeaders` onto every response, the
|
|
/// response-compression middleware that compresses responses 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 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.
|
|
/// - Parameters:
|
|
/// - staticFilesPath: the folder, relative to the working directory, the static files are served from.
|
|
/// - cacheControl: the cache-control directives applied to the served static files.
|
|
/// - compressionMinResponseSize: the minimum response body size, in bytes, before compression is applied.
|
|
/// - securityHeaders: the security headers applied to every response.
|
|
/// - logLevel: the level the request-logging middleware logs at.
|
|
/// - probe: the probe consulted by the `HealthController` readiness route.
|
|
/// - Returns: the configured router.
|
|
private func router(
|
|
staticFilesPath: String,
|
|
cacheControl: CacheControl,
|
|
compressionMinResponseSize: Int,
|
|
securityHeaders: SecurityHeadersMiddleware<AppRequestContext>.Configuration,
|
|
logLevel: Logger.Level,
|
|
probe: Probe
|
|
) -> Router<AppRequestContext> {
|
|
let router = Router(context: AppRequestContext.self)
|
|
|
|
router.addMiddleware {
|
|
LogRequestsMiddleware(logLevel)
|
|
SecurityHeadersMiddleware(
|
|
configuration: securityHeaders
|
|
)
|
|
ResponseCompressionMiddleware(
|
|
minimumResponseSizeToCompress: compressionMinResponseSize
|
|
)
|
|
LocalizationMiddleware()
|
|
NotFoundMiddleware()
|
|
FileMiddleware(
|
|
staticFilesPath,
|
|
cacheControl: cacheControl
|
|
)
|
|
}
|
|
|
|
router.addRoutes {
|
|
RootController<AppRequestContext>().routes
|
|
HealthController<AppRequestContext>(probe: probe).routes
|
|
}
|
|
|
|
return router
|
|
}
|