feat: Minimal user api controller on hummingbird app, not sure there's big wins over vapor.
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "68600988594927fcb25434476f598e1a813cb64030b7d9d8bf5b480e13a11989",
|
"originHash" : "0c4aa955a400075a593dfba3804996f803c9e812e765f72d0026e12c182b751c",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "async-http-client",
|
"identity" : "async-http-client",
|
||||||
@@ -82,6 +82,15 @@
|
|||||||
"version" : "2.0.2"
|
"version" : "2.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"identity" : "hummingbird-fluent",
|
||||||
|
"kind" : "remoteSourceControl",
|
||||||
|
"location" : "https://github.com/hummingbird-project/hummingbird-fluent.git",
|
||||||
|
"state" : {
|
||||||
|
"revision" : "45459ea5b541c6a96b87d1be848e384593b7dde3",
|
||||||
|
"version" : "2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"identity" : "leaf",
|
"identity" : "leaf",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ let package = Package(
|
|||||||
.package(url: "https://github.com/pointfreeco/swift-dependencies.git", from: "1.6.3"),
|
.package(url: "https://github.com/pointfreeco/swift-dependencies.git", from: "1.6.3"),
|
||||||
.package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.0.0"),
|
.package(url: "https://github.com/hummingbird-project/hummingbird.git", from: "2.0.0"),
|
||||||
.package(url: "https://github.com/hummingbird-project/hummingbird-auth.git", from: "2.0.2"),
|
.package(url: "https://github.com/hummingbird-project/hummingbird-auth.git", from: "2.0.2"),
|
||||||
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.3.0")
|
.package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.3.0"),
|
||||||
|
.package(url: "https://github.com/hummingbird-project/hummingbird-fluent.git", from: "2.0.0")
|
||||||
],
|
],
|
||||||
targets: [
|
targets: [
|
||||||
.executableTarget(
|
.executableTarget(
|
||||||
@@ -56,8 +57,11 @@ let package = Package(
|
|||||||
.executableTarget(
|
.executableTarget(
|
||||||
name: "HApp",
|
name: "HApp",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
|
"DatabaseClientLive",
|
||||||
.product(name: "Hummingbird", package: "hummingbird"),
|
.product(name: "Hummingbird", package: "hummingbird"),
|
||||||
.product(name: "ArgumentParser", package: "swift-argument-parser")
|
.product(name: "HummingbirdFluent", package: "hummingbird-fluent"),
|
||||||
|
.product(name: "ArgumentParser", package: "swift-argument-parser"),
|
||||||
|
.product(name: "FluentSQLiteDriver", package: "fluent-sqlite-driver")
|
||||||
],
|
],
|
||||||
swiftSettings: swiftSettings
|
swiftSettings: swiftSettings
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,65 +1,87 @@
|
|||||||
|
import DatabaseClient
|
||||||
|
import DatabaseClientLive
|
||||||
|
import FluentSQLiteDriver
|
||||||
import Hummingbird
|
import Hummingbird
|
||||||
|
import HummingbirdFluent
|
||||||
import Logging
|
import Logging
|
||||||
|
|
||||||
public struct AppConfiguration {
|
public protocol AppArguments {
|
||||||
|
var inMemoryDatabase: Bool { get }
|
||||||
public let hostname: String
|
var migrate: Bool { get }
|
||||||
public let port: Int
|
var revert: Bool { get }
|
||||||
public let logLevel: Logger.Level?
|
var hostname: String { get }
|
||||||
|
var port: Int { get }
|
||||||
public init(hostname: String, port: Int, logLevel: Logger.Level? = nil) {
|
var logLevel: Logger.Level? { get }
|
||||||
self.hostname = hostname
|
|
||||||
self.port = port
|
|
||||||
self.logLevel = logLevel
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build application
|
/// Build application
|
||||||
/// - Parameter arguments: application arguments
|
/// - Parameter arguments: application arguments
|
||||||
public func buildApplication(_ arguments: AppConfiguration) async throws -> some ApplicationProtocol {
|
public func buildApplication(_ arguments: some AppArguments) async throws -> some ApplicationProtocol {
|
||||||
let environment = Environment()
|
let environment = Environment()
|
||||||
|
|
||||||
let logger = {
|
let logger = {
|
||||||
var logger = Logger(label: "Todos")
|
var logger = Logger(label: "PurchaseOrders")
|
||||||
logger.logLevel = arguments.logLevel ??
|
logger.logLevel = arguments.logLevel ??
|
||||||
environment.get("LOG_LEVEL").map { Logger.Level(rawValue: $0) ?? .info } ??
|
environment.get("LOG_LEVEL").map { Logger.Level(rawValue: $0) ?? .info } ??
|
||||||
.info
|
.info
|
||||||
|
|
||||||
return logger
|
return logger
|
||||||
}()
|
}()
|
||||||
|
let fluent = Fluent(logger: logger)
|
||||||
|
if arguments.inMemoryDatabase {
|
||||||
|
fluent.databases.use(.sqlite(.memory), as: .sqlite)
|
||||||
|
} else {
|
||||||
|
fluent.databases.use(.sqlite(.file("hdb.sqlite")), as: .sqlite)
|
||||||
|
}
|
||||||
|
let dbClient = DatabaseClient.live(database: fluent.db())
|
||||||
|
try await fluent.migrations.add(dbClient.migrations())
|
||||||
|
|
||||||
|
if arguments.revert {
|
||||||
|
try await fluent.revert()
|
||||||
|
}
|
||||||
|
|
||||||
|
if arguments.migrate || arguments.inMemoryDatabase {
|
||||||
|
try await fluent.migrate()
|
||||||
|
}
|
||||||
|
|
||||||
|
let fluentPersist = await FluentPersistDriver(fluent: fluent)
|
||||||
|
|
||||||
let router = Router()
|
let router = Router()
|
||||||
|
|
||||||
// Add middleware
|
// Add middleware
|
||||||
|
|
||||||
router.addMiddleware {
|
// logging middleware
|
||||||
// logging middleware
|
router.add(middleware: LogRequestsMiddleware(.info))
|
||||||
LogRequestsMiddleware(.info)
|
router.add(middleware: FileMiddleware(logger: logger))
|
||||||
}
|
router.add(middleware: CORSMiddleware(
|
||||||
|
allowOrigin: .originBased,
|
||||||
|
allowHeaders: [.contentType],
|
||||||
|
allowMethods: [.get, .options, .post, .delete, .put, .patch]
|
||||||
|
))
|
||||||
|
|
||||||
// Add health endpoint
|
// Add health endpoint
|
||||||
|
|
||||||
router.get("/health") { _, _ -> HTTPResponse.Status in
|
router.get("/health") { _, _ -> HTTPResponse.Status in
|
||||||
return .ok
|
return .ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UserController(db: dbClient.users).addRoutes(to: router.group("api/users"))
|
||||||
|
|
||||||
// let router = buildRouter()
|
// let router = buildRouter()
|
||||||
//
|
//
|
||||||
let app = Application(
|
var app = Application(
|
||||||
router: router,
|
router: router,
|
||||||
|
|
||||||
configuration: .init(
|
configuration: .init(
|
||||||
address: .hostname(arguments.hostname, port: arguments.port),
|
address: .hostname(arguments.hostname, port: arguments.port),
|
||||||
serverName: "Todos"
|
serverName: "Purchase-Orders"
|
||||||
),
|
),
|
||||||
logger: logger
|
logger: logger
|
||||||
)
|
)
|
||||||
|
|
||||||
|
app.addServices(fluent, fluentPersist)
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build router
|
// Build router
|
||||||
|
|
||||||
// func buildRouter() -> Router<AppRequestContext> {
|
// func buildRouter() -> Router<AppRequestContext> {
|
||||||
// let router = Router()
|
// let router = Router()
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Hummingbird
|
|||||||
import Logging
|
import Logging
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct App: AsyncParsableCommand {
|
struct App: AsyncParsableCommand, AppArguments {
|
||||||
|
|
||||||
@Option(name: .shortAndLong)
|
@Option(name: .shortAndLong)
|
||||||
var hostname: String = "127.0.0.1"
|
var hostname: String = "127.0.0.1"
|
||||||
@@ -14,16 +14,28 @@ struct App: AsyncParsableCommand {
|
|||||||
@Option(name: .shortAndLong)
|
@Option(name: .shortAndLong)
|
||||||
var logLevel: Logger.Level?
|
var logLevel: Logger.Level?
|
||||||
|
|
||||||
|
@Flag(name: .shortAndLong)
|
||||||
|
var migrate: Bool = false
|
||||||
|
|
||||||
|
@Flag(name: .shortAndLong)
|
||||||
|
var revert: Bool = false
|
||||||
|
|
||||||
|
@Flag(name: .shortAndLong)
|
||||||
|
var inMemoryDatabase: Bool = false
|
||||||
|
|
||||||
func run() async throws {
|
func run() async throws {
|
||||||
let app = try await buildApplication(.init(hostname: hostname, port: port, logLevel: logLevel))
|
let app = try await buildApplication(self)
|
||||||
try await app.runService()
|
try await app.runService()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extend `Logger.Level` so it can be used as an argument
|
/// Extend `Logger.Level` so it can be used as an argument
|
||||||
|
|
||||||
#if hasFeature(RetroactiveAttribute)
|
#if hasFeature(RetroactiveAttribute)
|
||||||
extension Logger.Level: @retroactive ExpressibleByArgument {}
|
extension Logger.Level: @retroactive ExpressibleByArgument {
|
||||||
|
public init?(argument: String) {
|
||||||
|
self.init(rawValue: argument)
|
||||||
|
}
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
extension Logger.Level: ExpressibleByArgument {}
|
extension Logger.Level: ExpressibleByArgument {}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
28
Sources/HApp/Controllers/Api/UserController.swift
Normal file
28
Sources/HApp/Controllers/Api/UserController.swift
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import DatabaseClient
|
||||||
|
import Hummingbird
|
||||||
|
import HummingbirdFluent
|
||||||
|
import SharedModels
|
||||||
|
|
||||||
|
struct UserController<Context: RequestContext> {
|
||||||
|
let db: DatabaseClient.Users
|
||||||
|
|
||||||
|
func addRoutes(to group: RouterGroup<Context>) {
|
||||||
|
group.get(use: list)
|
||||||
|
.post(use: create)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Sendable
|
||||||
|
func list(_ request: Request, context: Context) async throws -> [User] {
|
||||||
|
try await db.fetchAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Sendable
|
||||||
|
func create(_ request: Request, context: Context) async throws -> User {
|
||||||
|
let create = try await request.decode(as: User.Create.self, context: context)
|
||||||
|
let user = try await db.create(create)
|
||||||
|
return user
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension User.Create: ResponseCodable {}
|
||||||
|
extension User: ResponseCodable {}
|
||||||
Reference in New Issue
Block a user