feat: WIP
This commit is contained in:
@@ -4,15 +4,14 @@ ARG SWIFT_MODE="debug"
|
|||||||
# ================================
|
# ================================
|
||||||
# Build image
|
# Build image
|
||||||
# ================================
|
# ================================
|
||||||
FROM swift:6.0-noble AS build
|
FROM swift:6.0.3-noble AS build
|
||||||
|
|
||||||
ARG SWIFT_MODE
|
ARG SWIFT_MODE
|
||||||
|
|
||||||
# Install OS updates
|
# Install OS updates
|
||||||
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
|
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
|
||||||
&& apt-get -q update \
|
&& apt-get -q update \
|
||||||
&& apt-get -q dist-upgrade -y \
|
&& apt-get -q dist-upgrade -y
|
||||||
&& apt-get install -y libjemalloc-dev
|
|
||||||
|
|
||||||
# Set up a build area
|
# Set up a build area
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
@@ -34,8 +33,7 @@ RUN --mount=type=cache,target=/build/.build swift build \
|
|||||||
-c ${SWIFT_MODE} \
|
-c ${SWIFT_MODE} \
|
||||||
--product App \
|
--product App \
|
||||||
--static-swift-stdlib \
|
--static-swift-stdlib \
|
||||||
-Xswiftc -g \
|
-Xswiftc -g
|
||||||
-Xlinker -ljemalloc
|
|
||||||
|
|
||||||
# Switch to the staging area
|
# Switch to the staging area
|
||||||
WORKDIR /staging
|
WORKDIR /staging
|
||||||
|
|||||||
@@ -4,58 +4,58 @@ import Logging
|
|||||||
import PsychrometricClient
|
import PsychrometricClient
|
||||||
import Routes
|
import Routes
|
||||||
|
|
||||||
public extension DependencyValues {
|
// public extension DependencyValues {
|
||||||
var apiController: ApiController {
|
// var apiController: ApiController {
|
||||||
get { self[ApiController.self] }
|
// get { self[ApiController.self] }
|
||||||
set { self[ApiController.self] = newValue }
|
// set { self[ApiController.self] = newValue }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@DependencyClient
|
// @DependencyClient
|
||||||
public struct ApiController: Sendable {
|
// public struct ApiController: Sendable {
|
||||||
public var json: @Sendable (SiteRoute.Api, Logger) async throws -> (any Encodable)?
|
// public var json: @Sendable (SiteRoute.Api, Logger) async throws -> (any Encodable)?
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
extension ApiController: TestDependencyKey {
|
// extension ApiController: TestDependencyKey {
|
||||||
public static let testValue: ApiController = Self()
|
// public static let testValue: ApiController = Self()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
extension ApiController: DependencyKey {
|
// extension ApiController: DependencyKey {
|
||||||
public static var liveValue: ApiController {
|
// public static var liveValue: ApiController {
|
||||||
@Dependency(\.psychrometricClient) var psychrometricClient
|
// @Dependency(\.psychrometricClient) var psychrometricClient
|
||||||
|
//
|
||||||
return .init(json: { route, logger in
|
// return .init(json: { route, logger in
|
||||||
logger.debug("API Route: \(route)")
|
// logger.debug("API Route: \(route)")
|
||||||
|
//
|
||||||
switch route {
|
// switch route {
|
||||||
case let .calculateAtticVentilation(request):
|
// case let .calculateAtticVentilation(request):
|
||||||
logger.debug("Calculating attic ventilation: \(request)")
|
// logger.debug("Calculating attic ventilation: \(request)")
|
||||||
return try await request.respond(logger: logger)
|
// return try await request.respond(logger: logger)
|
||||||
|
//
|
||||||
case let .calculateCapacitor(request):
|
// case let .calculateCapacitor(request):
|
||||||
logger.debug("Calculating capacitor: \(request)")
|
// logger.debug("Calculating capacitor: \(request)")
|
||||||
return try await request.respond(logger: logger)
|
// return try await request.respond(logger: logger)
|
||||||
|
//
|
||||||
case let .calculateDehumidifierSize(request):
|
// case let .calculateDehumidifierSize(request):
|
||||||
logger.debug("Calculating dehumidifier size: \(request)")
|
// logger.debug("Calculating dehumidifier size: \(request)")
|
||||||
return try await request.respond(logger)
|
// return try await request.respond(logger)
|
||||||
|
//
|
||||||
case let .calculateFilterPressureDrop(request):
|
// case let .calculateFilterPressureDrop(request):
|
||||||
logger.debug("Calculating filter pressure drop: \(request)")
|
// logger.debug("Calculating filter pressure drop: \(request)")
|
||||||
return try await request.respond(logger: logger)
|
// return try await request.respond(logger: logger)
|
||||||
|
//
|
||||||
case let .calculateHVACSystemPerformance(request):
|
// case let .calculateHVACSystemPerformance(request):
|
||||||
logger.debug("Calculating hvac system performance: \(request)")
|
// logger.debug("Calculating hvac system performance: \(request)")
|
||||||
return try await request.respond(logger: logger)
|
// return try await request.respond(logger: logger)
|
||||||
|
//
|
||||||
case let .calculateMoldRisk(request):
|
// case let .calculateMoldRisk(request):
|
||||||
logger.debug("Calculating mold risk: \(request)")
|
// logger.debug("Calculating mold risk: \(request)")
|
||||||
return try await psychrometricClient.respond(request, logger)
|
// return try await psychrometricClient.respond(request, logger)
|
||||||
|
//
|
||||||
case let .calculateRoomPressure(request):
|
// case let .calculateRoomPressure(request):
|
||||||
logger.debug("Calculating room pressure: \(request)")
|
// logger.debug("Calculating room pressure: \(request)")
|
||||||
return try await request.respond(logger: logger)
|
// return try await request.respond(logger: logger)
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ private extension HeatingBalancePoint.Request.Thermal {
|
|||||||
switch buildingHeatLoss {
|
switch buildingHeatLoss {
|
||||||
case let .known(btu: btu): return btu
|
case let .known(btu: btu): return btu
|
||||||
case let .estimated(squareFeet: squareFeet):
|
case let .estimated(squareFeet: squareFeet):
|
||||||
|
// TODO: Should this be 65 - designTemperature
|
||||||
return squareFeet * climateZone!.averageHeatLossPerSquareFoot * (70 - designTemperature)
|
return squareFeet * climateZone!.averageHeatLossPerSquareFoot * (70 - designTemperature)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +1,35 @@
|
|||||||
import ApiController
|
// import ApiController
|
||||||
import Routes
|
// import Routes
|
||||||
import Vapor
|
// import Vapor
|
||||||
|
//
|
||||||
extension ApiController {
|
// extension ApiController {
|
||||||
|
//
|
||||||
func respond(_ route: SiteRoute.Api, request: Vapor.Request) async throws -> any AsyncResponseEncodable {
|
// func respond(_ route: SiteRoute.Api, request: Vapor.Request) async throws -> any AsyncResponseEncodable {
|
||||||
guard let encodable = try await json(route, request.logger) else {
|
// guard let encodable = try await json(route, request.logger) else {
|
||||||
return HTTPStatus.ok
|
// return HTTPStatus.ok
|
||||||
}
|
// }
|
||||||
return AnyJSONResponse(value: encodable)
|
// return AnyJSONResponse(value: encodable)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
struct AnyJSONResponse: AsyncResponseEncodable {
|
// struct AnyJSONResponse: AsyncResponseEncodable {
|
||||||
public var headers: HTTPHeaders = ["Content-Type": "application/json"]
|
// public var headers: HTTPHeaders = ["Content-Type": "application/json"]
|
||||||
let value: any Encodable
|
// let value: any Encodable
|
||||||
|
//
|
||||||
init(additionalHeaders: HTTPHeaders = [:], value: any Encodable) {
|
// init(additionalHeaders: HTTPHeaders = [:], value: any Encodable) {
|
||||||
if additionalHeaders.contains(name: .contentType) {
|
// if additionalHeaders.contains(name: .contentType) {
|
||||||
self.headers = additionalHeaders
|
// self.headers = additionalHeaders
|
||||||
} else {
|
// } else {
|
||||||
headers.add(contentsOf: additionalHeaders)
|
// headers.add(contentsOf: additionalHeaders)
|
||||||
}
|
// }
|
||||||
self.value = value
|
// self.value = value
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
func encodeResponse(for request: Request) async throws -> Response {
|
// func encodeResponse(for request: Request) async throws -> Response {
|
||||||
try Response(
|
// try Response(
|
||||||
status: .ok,
|
// status: .ok,
|
||||||
headers: headers,
|
// headers: headers,
|
||||||
body: .init(data: JSONEncoder().encode(value))
|
// body: .init(data: JSONEncoder().encode(value))
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import ApiController
|
// import ApiController
|
||||||
import Dependencies
|
import Dependencies
|
||||||
import PsychrometricClientLive
|
import PsychrometricClientLive
|
||||||
import Vapor
|
import Vapor
|
||||||
@@ -9,19 +9,19 @@ import ViewController
|
|||||||
struct DependenciesMiddleware: AsyncMiddleware {
|
struct DependenciesMiddleware: AsyncMiddleware {
|
||||||
|
|
||||||
private let values: DependencyValues.Continuation
|
private let values: DependencyValues.Continuation
|
||||||
private let apiController: ApiController
|
// private let apiController: ApiController
|
||||||
private let psychrometricClient: PsychrometricClient
|
private let psychrometricClient: PsychrometricClient
|
||||||
// private let database: DatabaseClient
|
// private let database: DatabaseClient
|
||||||
private let viewController: ViewController
|
private let viewController: ViewController
|
||||||
|
|
||||||
init(
|
init(
|
||||||
// database: DatabaseClient,
|
// database: DatabaseClient,
|
||||||
apiController: ApiController = .liveValue,
|
// apiController: ApiController = .liveValue,
|
||||||
psychrometricClient: PsychrometricClient = .liveValue,
|
psychrometricClient: PsychrometricClient = .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.psychrometricClient = psychrometricClient
|
self.psychrometricClient = psychrometricClient
|
||||||
self.viewController = viewController
|
self.viewController = viewController
|
||||||
@@ -30,7 +30,7 @@ struct DependenciesMiddleware: AsyncMiddleware {
|
|||||||
func respond(to request: Request, chainingTo next: any AsyncResponder) async throws -> Response {
|
func respond(to request: Request, chainingTo next: any AsyncResponder) async throws -> Response {
|
||||||
try await values.yield {
|
try await values.yield {
|
||||||
try await withDependencies {
|
try await withDependencies {
|
||||||
$0.apiController = apiController
|
// $0.apiController = apiController
|
||||||
// $0.database = database
|
// $0.database = database
|
||||||
// $0.dateFormatter = .liveValue
|
// $0.dateFormatter = .liveValue
|
||||||
$0.psychrometricClient = psychrometricClient
|
$0.psychrometricClient = psychrometricClient
|
||||||
|
|||||||
@@ -55,12 +55,12 @@ private func siteHandler(
|
|||||||
request: Request,
|
request: Request,
|
||||||
route: SiteRoute
|
route: SiteRoute
|
||||||
) async throws -> any AsyncResponseEncodable {
|
) async throws -> any AsyncResponseEncodable {
|
||||||
@Dependency(\.apiController) var apiController
|
// @Dependency(\.apiController) var apiController
|
||||||
@Dependency(\.viewController) var viewController
|
@Dependency(\.viewController) var viewController
|
||||||
|
|
||||||
switch route {
|
switch route {
|
||||||
case let .api(route):
|
// case let .api(route):
|
||||||
return try await apiController.respond(route, request: request)
|
// return try await apiController.respond(route, request: request)
|
||||||
case .health:
|
case .health:
|
||||||
return HTTPStatus.ok
|
return HTTPStatus.ok
|
||||||
case let .view(route):
|
case let .view(route):
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ public extension HTMLAttribute.hx {
|
|||||||
put(SiteRoute.View.router.path(for: route))
|
put(SiteRoute.View.router.path(for: route))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Sendable
|
// @Sendable
|
||||||
static func delete(route: SiteRoute.Api) -> HTMLAttribute {
|
// static func delete(route: SiteRoute.Api) -> HTMLAttribute {
|
||||||
delete(SiteRoute.Api.router.path(for: route))
|
// delete(SiteRoute.Api.router.path(for: route))
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ import PsychrometricClient
|
|||||||
// swiftlint:disable type_body_length
|
// swiftlint:disable type_body_length
|
||||||
public enum SiteRoute: Equatable, Sendable {
|
public enum SiteRoute: Equatable, Sendable {
|
||||||
|
|
||||||
case api(Api)
|
// case api(Api)
|
||||||
case health
|
case health
|
||||||
case view(View)
|
case view(View)
|
||||||
|
|
||||||
public static let router = OneOf {
|
public static let router = OneOf {
|
||||||
Route(.case(Self.api)) {
|
// Route(.case(Self.api)) {
|
||||||
Api.router
|
// Api.router
|
||||||
}
|
// }
|
||||||
Route(.case(Self.health)) {
|
Route(.case(Self.health)) {
|
||||||
Path { "health" }
|
Path { "health" }
|
||||||
Method.get
|
Method.get
|
||||||
@@ -25,74 +25,74 @@ public enum SiteRoute: Equatable, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension SiteRoute {
|
// public extension SiteRoute {
|
||||||
|
//
|
||||||
enum Api: Equatable, Sendable {
|
// enum Api: Equatable, Sendable {
|
||||||
|
//
|
||||||
case calculateAtticVentilation(AtticVentilation.Request)
|
// case calculateAtticVentilation(AtticVentilation.Request)
|
||||||
case calculateCapacitor(Capacitor.Request)
|
// case calculateCapacitor(Capacitor.Request)
|
||||||
case calculateDehumidifierSize(DehumidifierSize.Request)
|
// case calculateDehumidifierSize(DehumidifierSize.Request)
|
||||||
case calculateFilterPressureDrop(FilterPressureDrop.Request)
|
// case calculateFilterPressureDrop(FilterPressureDrop.Request)
|
||||||
case calculateHVACSystemPerformance(HVACSystemPerformance.Request)
|
// case calculateHVACSystemPerformance(HVACSystemPerformance.Request)
|
||||||
case calculateMoldRisk(MoldRisk.Request)
|
// case calculateMoldRisk(MoldRisk.Request)
|
||||||
case calculateRoomPressure(RoomPressure.Request)
|
// case calculateRoomPressure(RoomPressure.Request)
|
||||||
|
//
|
||||||
static let rootPath = Path { "api"; "v1" }
|
// static let rootPath = Path { "api"; "v1" }
|
||||||
|
//
|
||||||
public static let router = OneOf {
|
// public static let router = OneOf {
|
||||||
Route(.case(Self.calculateAtticVentilation)) {
|
// Route(.case(Self.calculateAtticVentilation)) {
|
||||||
Path { "api"; "v1"; "calculateAtticPressure" }
|
// Path { "api"; "v1"; "calculateAtticPressure" }
|
||||||
Method.post
|
// Method.post
|
||||||
Body(.json(AtticVentilation.Request.self))
|
// Body(.json(AtticVentilation.Request.self))
|
||||||
}
|
// }
|
||||||
Route(.case(Self.calculateCapacitor)) {
|
// Route(.case(Self.calculateCapacitor)) {
|
||||||
Path { "api"; "v1"; "calculateRoomPressure" }
|
// Path { "api"; "v1"; "calculateRoomPressure" }
|
||||||
Method.post
|
// Method.post
|
||||||
OneOf {
|
// OneOf {
|
||||||
Body(.json(Capacitor.Request.SizeRequest.self))
|
// Body(.json(Capacitor.Request.SizeRequest.self))
|
||||||
.map(.case(Capacitor.Request.size))
|
// .map(.case(Capacitor.Request.size))
|
||||||
Body(.json(Capacitor.Request.TestRequest.self))
|
// Body(.json(Capacitor.Request.TestRequest.self))
|
||||||
.map(.case(Capacitor.Request.test))
|
// .map(.case(Capacitor.Request.test))
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
Route(.case(Self.calculateDehumidifierSize)) {
|
// Route(.case(Self.calculateDehumidifierSize)) {
|
||||||
Path { "api"; "v1"; "calculateDehumidifierSize" }
|
// Path { "api"; "v1"; "calculateDehumidifierSize" }
|
||||||
Method.post
|
// Method.post
|
||||||
Body(.json(DehumidifierSize.Request.self))
|
// Body(.json(DehumidifierSize.Request.self))
|
||||||
}
|
// }
|
||||||
Route(.case(Self.calculateFilterPressureDrop)) {
|
// Route(.case(Self.calculateFilterPressureDrop)) {
|
||||||
Path { "api"; "v1"; "calculateFilterPressureDrop" }
|
// Path { "api"; "v1"; "calculateFilterPressureDrop" }
|
||||||
Method.post
|
// Method.post
|
||||||
OneOf {
|
// OneOf {
|
||||||
Body(.json(FilterPressureDrop.Request.Basic.self))
|
// Body(.json(FilterPressureDrop.Request.Basic.self))
|
||||||
.map(.case(FilterPressureDrop.Request.basic))
|
// .map(.case(FilterPressureDrop.Request.basic))
|
||||||
Body(.json(FilterPressureDrop.Request.FanLaw.self))
|
// Body(.json(FilterPressureDrop.Request.FanLaw.self))
|
||||||
.map(.case(FilterPressureDrop.Request.fanLaw))
|
// .map(.case(FilterPressureDrop.Request.fanLaw))
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
Route(.case(Self.calculateHVACSystemPerformance)) {
|
// Route(.case(Self.calculateHVACSystemPerformance)) {
|
||||||
Path { "api"; "v1"; "calculateHVACSystemPerformance" }
|
// Path { "api"; "v1"; "calculateHVACSystemPerformance" }
|
||||||
Method.post
|
// Method.post
|
||||||
Body(.json(HVACSystemPerformance.Request.self))
|
// Body(.json(HVACSystemPerformance.Request.self))
|
||||||
}
|
// }
|
||||||
Route(.case(Self.calculateMoldRisk)) {
|
// Route(.case(Self.calculateMoldRisk)) {
|
||||||
Path { "api"; "v1"; "calculateMoldRisk" }
|
// Path { "api"; "v1"; "calculateMoldRisk" }
|
||||||
Method.post
|
// Method.post
|
||||||
Body(.json(MoldRisk.Request.self))
|
// Body(.json(MoldRisk.Request.self))
|
||||||
}
|
// }
|
||||||
Route(.case(Self.calculateRoomPressure)) {
|
// Route(.case(Self.calculateRoomPressure)) {
|
||||||
Path { "api"; "v1"; "calculateRoomPressure" }
|
// Path { "api"; "v1"; "calculateRoomPressure" }
|
||||||
Method.post
|
// Method.post
|
||||||
OneOf {
|
// OneOf {
|
||||||
Body(.json(RoomPressure.Request.KnownAirflow.self))
|
// Body(.json(RoomPressure.Request.KnownAirflow.self))
|
||||||
.map(.case(RoomPressure.Request.knownAirflow))
|
// .map(.case(RoomPressure.Request.knownAirflow))
|
||||||
Body(.json(RoomPressure.Request.MeasuredPressure.self))
|
// Body(.json(RoomPressure.Request.MeasuredPressure.self))
|
||||||
.map(.case(RoomPressure.Request.measuredPressure))
|
// .map(.case(RoomPressure.Request.measuredPressure))
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
public extension SiteRoute {
|
public extension SiteRoute {
|
||||||
enum View: Equatable, Sendable {
|
enum View: Equatable, Sendable {
|
||||||
|
|||||||
Reference in New Issue
Block a user