Files
vapor-po/Sources/App/configure.swift

152 lines
4.2 KiB
Swift

import DatabaseClientLive
import Dependencies
import Elementary
import Fluent
import FluentSQLiteDriver
import NIOSSL
import SharedModels
import Vapor
import VaporElementary
@preconcurrency import VaporRouting
import ViewControllerLive
// configures your application
public func configure(
_ app: Application,
makeDatabaseClient: @escaping (any Database) -> DatabaseClient = { .live(database: $0) }
) async throws {
// cors middleware should come before default error middleware using `at: .beginning`
let corsConfiguration = CORSMiddleware.Configuration(
allowedOrigin: .all,
allowedMethods: [.GET, .POST, .PUT, .OPTIONS, .DELETE, .PATCH],
allowedHeaders: [.accept, .authorization, .contentType, .origin,
.xRequestedWith, .userAgent, .accessControlAllowOrigin]
)
let cors = CORSMiddleware(configuration: corsConfiguration)
app.middleware.use(cors, at: .beginning)
app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
app.middleware.use(app.sessions.middleware)
#if DEBUG
app.lifecycle.use(BrowserSyncHandler())
#endif
switch app.environment {
case .production, .development:
let dbFileName = Environment.get("SQLITE_FILENAME") ?? "db.sqlite"
app.databases.use(DatabaseConfigurationFactory.sqlite(.file(dbFileName)), as: .sqlite)
default:
app.databases.use(DatabaseConfigurationFactory.sqlite(.memory), as: .sqlite)
}
let databaseClient = makeDatabaseClient(app.db)
if app.environment != .testing {
try await app.migrations.add(databaseClient.migrations())
}
app.middleware.use(DependenciesMiddleware(database: databaseClient))
app.mount(
SiteRoute.router,
middleware: {
if app.environment == .testing {
return nil
} else {
return $0.middleware()
}
},
use: siteHandler
)
if app.environment != .testing {
try await app.autoMigrate()
}
#if DEBUG
app.asyncCommands.use(SeedCommand(), as: "seed")
#endif
app.asyncCommands.use(GenerateAdminUserCommand(), as: "generate-admin")
}
extension SiteRoute {
func middleware() -> [any Middleware]? {
switch self {
case let .api(route):
return route.middleware
case .health:
return nil
case let .view(route):
// return nil
return route.middleware
}
}
}
@Sendable
func siteHandler(
request: Request,
route: SiteRoute
) async throws -> any AsyncResponseEncodable {
switch route {
case let .api(route):
return try await route.handle(request: request)
case .health:
return HTTPStatus.ok
case let .view(route):
return try await route.respond(request: request)
// return try await route.handle(request: request)
}
}
extension ViewRoute {
func respond(request: Request) async throws -> any AsyncResponseEncodable {
if self == .index {
return request.redirect(to: ViewRoute.router.path(for: .purchaseOrder(.index)))
} else {
let html = try await view(isHtmxRequest: request.isHtmxRequest, authenticate: { request.auth.login($0) })
// Delete routes return nil, but are valid routes.
guard let html else {
return HTTPStatus.ok
}
return AnyHTMLResponse(value: html)
}
}
}
struct AnyHTMLResponse: AsyncResponseEncodable {
public var chunkSize: Int
public var headers: HTTPHeaders = ["Content-Type": "text/html; charset=utf-8"]
var value: _SendableAnyHTMLBox
init(chunkSize: Int = 1024, additionalHeaders: HTTPHeaders = [:], value: any HTML & Sendable) {
self.chunkSize = chunkSize
if additionalHeaders.contains(name: .contentType) {
self.headers = additionalHeaders
} else {
headers.add(contentsOf: additionalHeaders)
}
self.value = .init(value)
}
func encodeResponse(for request: Request) async throws -> Response {
Response(
status: .ok,
headers: headers,
body: .init(asyncStream: { [value, chunkSize] writer in
guard let html = value.tryTake() else {
assertionFailure("Non-sendable HTML value consumed more than once")
request.logger.error("Non-sendable HTML value consumed more than once")
throw Abort(.internalServerError)
}
try await writer.writeHTML(html, chunkSize: chunkSize)
try await writer.write(.end)
})
)
}
}