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,7 +2,7 @@
|
|||||||
"name": "swift-manual-d-dev",
|
"name": "swift-manual-d-dev",
|
||||||
"image": "git.housh.dev/michael/swift-dev-container:latest",
|
"image": "git.housh.dev/michael/swift-dev-container:latest",
|
||||||
"features": {
|
"features": {
|
||||||
"ghcr.io/devcontainers/features/sshd:1": {},
|
//"ghcr.io/devcontainers/features/sshd:1": {},
|
||||||
"ghcr.io/devcontainers/features/git:1": {
|
"ghcr.io/devcontainers/features/git:1": {
|
||||||
"version": "os-provided",
|
"version": "os-provided",
|
||||||
"ppa": "false"
|
"ppa": "false"
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"ghcr.io/rocker-org/devcontainer-features/pandoc:1": {},
|
"ghcr.io/rocker-org/devcontainer-features/pandoc:1": {},
|
||||||
//"ghcr.io/devcontainers/features/docker-in-docker:2": {},
|
//"ghcr.io/devcontainers/features/docker-in-docker:2": {},
|
||||||
"ghcr.io/wxw-matt/devcontainer-features/apt:latest": {
|
"ghcr.io/wxw-matt/devcontainer-features/apt:latest": {
|
||||||
"packages": "weasyprint gnupg2"
|
"packages": "weasyprint gnupg2 tmux"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"runArgs": [
|
"runArgs": [
|
||||||
@@ -22,6 +22,9 @@
|
|||||||
"--security-opt",
|
"--security-opt",
|
||||||
"seccomp=unconfined"
|
"seccomp=unconfined"
|
||||||
],
|
],
|
||||||
|
"remoteEnv": {
|
||||||
|
"TERM": "xterm-256color"
|
||||||
|
},
|
||||||
"remoteUser": "swift",
|
"remoteUser": "swift",
|
||||||
"forwardPorts": [8080],
|
"forwardPorts": [8080],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "ed354a8e92f6b810403986d192b495a0e8e67cc9577e8ec24bce4ba275c0513d",
|
"originHash" : "b6e6af1076a5bcce49e1231c44be25d770eaef278e2d1ce1c961446d49cb2d00",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "async-http-client",
|
"identity" : "async-http-client",
|
||||||
@@ -73,6 +73,15 @@
|
|||||||
"version" : "1.53.0"
|
"version" : "1.53.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "fluent-postgres-driver",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/vapor/fluent-postgres-driver.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "59bff45a41d1ece1950bb8a6e0006d88c1fb6e69",
|
||||||
|
"version" : "2.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "fluent-sqlite-driver",
|
"identity" : "fluent-sqlite-driver",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
@@ -100,6 +109,24 @@
|
|||||||
"version" : "0.14.0"
|
"version" : "0.14.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "postgres-kit",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/vapor/postgres-kit.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "7c079553e9cda74811e627775bf22e40a9405ad9",
|
||||||
|
"version" : "2.15.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"identity" : "postgres-nio",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/vapor/postgres-nio.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "d578b86fb2c8321b114d97cd70831d1a3e9531a6",
|
||||||
|
"version" : "1.30.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "routing-kit",
|
"identity" : "routing-kit",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ let package = Package(
|
|||||||
.executable(name: "App", targets: ["App"]),
|
.executable(name: "App", targets: ["App"]),
|
||||||
.library(name: "AuthClient", targets: ["AuthClient"]),
|
.library(name: "AuthClient", targets: ["AuthClient"]),
|
||||||
.library(name: "DatabaseClient", targets: ["DatabaseClient"]),
|
.library(name: "DatabaseClient", targets: ["DatabaseClient"]),
|
||||||
.library(name: "EnvClient", targets: ["EnvClient"]),
|
.library(name: "EnvVars", targets: ["EnvVars"]),
|
||||||
.library(name: "FileClient", targets: ["FileClient"]),
|
.library(name: "FileClient", targets: ["FileClient"]),
|
||||||
.library(name: "HTMLSnapshotTesting", targets: ["HTMLSnapshotTesting"]),
|
.library(name: "HTMLSnapshotTesting", targets: ["HTMLSnapshotTesting"]),
|
||||||
.library(name: "PdfClient", targets: ["PdfClient"]),
|
.library(name: "PdfClient", targets: ["PdfClient"]),
|
||||||
@@ -22,6 +22,7 @@ let package = Package(
|
|||||||
.package(url: "https://github.com/vapor/vapor.git", from: "4.110.1"),
|
.package(url: "https://github.com/vapor/vapor.git", from: "4.110.1"),
|
||||||
.package(url: "https://github.com/vapor/fluent.git", from: "4.9.0"),
|
.package(url: "https://github.com/vapor/fluent.git", from: "4.9.0"),
|
||||||
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.6.0"),
|
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.6.0"),
|
||||||
|
.package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0"),
|
||||||
.package(url: "https://github.com/apple/swift-nio.git", from: "2.65.0"),
|
.package(url: "https://github.com/apple/swift-nio.git", from: "2.65.0"),
|
||||||
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.0.0"),
|
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.0.0"),
|
||||||
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.12.0"),
|
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.12.0"),
|
||||||
@@ -44,6 +45,7 @@ let package = Package(
|
|||||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
.product(name: "Fluent", package: "fluent"),
|
.product(name: "Fluent", package: "fluent"),
|
||||||
.product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"),
|
.product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver"),
|
||||||
|
.product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"),
|
||||||
.product(name: "Vapor", package: "vapor"),
|
.product(name: "Vapor", package: "vapor"),
|
||||||
.product(name: "NIOCore", package: "swift-nio"),
|
.product(name: "NIOCore", package: "swift-nio"),
|
||||||
.product(name: "NIOPosix", package: "swift-nio"),
|
.product(name: "NIOPosix", package: "swift-nio"),
|
||||||
@@ -81,7 +83,7 @@ let package = Package(
|
|||||||
]
|
]
|
||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "EnvClient",
|
name: "EnvVars",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
||||||
@@ -105,7 +107,7 @@ let package = Package(
|
|||||||
.target(
|
.target(
|
||||||
name: "PdfClient",
|
name: "PdfClient",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.target(name: "EnvClient"),
|
.target(name: "EnvVars"),
|
||||||
.target(name: "FileClient"),
|
.target(name: "FileClient"),
|
||||||
.target(name: "ManualDCore"),
|
.target(name: "ManualDCore"),
|
||||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||||
|
|||||||
@@ -7,12 +7,7 @@
|
|||||||
'Noto Color Emoji';
|
'Noto Color Emoji';
|
||||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
|
||||||
monospace;
|
monospace;
|
||||||
--color-red-500: oklch(63.7% 0.237 25.331);
|
|
||||||
--color-red-600: oklch(57.7% 0.245 27.325);
|
|
||||||
--color-green-400: oklch(79.2% 0.209 151.711);
|
--color-green-400: oklch(79.2% 0.209 151.711);
|
||||||
--color-indigo-600: oklch(51.1% 0.262 276.966);
|
|
||||||
--color-slate-300: oklch(86.9% 0.022 252.894);
|
|
||||||
--color-slate-900: oklch(20.8% 0.042 265.755);
|
|
||||||
--color-gray-200: oklch(92.8% 0.006 264.531);
|
--color-gray-200: oklch(92.8% 0.006 264.531);
|
||||||
--color-gray-400: oklch(70.7% 0.022 261.325);
|
--color-gray-400: oklch(70.7% 0.022 261.325);
|
||||||
--color-black: #000;
|
--color-black: #000;
|
||||||
@@ -30,7 +25,6 @@
|
|||||||
--text-3xl--line-height: calc(2.25 / 1.875);
|
--text-3xl--line-height: calc(2.25 / 1.875);
|
||||||
--font-weight-bold: 700;
|
--font-weight-bold: 700;
|
||||||
--radius-sm: 0.25rem;
|
--radius-sm: 0.25rem;
|
||||||
--radius-md: 0.375rem;
|
|
||||||
--radius-lg: 0.5rem;
|
--radius-lg: 0.5rem;
|
||||||
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
--ease-out: cubic-bezier(0, 0, 0.2, 1);
|
||||||
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import AuthClient
|
import AuthClient
|
||||||
import DatabaseClient
|
import DatabaseClient
|
||||||
import Dependencies
|
import Dependencies
|
||||||
|
import EnvVars
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
import PdfClient
|
import PdfClient
|
||||||
import Vapor
|
import Vapor
|
||||||
@@ -14,16 +15,19 @@ struct DependenciesMiddleware: AsyncMiddleware {
|
|||||||
private let values: DependencyValues.Continuation
|
private let values: DependencyValues.Continuation
|
||||||
// private let apiController: ApiController
|
// private let apiController: ApiController
|
||||||
private let database: DatabaseClient
|
private let database: DatabaseClient
|
||||||
|
private let environment: EnvVars
|
||||||
private let viewController: ViewController
|
private let viewController: ViewController
|
||||||
|
|
||||||
init(
|
init(
|
||||||
database: DatabaseClient,
|
database: DatabaseClient,
|
||||||
|
environment: EnvVars,
|
||||||
// apiController: ApiController = .liveValue,
|
// apiController: ApiController = .liveValue,
|
||||||
viewController: ViewController = .liveValue
|
viewController: ViewController = .liveValue
|
||||||
) {
|
) {
|
||||||
self.values = withEscapedDependencies { $0 }
|
self.values = withEscapedDependencies { $0 }
|
||||||
// self.apiController = apiController
|
// self.apiController = apiController
|
||||||
self.database = database
|
self.database = database
|
||||||
|
self.environment = environment
|
||||||
self.viewController = viewController
|
self.viewController = viewController
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,6 +37,7 @@ struct DependenciesMiddleware: AsyncMiddleware {
|
|||||||
// $0.apiController = apiController
|
// $0.apiController = apiController
|
||||||
$0.auth = .live(on: request)
|
$0.auth = .live(on: request)
|
||||||
$0.database = database
|
$0.database = database
|
||||||
|
$0.environment = environment
|
||||||
// $0.dateFormatter = .liveValue
|
// $0.dateFormatter = .liveValue
|
||||||
$0.viewController = viewController
|
$0.viewController = viewController
|
||||||
$0.pdfClient = .liveValue
|
$0.pdfClient = .liveValue
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import DatabaseClient
|
import DatabaseClient
|
||||||
import Dependencies
|
import Dependencies
|
||||||
import Elementary
|
import Elementary
|
||||||
|
import EnvVars
|
||||||
import Fluent
|
import Fluent
|
||||||
|
import FluentPostgresDriver
|
||||||
import FluentSQLiteDriver
|
import FluentSQLiteDriver
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
import NIOSSL
|
import NIOSSL
|
||||||
@@ -14,12 +16,15 @@ import ViewController
|
|||||||
// configures your application
|
// configures your application
|
||||||
public func configure(
|
public func configure(
|
||||||
_ app: Application,
|
_ app: Application,
|
||||||
|
in environment: EnvVars,
|
||||||
makeDatabaseClient: @escaping (any Database) -> DatabaseClient = { .live(database: $0) }
|
makeDatabaseClient: @escaping (any Database) -> DatabaseClient = { .live(database: $0) }
|
||||||
) async throws {
|
) async throws {
|
||||||
// Setup the database client.
|
// 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.
|
// Add the global middlewares.
|
||||||
addMiddleware(to: app, database: databaseClient)
|
addMiddleware(to: app, database: databaseClient, environment: environment)
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
// Live reload of the application for development when launched with the `./swift-dev` command
|
// Live reload of the application for development when launched with the `./swift-dev` command
|
||||||
// app.lifecycle.use(BrowserSyncHandler())
|
// app.lifecycle.use(BrowserSyncHandler())
|
||||||
@@ -33,7 +38,11 @@ public func configure(
|
|||||||
addCommands(to: app)
|
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`
|
// cors middleware should come before default error middleware using `at: .beginning`
|
||||||
let corsConfiguration = CORSMiddleware.Configuration(
|
let corsConfiguration = CORSMiddleware.Configuration(
|
||||||
allowedOrigin: .all,
|
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(FileMiddleware(publicDirectory: app.directory.publicDirectory))
|
||||||
app.middleware.use(app.sessions.middleware)
|
app.middleware.use(app.sessions.middleware)
|
||||||
app.middleware.use(DependenciesMiddleware(database: databaseClient))
|
app.middleware.use(DependenciesMiddleware(database: databaseClient, environment: environment))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupDatabase(
|
private func setupDatabase(
|
||||||
on app: Application,
|
on app: Application,
|
||||||
|
environment: EnvVars,
|
||||||
factory makeDatabaseClient: @escaping (any Database) -> DatabaseClient
|
factory makeDatabaseClient: @escaping (any Database) -> DatabaseClient
|
||||||
) async throws -> DatabaseClient {
|
) async throws -> DatabaseClient {
|
||||||
switch app.environment {
|
switch app.environment {
|
||||||
case .production, .development:
|
case .production:
|
||||||
let dbFileName = Environment.get("SQLITE_FILENAME") ?? "db.sqlite"
|
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)
|
app.databases.use(DatabaseConfigurationFactory.sqlite(.file(dbFileName)), as: .sqlite)
|
||||||
default:
|
default:
|
||||||
app.databases.use(DatabaseConfigurationFactory.sqlite(.memory), as: .sqlite)
|
app.databases.use(DatabaseConfigurationFactory.sqlite(.memory), as: .sqlite)
|
||||||
@@ -129,3 +142,30 @@ private func siteHandler(
|
|||||||
return try await viewController.respond(route: route, request: request)
|
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 DatabaseClient
|
||||||
import Dependencies
|
import Dependencies
|
||||||
|
import EnvVars
|
||||||
import Logging
|
import Logging
|
||||||
import NIOCore
|
import NIOCore
|
||||||
import NIOPosix
|
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.
|
// 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.
|
// 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.
|
// If enabled, you should be careful about calling async functions before this point as it can cause assertion failures.
|
||||||
// let executorTakeoverSuccess = NIOSingletons.unsafeTryInstallSingletonPosixEventLoopGroupAsConcurrencyGlobalExecutor()
|
let executorTakeoverSuccess =
|
||||||
// app.logger.debug("Tried to install SwiftNIO's EventLoopGroup as Swift's global concurrency executor", metadata: ["success": .stringConvertible(executorTakeoverSuccess)])
|
NIOSingletons.unsafeTryInstallSingletonPosixEventLoopGroupAsConcurrencyGlobalExecutor()
|
||||||
|
app.logger.debug(
|
||||||
|
"Tried to install SwiftNIO's EventLoopGroup as Swift's global concurrency executor",
|
||||||
|
metadata: ["success": .stringConvertible(executorTakeoverSuccess)]
|
||||||
|
)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try await configure(app)
|
try await configure(app, in: EnvVars.live())
|
||||||
} catch {
|
} catch {
|
||||||
app.logger.report(error: error)
|
app.logger.report(error: error)
|
||||||
try? await app.asyncShutdown()
|
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 Dependencies
|
||||||
import DependenciesMacros
|
import DependenciesMacros
|
||||||
import Elementary
|
import Elementary
|
||||||
import EnvClient
|
import EnvVars
|
||||||
import FileClient
|
import FileClient
|
||||||
import Foundation
|
import Foundation
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
@@ -46,9 +46,8 @@ extension PdfClient: DependencyKey {
|
|||||||
},
|
},
|
||||||
generatePdf: { projectID, html in
|
generatePdf: { projectID, html in
|
||||||
@Dependency(\.fileClient) var fileClient
|
@Dependency(\.fileClient) var fileClient
|
||||||
@Dependency(\.env) var env
|
@Dependency(\.environment) var environment
|
||||||
|
|
||||||
let envVars = try env()
|
|
||||||
let baseUrl = "/tmp/\(projectID)"
|
let baseUrl = "/tmp/\(projectID)"
|
||||||
try await fileClient.writeFile(html.render(), "\(baseUrl).html")
|
try await fileClient.writeFile(html.render(), "\(baseUrl).html")
|
||||||
|
|
||||||
@@ -57,10 +56,10 @@ extension PdfClient: DependencyKey {
|
|||||||
let standardOutput = Pipe()
|
let standardOutput = Pipe()
|
||||||
process.standardInput = standardInput
|
process.standardInput = standardInput
|
||||||
process.standardOutput = standardOutput
|
process.standardOutput = standardOutput
|
||||||
process.executableURL = URL(fileURLWithPath: envVars.pandocPath)
|
process.executableURL = URL(fileURLWithPath: environment.pandocPath)
|
||||||
process.arguments = [
|
process.arguments = [
|
||||||
"\(baseUrl).html",
|
"\(baseUrl).html",
|
||||||
"--pdf-engine=\(envVars.pdfEngine)",
|
"--pdf-engine=\(environment.pdfEngine)",
|
||||||
"--from=html",
|
"--from=html",
|
||||||
"--css=Public/css/pdf.css",
|
"--css=Public/css/pdf.css",
|
||||||
"--output=\(baseUrl).pdf",
|
"--output=\(baseUrl).pdf",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func withDatabase(
|
|||||||
) async throws {
|
) async throws {
|
||||||
let app = try await Application.make(.testing)
|
let app = try await Application.make(.testing)
|
||||||
do {
|
do {
|
||||||
try await configure(app)
|
try await configure(app, in: .live())
|
||||||
let database = app.db
|
let database = app.db
|
||||||
try await app.autoMigrate()
|
try await app.autoMigrate()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user