feat: Begins view controller integration into app target.

This commit is contained in:
2025-01-24 09:37:54 -05:00
parent 94f0c660ff
commit ce9cbe168e
3 changed files with 66 additions and 11 deletions

View File

@@ -37,7 +37,8 @@ let package = Package(
.executableTarget( .executableTarget(
name: "App", name: "App",
dependencies: [ dependencies: [
"DatabaseClientLive", .target(name: "DatabaseClientLive"),
.target(name: "ViewControllerLive"),
.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: "Vapor", package: "vapor"), .product(name: "Vapor", package: "vapor"),

View File

@@ -1,11 +1,14 @@
import DatabaseClientLive import DatabaseClientLive
import Dependencies import Dependencies
import Elementary
import Fluent import Fluent
import FluentSQLiteDriver import FluentSQLiteDriver
import NIOSSL import NIOSSL
import SharedModels import SharedModels
import Vapor import Vapor
import VaporElementary
@preconcurrency import VaporRouting @preconcurrency import VaporRouting
import ViewControllerLive
// configures your application // configures your application
public func configure( public func configure(
@@ -95,3 +98,52 @@ func siteHandler(
return try await route.handle(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)
})
)
}
}

View File

@@ -4,9 +4,11 @@ import Elementary
import SharedModels import SharedModels
import Vapor import Vapor
extension SharedModels.ViewRoute { public typealias AnySendableHTML = (any HTML & Sendable)
func view(isHtmxRequest: Bool, authenticate: @escaping @Sendable (User) -> Void) async throws -> (any HTML)? { public extension SharedModels.ViewRoute {
func view(isHtmxRequest: Bool, authenticate: @escaping @Sendable (User) -> Void) async throws -> AnySendableHTML? {
@Dependency(\.database.users) var users @Dependency(\.database.users) var users
switch self { switch self {
case .index: case .index:
@@ -60,7 +62,7 @@ extension SharedModels.ViewRoute.EmployeeRoute {
} }
} }
func view(isHtmxRequest: Bool) async throws -> (any HTML)? { func view(isHtmxRequest: Bool) async throws -> AnySendableHTML? {
@Dependency(\.database.employees) var employees @Dependency(\.database.employees) var employees
switch self { switch self {
@@ -110,7 +112,7 @@ extension SharedModels.ViewRoute.PurchaseOrderRoute {
} }
} }
func view(isHtmxRequest: Bool) async throws -> (any HTML)? { func view(isHtmxRequest: Bool) async throws -> AnySendableHTML? {
@Dependency(\.database.purchaseOrders) var purchaseOrders @Dependency(\.database.purchaseOrders) var purchaseOrders
switch self { switch self {
@@ -163,7 +165,7 @@ extension SharedModels.ViewRoute.PurchaseOrderRoute.Search {
} }
} }
func view(isHtmxRequest: Bool) async throws -> any HTML { func view(isHtmxRequest: Bool) async throws -> AnySendableHTML {
@Dependency(\.database) var database @Dependency(\.database) var database
switch self { switch self {
case let .index(context: context, table: table): case let .index(context: context, table: table):
@@ -193,7 +195,7 @@ extension SharedModels.ViewRoute.UserRoute {
} }
} }
func view(isHtmxRequest: Bool) async throws -> (any HTML)? { func view(isHtmxRequest: Bool) async throws -> AnySendableHTML? {
@Dependency(\.database.users) var users @Dependency(\.database.users) var users
switch self { switch self {
@@ -235,7 +237,7 @@ extension SharedModels.ViewRoute.VendorRoute {
} }
} }
func view(isHtmxRequest: Bool) async throws -> (any HTML)? { func view(isHtmxRequest: Bool) async throws -> AnySendableHTML? {
@Dependency(\.database) var database @Dependency(\.database) var database
switch self { switch self {
@@ -277,7 +279,7 @@ extension SharedModels.ViewRoute.VendorRoute {
extension SharedModels.ViewRoute.VendorBranchRoute { extension SharedModels.ViewRoute.VendorBranchRoute {
func view(isHtmxRequest: Bool) async throws -> (any HTML)? { func view(isHtmxRequest: Bool) async throws -> AnySendableHTML? {
@Dependency(\.database) var database @Dependency(\.database) var database
switch self { switch self {
@@ -350,7 +352,7 @@ private func render<C: HTML>(
_ mainPage: (C) async throws -> any SendableHTMLDocument, _ mainPage: (C) async throws -> any SendableHTMLDocument,
_ isHtmxRequest: Bool, _ isHtmxRequest: Bool,
@HTMLBuilder html: () -> C @HTMLBuilder html: () -> C
) async rethrows -> any HTML { ) async rethrows -> AnySendableHTML where C: Sendable {
guard isHtmxRequest else { guard isHtmxRequest else {
return try await mainPage(html()) return try await mainPage(html())
} }
@@ -361,6 +363,6 @@ private func render<C: HTML>(
_ mainPage: (C) async throws -> any SendableHTMLDocument, _ mainPage: (C) async throws -> any SendableHTMLDocument,
_ isHtmxRequest: Bool, _ isHtmxRequest: Bool,
_ html: @autoclosure @escaping () -> C _ html: @autoclosure @escaping () -> C
) async rethrows -> any HTML { ) async rethrows -> AnySendableHTML where C: Sendable {
try await render(mainPage, isHtmxRequest) { html() } try await render(mainPage, isHtmxRequest) { html() }
} }