feat: Updates environment variables and datbase to allow postgres configuration for production environments.
All checks were successful
CI / Linux Tests (push) Successful in 6m39s
All checks were successful
CI / Linux Tests (push) Successful in 6m39s
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
import AuthClient
|
||||
import DatabaseClient
|
||||
import Dependencies
|
||||
import EnvVars
|
||||
import ManualDCore
|
||||
import PdfClient
|
||||
import Vapor
|
||||
@@ -14,16 +15,19 @@ struct DependenciesMiddleware: AsyncMiddleware {
|
||||
private let values: DependencyValues.Continuation
|
||||
// private let apiController: ApiController
|
||||
private let database: DatabaseClient
|
||||
private let environment: EnvVars
|
||||
private let viewController: ViewController
|
||||
|
||||
init(
|
||||
database: DatabaseClient,
|
||||
environment: EnvVars,
|
||||
// apiController: ApiController = .liveValue,
|
||||
viewController: ViewController = .liveValue
|
||||
) {
|
||||
self.values = withEscapedDependencies { $0 }
|
||||
// self.apiController = apiController
|
||||
self.database = database
|
||||
self.environment = environment
|
||||
self.viewController = viewController
|
||||
}
|
||||
|
||||
@@ -33,6 +37,7 @@ struct DependenciesMiddleware: AsyncMiddleware {
|
||||
// $0.apiController = apiController
|
||||
$0.auth = .live(on: request)
|
||||
$0.database = database
|
||||
$0.environment = environment
|
||||
// $0.dateFormatter = .liveValue
|
||||
$0.viewController = viewController
|
||||
$0.pdfClient = .liveValue
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import DatabaseClient
|
||||
import Dependencies
|
||||
import Elementary
|
||||
import EnvVars
|
||||
import Fluent
|
||||
import FluentPostgresDriver
|
||||
import FluentSQLiteDriver
|
||||
import ManualDCore
|
||||
import NIOSSL
|
||||
@@ -14,12 +16,15 @@ import ViewController
|
||||
// configures your application
|
||||
public func configure(
|
||||
_ app: Application,
|
||||
in environment: EnvVars,
|
||||
makeDatabaseClient: @escaping (any Database) -> DatabaseClient = { .live(database: $0) }
|
||||
) async throws {
|
||||
// Setup the database client.
|
||||
let databaseClient = try await setupDatabase(on: app, factory: makeDatabaseClient)
|
||||
let databaseClient = try await setupDatabase(
|
||||
on: app, environment: environment, factory: makeDatabaseClient
|
||||
)
|
||||
// Add the global middlewares.
|
||||
addMiddleware(to: app, database: databaseClient)
|
||||
addMiddleware(to: app, database: databaseClient, environment: environment)
|
||||
#if DEBUG
|
||||
// Live reload of the application for development when launched with the `./swift-dev` command
|
||||
// app.lifecycle.use(BrowserSyncHandler())
|
||||
@@ -33,7 +38,11 @@ public func configure(
|
||||
addCommands(to: app)
|
||||
}
|
||||
|
||||
private func addMiddleware(to app: Application, database databaseClient: DatabaseClient) {
|
||||
private func addMiddleware(
|
||||
to app: Application,
|
||||
database databaseClient: DatabaseClient,
|
||||
environment: EnvVars
|
||||
) {
|
||||
// cors middleware should come before default error middleware using `at: .beginning`
|
||||
let corsConfiguration = CORSMiddleware.Configuration(
|
||||
allowedOrigin: .all,
|
||||
@@ -48,16 +57,20 @@ private func addMiddleware(to app: Application, database databaseClient: Databas
|
||||
|
||||
app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
|
||||
app.middleware.use(app.sessions.middleware)
|
||||
app.middleware.use(DependenciesMiddleware(database: databaseClient))
|
||||
app.middleware.use(DependenciesMiddleware(database: databaseClient, environment: environment))
|
||||
}
|
||||
|
||||
private func setupDatabase(
|
||||
on app: Application,
|
||||
environment: EnvVars,
|
||||
factory makeDatabaseClient: @escaping (any Database) -> DatabaseClient
|
||||
) async throws -> DatabaseClient {
|
||||
switch app.environment {
|
||||
case .production, .development:
|
||||
let dbFileName = Environment.get("SQLITE_FILENAME") ?? "db.sqlite"
|
||||
case .production:
|
||||
let configuration = try environment.postgresConfiguration()
|
||||
app.databases.use(.postgres(configuration: configuration), as: .psql)
|
||||
case .development:
|
||||
let dbFileName = environment.sqlitePath ?? "db.sqlite"
|
||||
app.databases.use(DatabaseConfigurationFactory.sqlite(.file(dbFileName)), as: .sqlite)
|
||||
default:
|
||||
app.databases.use(DatabaseConfigurationFactory.sqlite(.memory), as: .sqlite)
|
||||
@@ -129,3 +142,30 @@ private func siteHandler(
|
||||
return try await viewController.respond(route: route, request: request)
|
||||
}
|
||||
}
|
||||
|
||||
extension EnvVars {
|
||||
func postgresConfiguration() throws -> SQLPostgresConfiguration {
|
||||
guard let hostname = postgresHostname,
|
||||
let username = postgresUsername,
|
||||
let password = postgresPassword,
|
||||
let database = postgresDatabase
|
||||
else {
|
||||
throw EnvError("Missing environment variables for postgres connection.")
|
||||
}
|
||||
return .init(
|
||||
hostname: hostname,
|
||||
username: username,
|
||||
password: password,
|
||||
database: database,
|
||||
tls: .disable
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
struct EnvError: Error {
|
||||
let reason: String
|
||||
|
||||
init(_ reason: String) {
|
||||
self.reason = reason
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import DatabaseClient
|
||||
import Dependencies
|
||||
import EnvVars
|
||||
import Logging
|
||||
import NIOCore
|
||||
import NIOPosix
|
||||
@@ -17,11 +18,15 @@ enum Entrypoint {
|
||||
// You can enable it if you'd like to reduce the amount of context switching between NIO and Swift Concurrency.
|
||||
// Note: this has caused issues with some libraries that use `.wait()` and cleanly shutting down.
|
||||
// If enabled, you should be careful about calling async functions before this point as it can cause assertion failures.
|
||||
// let executorTakeoverSuccess = NIOSingletons.unsafeTryInstallSingletonPosixEventLoopGroupAsConcurrencyGlobalExecutor()
|
||||
// app.logger.debug("Tried to install SwiftNIO's EventLoopGroup as Swift's global concurrency executor", metadata: ["success": .stringConvertible(executorTakeoverSuccess)])
|
||||
let executorTakeoverSuccess =
|
||||
NIOSingletons.unsafeTryInstallSingletonPosixEventLoopGroupAsConcurrencyGlobalExecutor()
|
||||
app.logger.debug(
|
||||
"Tried to install SwiftNIO's EventLoopGroup as Swift's global concurrency executor",
|
||||
metadata: ["success": .stringConvertible(executorTakeoverSuccess)]
|
||||
)
|
||||
|
||||
do {
|
||||
try await configure(app)
|
||||
try await configure(app, in: EnvVars.live())
|
||||
} catch {
|
||||
app.logger.report(error: error)
|
||||
try? await app.asyncShutdown()
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
import Dependencies
|
||||
import DependenciesMacros
|
||||
import Foundation
|
||||
|
||||
extension DependencyValues {
|
||||
|
||||
/// Holds values defined in the process environment that are needed.
|
||||
///
|
||||
/// These are generally loaded from a `.env` file, but also have default values,
|
||||
/// if not found.
|
||||
public var env: @Sendable () throws -> EnvVars {
|
||||
get { self[EnvClient.self].env }
|
||||
set { self[EnvClient.self].env = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
@DependencyClient
|
||||
struct EnvClient: Sendable {
|
||||
public var env: @Sendable () throws -> EnvVars
|
||||
}
|
||||
|
||||
/// Holds values defined in the process environment that are needed.
|
||||
///
|
||||
/// These are generally loaded from a `.env` file, but also have default values,
|
||||
/// if not found.
|
||||
public struct EnvVars: Codable, Equatable, Sendable {
|
||||
|
||||
/// The path to the pandoc executable on the system, used to generate pdf's.
|
||||
public let pandocPath: String
|
||||
|
||||
/// The pdf engine to use with pandoc when creating pdf's.
|
||||
public let pdfEngine: String
|
||||
|
||||
public init(
|
||||
pandocPath: String = "/usr/bin/pandoc",
|
||||
pdfEngine: String = "weasyprint"
|
||||
) {
|
||||
self.pandocPath = pandocPath
|
||||
self.pdfEngine = pdfEngine
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case pandocPath = "PANDOC_PATH"
|
||||
case pdfEngine = "PDF_ENGINE"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension EnvClient: DependencyKey {
|
||||
static let testValue = Self()
|
||||
|
||||
static let liveValue = Self(env: {
|
||||
// Convert default values into a dictionary.
|
||||
let defaults =
|
||||
(try? encoder.encode(EnvVars()))
|
||||
.flatMap { try? decoder.decode([String: String].self, from: $0) }
|
||||
?? [:]
|
||||
|
||||
// Merge the default values with values found in process environment.
|
||||
let assigned = defaults.merging(ProcessInfo.processInfo.environment, uniquingKeysWith: { $1 })
|
||||
|
||||
return (try? JSONSerialization.data(withJSONObject: assigned))
|
||||
.flatMap { try? decoder.decode(EnvVars.self, from: $0) }
|
||||
?? .init()
|
||||
})
|
||||
}
|
||||
|
||||
private let encoder: JSONEncoder = {
|
||||
JSONEncoder()
|
||||
}()
|
||||
|
||||
private let decoder: JSONDecoder = {
|
||||
JSONDecoder()
|
||||
}()
|
||||
100
Sources/EnvVars/Interface.swift
Normal file
100
Sources/EnvVars/Interface.swift
Normal file
@@ -0,0 +1,100 @@
|
||||
import Dependencies
|
||||
import DependenciesMacros
|
||||
import Foundation
|
||||
|
||||
extension DependencyValues {
|
||||
|
||||
/// Holds values defined in the process environment that are needed.
|
||||
///
|
||||
/// These are generally loaded from a `.env` file, but also have default values,
|
||||
/// if not found.
|
||||
public var environment: EnvVars {
|
||||
get { self[EnvVars.self] }
|
||||
set { self[EnvVars.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds values defined in the process environment that are needed.
|
||||
///
|
||||
/// These are generally loaded from a `.env` file, but also have default values,
|
||||
/// if not found.
|
||||
public struct EnvVars: Codable, Equatable, Sendable {
|
||||
|
||||
/// The path to the pandoc executable on the system, used to generate pdf's.
|
||||
public let pandocPath: String
|
||||
|
||||
/// The pdf engine to use with pandoc when creating pdf's.
|
||||
public let pdfEngine: String
|
||||
|
||||
/// The postgres hostname, used for production database connection.
|
||||
public let postgresHostname: String?
|
||||
|
||||
/// The postgres username, used for production database connection.
|
||||
public let postgresUsername: String?
|
||||
|
||||
/// The postgres password, used for production database connection.
|
||||
public let postgresPassword: String?
|
||||
|
||||
/// The postgres database, used for production database connection.
|
||||
public let postgresDatabase: String?
|
||||
|
||||
/// The path to the sqlite database, used for development database connection.
|
||||
public let sqlitePath: String?
|
||||
|
||||
public init(
|
||||
pandocPath: String = "/usr/bin/pandoc",
|
||||
pdfEngine: String = "weasyprint",
|
||||
postgresHostname: String? = "localhost",
|
||||
postgresUsername: String? = "vapor",
|
||||
postgresPassword: String? = "super-secret",
|
||||
postgresDatabase: String? = "vapor",
|
||||
sqlitePath: String? = "db.sqlite"
|
||||
) {
|
||||
self.pandocPath = pandocPath
|
||||
self.pdfEngine = pdfEngine
|
||||
self.postgresHostname = postgresHostname
|
||||
self.postgresUsername = postgresUsername
|
||||
self.postgresPassword = postgresPassword
|
||||
self.postgresDatabase = postgresDatabase
|
||||
self.sqlitePath = sqlitePath
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case pandocPath = "PANDOC_PATH"
|
||||
case pdfEngine = "PDF_ENGINE"
|
||||
case postgresHostname = "POSTGRES_HOSTNAME"
|
||||
case postgresUsername = "POSTGRES_USERNAME"
|
||||
case postgresPassword = "POSTGRES_PASSWORD"
|
||||
case postgresDatabase = "POSTGRES_DATABASE"
|
||||
case sqlitePath = "SQLITE_PATH"
|
||||
}
|
||||
|
||||
public static func live(_ env: [String: String] = ProcessInfo.processInfo.environment) -> Self {
|
||||
|
||||
// Convert default values into a dictionary.
|
||||
let defaults =
|
||||
(try? encoder.encode(EnvVars()))
|
||||
.flatMap { try? decoder.decode([String: String].self, from: $0) }
|
||||
?? [:]
|
||||
|
||||
// Merge the default values with values found in process environment.
|
||||
let assigned = defaults.merging(env, uniquingKeysWith: { $1 })
|
||||
|
||||
return (try? JSONSerialization.data(withJSONObject: assigned))
|
||||
.flatMap { try? decoder.decode(EnvVars.self, from: $0) }
|
||||
?? .init()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension EnvVars: TestDependencyKey {
|
||||
public static let testValue = Self()
|
||||
}
|
||||
|
||||
private let encoder: JSONEncoder = {
|
||||
JSONEncoder()
|
||||
}()
|
||||
|
||||
private let decoder: JSONDecoder = {
|
||||
JSONDecoder()
|
||||
}()
|
||||
@@ -1,7 +1,7 @@
|
||||
import Dependencies
|
||||
import DependenciesMacros
|
||||
import Elementary
|
||||
import EnvClient
|
||||
import EnvVars
|
||||
import FileClient
|
||||
import Foundation
|
||||
import ManualDCore
|
||||
@@ -46,9 +46,8 @@ extension PdfClient: DependencyKey {
|
||||
},
|
||||
generatePdf: { projectID, html in
|
||||
@Dependency(\.fileClient) var fileClient
|
||||
@Dependency(\.env) var env
|
||||
@Dependency(\.environment) var environment
|
||||
|
||||
let envVars = try env()
|
||||
let baseUrl = "/tmp/\(projectID)"
|
||||
try await fileClient.writeFile(html.render(), "\(baseUrl).html")
|
||||
|
||||
@@ -57,10 +56,10 @@ extension PdfClient: DependencyKey {
|
||||
let standardOutput = Pipe()
|
||||
process.standardInput = standardInput
|
||||
process.standardOutput = standardOutput
|
||||
process.executableURL = URL(fileURLWithPath: envVars.pandocPath)
|
||||
process.executableURL = URL(fileURLWithPath: environment.pandocPath)
|
||||
process.arguments = [
|
||||
"\(baseUrl).html",
|
||||
"--pdf-engine=\(envVars.pdfEngine)",
|
||||
"--pdf-engine=\(environment.pdfEngine)",
|
||||
"--from=html",
|
||||
"--css=Public/css/pdf.css",
|
||||
"--output=\(baseUrl).pdf",
|
||||
|
||||
Reference in New Issue
Block a user