import DatabaseClient import Dependencies import Elementary import Fluent import FluentSQLiteDriver import ManualDCore import NIOSSL import ProjectClient import Vapor import VaporElementary @preconcurrency import VaporRouting import ViewController // configures your application public func configure( _ app: Application, makeDatabaseClient: @escaping (any Database) -> DatabaseClient = { .live(database: $0) } ) async throws { // Setup the database client. let databaseClient = try await setupDatabase(on: app, factory: makeDatabaseClient) // Add the global middlewares. addMiddleware(to: app, database: databaseClient) #if DEBUG // Live reload of the application for development when launched with the `./swift-dev` command // app.lifecycle.use(BrowserSyncHandler()) #endif // Add our route handlers. addRoutes(to: app) if app.environment != .testing { try await app.autoMigrate() } // Add our custom cli-commands to the application. addCommands(to: app) } private func addMiddleware(to app: Application, database databaseClient: DatabaseClient) { // 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) app.middleware.use(DependenciesMiddleware(database: databaseClient)) } private func setupDatabase( on app: Application, factory makeDatabaseClient: @escaping (any Database) -> DatabaseClient ) async throws -> DatabaseClient { 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.run()) } return databaseClient } private func addRoutes(to app: Application) { // Redirect the index path to project route. app.get { req in req.redirect(to: SiteRoute.View.router.path(for: .project(.index))) } app.mount( SiteRoute.router, middleware: { if app.environment == .testing { return nil } else { return $0.middleware() } }, use: siteHandler ) } private func addCommands(to app: Application) { // #if DEBUG // app.asyncCommands.use(SeedCommand(), as: "seed") // #endif // app.asyncCommands.use(GenerateAdminUserCommand(), as: "generate-admin") } extension SiteRoute { fileprivate func middleware() -> [any Middleware]? { switch self { case .api: return nil case .health: return nil case .view(let route): return route.middleware } } } extension DuctSizes: Content {} // FIX: Move func handlePdf(_ projectID: Project.ID, on request: Request) async throws -> Response { @Dependency(\.projectClient) var projectClient let html = try await projectClient.toHTML(projectID) let url = "/tmp/\(projectID)" try await request.fileio.writeFile(.init(string: html.render()), at: "\(url).html") let process = Process() let standardInput = Pipe() let standardOutput = Pipe() process.standardInput = standardInput process.standardOutput = standardOutput process.executableURL = URL(fileURLWithPath: "/bin/pandoc") process.arguments = [ "\(url).html", "--pdf-engine=weasyprint", "--from=html", "--css=Public/css/pdf.css", "-o", "\(url).pdf", ] try process.run() process.waitUntilExit() let response = try await request.fileio.asyncStreamFile(at: "\(url).pdf", mediaType: .pdf) { _ in // Remove files here. try FileManager.default.removeItem(atPath: "\(url).pdf") try FileManager.default.removeItem(atPath: "\(url).html") } response.headers.replaceOrAdd(name: .contentType, value: "application/octet-stream") response.headers.replaceOrAdd( name: .contentDisposition, value: "attachment; filename=Duct-Calc.pdf" ) return response } @Sendable private func siteHandler( request: Request, route: SiteRoute ) async throws -> any AsyncResponseEncodable { @Dependency(\.apiController) var apiController @Dependency(\.viewController) var viewController @Dependency(\.projectClient) var projectClient switch route { case .api(let route): return try await apiController.respond(route, request: request) case .health: return HTTPStatus.ok // FIX: Move case .view(.project(.detail(let projectID, .pdf))): return try await handlePdf(projectID, on: request) case .view(let route): return try await viewController.respond(route: route, request: request) } }