WIP: Working on a project detail database request to minimize database calls.
This commit is contained in:
@@ -33,6 +33,8 @@ extension SiteRoute.Api.ProjectRoute {
|
|||||||
return nil
|
return nil
|
||||||
case .detail(let id, let route):
|
case .detail(let id, let route):
|
||||||
switch route {
|
switch route {
|
||||||
|
case .index:
|
||||||
|
return try await database.projects.detail(id)
|
||||||
case .completedSteps:
|
case .completedSteps:
|
||||||
// FIX:
|
// FIX:
|
||||||
fatalError()
|
fatalError()
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ extension DatabaseClient {
|
|||||||
public struct Projects: Sendable {
|
public struct Projects: Sendable {
|
||||||
public var create: @Sendable (User.ID, Project.Create) async throws -> Project
|
public var create: @Sendable (User.ID, Project.Create) async throws -> Project
|
||||||
public var delete: @Sendable (Project.ID) async throws -> Void
|
public var delete: @Sendable (Project.ID) async throws -> Void
|
||||||
|
public var detail: @Sendable (Project.ID) async throws -> Project.Detail?
|
||||||
public var get: @Sendable (Project.ID) async throws -> Project?
|
public var get: @Sendable (Project.ID) async throws -> Project?
|
||||||
public var getCompletedSteps: @Sendable (Project.ID) async throws -> Project.CompletedSteps
|
public var getCompletedSteps: @Sendable (Project.ID) async throws -> Project.CompletedSteps
|
||||||
public var getSensibleHeatRatio: @Sendable (Project.ID) async throws -> Double?
|
public var getSensibleHeatRatio: @Sendable (Project.ID) async throws -> Double?
|
||||||
@@ -33,6 +34,34 @@ extension DatabaseClient.Projects: TestDependencyKey {
|
|||||||
}
|
}
|
||||||
try await model.delete(on: database)
|
try await model.delete(on: database)
|
||||||
},
|
},
|
||||||
|
detail: { id in
|
||||||
|
guard
|
||||||
|
let model = try await ProjectModel.query(on: database)
|
||||||
|
.with(\.$componentLosses)
|
||||||
|
.with(\.$equipment)
|
||||||
|
.with(\.$equivalentLengths)
|
||||||
|
.with(\.$rooms)
|
||||||
|
.with(\.$trunks, { $0.with(\.$rooms) })
|
||||||
|
.filter(\.$id == id)
|
||||||
|
.first()
|
||||||
|
else {
|
||||||
|
throw NotFoundError()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Different error ??
|
||||||
|
guard let equipmentInfo = model.equipment else { return nil }
|
||||||
|
|
||||||
|
let trunks = try await model.trunks.toDTO(on: database)
|
||||||
|
|
||||||
|
return try .init(
|
||||||
|
project: model.toDTO(),
|
||||||
|
componentLosses: model.componentLosses.map { try $0.toDTO() },
|
||||||
|
equipmentInfo: equipmentInfo.toDTO(),
|
||||||
|
equivalentLengths: model.equivalentLengths.map { try $0.toDTO() },
|
||||||
|
rooms: model.rooms.map { try $0.toDTO() },
|
||||||
|
trunks: trunks
|
||||||
|
)
|
||||||
|
},
|
||||||
get: { id in
|
get: { id in
|
||||||
try await ProjectModel.find(id, on: database).map { try $0.toDTO() }
|
try await ProjectModel.find(id, on: database).map { try $0.toDTO() }
|
||||||
},
|
},
|
||||||
@@ -248,6 +277,18 @@ final class ProjectModel: Model, @unchecked Sendable {
|
|||||||
@Children(for: \.$project)
|
@Children(for: \.$project)
|
||||||
var componentLosses: [ComponentLossModel]
|
var componentLosses: [ComponentLossModel]
|
||||||
|
|
||||||
|
@OptionalChild(for: \.$project)
|
||||||
|
var equipment: EquipmentModel?
|
||||||
|
|
||||||
|
@Children(for: \.$project)
|
||||||
|
var equivalentLengths: [EffectiveLengthModel]
|
||||||
|
|
||||||
|
@Children(for: \.$project)
|
||||||
|
var rooms: [RoomModel]
|
||||||
|
|
||||||
|
@Children(for: \.$project)
|
||||||
|
var trunks: [TrunkModel]
|
||||||
|
|
||||||
@Parent(key: "userID")
|
@Parent(key: "userID")
|
||||||
var user: UserModel
|
var user: UserModel
|
||||||
|
|
||||||
|
|||||||
@@ -84,6 +84,32 @@ extension Project {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct Detail: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
public let project: Project
|
||||||
|
public let componentLosses: [ComponentPressureLoss]
|
||||||
|
public let equipmentInfo: EquipmentInfo
|
||||||
|
public let equivalentLengths: [EffectiveLength]
|
||||||
|
public let rooms: [Room]
|
||||||
|
public let trunks: [TrunkSize]
|
||||||
|
|
||||||
|
public init(
|
||||||
|
project: Project,
|
||||||
|
componentLosses: [ComponentPressureLoss],
|
||||||
|
equipmentInfo: EquipmentInfo,
|
||||||
|
equivalentLengths: [EffectiveLength],
|
||||||
|
rooms: [Room],
|
||||||
|
trunks: [TrunkSize]
|
||||||
|
) {
|
||||||
|
self.project = project
|
||||||
|
self.componentLosses = componentLosses
|
||||||
|
self.equipmentInfo = equipmentInfo
|
||||||
|
self.equivalentLengths = equivalentLengths
|
||||||
|
self.rooms = rooms
|
||||||
|
self.trunks = trunks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct Update: Codable, Equatable, Sendable {
|
public struct Update: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
public let name: String?
|
public let name: String?
|
||||||
|
|||||||
@@ -88,11 +88,16 @@ extension SiteRoute.Api {
|
|||||||
|
|
||||||
extension SiteRoute.Api.ProjectRoute {
|
extension SiteRoute.Api.ProjectRoute {
|
||||||
public enum DetailRoute: Equatable, Sendable {
|
public enum DetailRoute: Equatable, Sendable {
|
||||||
|
case index
|
||||||
case completedSteps
|
case completedSteps
|
||||||
|
|
||||||
static let rootPath = "details"
|
static let rootPath = "details"
|
||||||
|
|
||||||
static let router = OneOf {
|
static let router = OneOf {
|
||||||
|
Route(.case(Self.index)) {
|
||||||
|
Path { rootPath }
|
||||||
|
Method.get
|
||||||
|
}
|
||||||
Route(.case(Self.completedSteps)) {
|
Route(.case(Self.completedSteps)) {
|
||||||
Path {
|
Path {
|
||||||
rootPath
|
rootPath
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
case equipment(EquipmentInfoRoute)
|
case equipment(EquipmentInfoRoute)
|
||||||
case equivalentLength(EquivalentLengthRoute)
|
case equivalentLength(EquivalentLengthRoute)
|
||||||
case frictionRate(FrictionRateRoute)
|
case frictionRate(FrictionRateRoute)
|
||||||
|
case pdf
|
||||||
case rooms(RoomRoute)
|
case rooms(RoomRoute)
|
||||||
|
|
||||||
static let router = OneOf {
|
static let router = OneOf {
|
||||||
@@ -167,6 +168,10 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
Route(.case(Self.frictionRate)) {
|
Route(.case(Self.frictionRate)) {
|
||||||
FrictionRateRoute.router
|
FrictionRateRoute.router
|
||||||
}
|
}
|
||||||
|
Route(.case(Self.pdf)) {
|
||||||
|
Path { "pdf" }
|
||||||
|
Method.get
|
||||||
|
}
|
||||||
Route(.case(Self.rooms)) {
|
Route(.case(Self.rooms)) {
|
||||||
RoomRoute.router
|
RoomRoute.router
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,31 +5,51 @@ import ManualDCore
|
|||||||
|
|
||||||
extension ManualDClient {
|
extension ManualDClient {
|
||||||
|
|
||||||
|
func frictionRate(details: Project.Detail) async throws -> ProjectClient.FrictionRateResponse {
|
||||||
|
|
||||||
|
let maxContainer = details.maxContainer
|
||||||
|
guard let totalEquivalentLength = maxContainer.total else {
|
||||||
|
return .init(componentLosses: details.componentLosses, equivalentLengths: maxContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return try await .init(
|
||||||
|
componentLosses: details.componentLosses,
|
||||||
|
equivalentLengths: maxContainer,
|
||||||
|
frictionRate: frictionRate(
|
||||||
|
.init(
|
||||||
|
externalStaticPressure: details.equipmentInfo.staticPressure,
|
||||||
|
componentPressureLosses: details.componentLosses,
|
||||||
|
totalEffectiveLength: Int(totalEquivalentLength)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
func frictionRate(projectID: Project.ID) async throws -> ProjectClient.FrictionRateResponse {
|
func frictionRate(projectID: Project.ID) async throws -> ProjectClient.FrictionRateResponse {
|
||||||
@Dependency(\.database) var database
|
@Dependency(\.database) var database
|
||||||
|
|
||||||
let componentLosses = try await database.componentLoss.fetch(projectID)
|
let componentLosses = try await database.componentLoss.fetch(projectID)
|
||||||
let lengths = try await database.effectiveLength.fetchMax(projectID)
|
let lengths = try await database.effectiveLength.fetchMax(projectID)
|
||||||
|
|
||||||
let equipmentInfo = try await database.equipment.fetch(projectID)
|
let equipmentInfo = try await database.equipment.fetch(projectID)
|
||||||
guard let staticPressure = equipmentInfo?.staticPressure else {
|
guard let staticPressure = equipmentInfo?.staticPressure else {
|
||||||
return .init(componentLosses: componentLosses, equivalentLengths: lengths)
|
return .init(componentLosses: componentLosses, equivalentLengths: lengths)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let totalEquivalentLength = lengths.total else {
|
guard let totalEquivalentLength = lengths.total else {
|
||||||
return .init(componentLosses: componentLosses, equivalentLengths: lengths)
|
return .init(componentLosses: componentLosses, equivalentLengths: lengths)
|
||||||
}
|
}
|
||||||
|
|
||||||
return try await .init(
|
return try await .init(
|
||||||
componentLosses: componentLosses,
|
componentLosses: componentLosses,
|
||||||
equivalentLengths: lengths,
|
equivalentLengths: lengths,
|
||||||
frictionRate: frictionRate(
|
frictionRate: frictionRate(
|
||||||
.init(
|
.init(
|
||||||
externalStaticPressure: staticPressure,
|
externalStaticPressure: staticPressure,
|
||||||
componentPressureLosses: database.componentLoss.fetch(projectID),
|
componentPressureLosses: database.componentLoss.fetch(projectID),
|
||||||
totalEffectiveLength: Int(totalEquivalentLength)
|
totalEffectiveLength: Int(totalEquivalentLength)
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import ManualDCore
|
||||||
|
|
||||||
|
extension Project.Detail {
|
||||||
|
var maxContainer: EffectiveLength.MaxContainer {
|
||||||
|
.init(
|
||||||
|
supply: equivalentLengths.filter({ $0.type == .supply })
|
||||||
|
.sorted(by: { $0.totalEquivalentLength > $1.totalEquivalentLength })
|
||||||
|
.first,
|
||||||
|
return: equivalentLengths.filter({ $0.type == .return })
|
||||||
|
.sorted(by: { $0.totalEquivalentLength > $1.totalEquivalentLength })
|
||||||
|
.first
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@ public struct DateView: HTML, Sendable {
|
|||||||
|
|
||||||
var formatter: DateFormatter {
|
var formatter: DateFormatter {
|
||||||
let formatter = DateFormatter()
|
let formatter = DateFormatter()
|
||||||
formatter.dateStyle = .short
|
formatter.dateFormat = "MM/dd/yyyy"
|
||||||
return formatter
|
return formatter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,6 +67,19 @@ extension ResultView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension ResultView where V == ValueView {
|
||||||
|
|
||||||
|
public init(
|
||||||
|
catching: @escaping @Sendable () async throws(E) -> V
|
||||||
|
) async where ErrorView == Styleguide.ErrorView<E> {
|
||||||
|
await self.init(result: .init(catching: catching)) {
|
||||||
|
$0
|
||||||
|
} onError: { error in
|
||||||
|
Styleguide.ErrorView(error: error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension ResultView: Sendable where Error: Sendable, ValueView: Sendable, ErrorView: Sendable {}
|
extension ResultView: Sendable where Error: Sendable, ValueView: Sendable, ErrorView: Sendable {}
|
||||||
|
|
||||||
public struct ErrorView<E: Error>: HTML, Sendable where Error: Sendable {
|
public struct ErrorView<E: Error>: HTML, Sendable where Error: Sendable {
|
||||||
|
|||||||
@@ -192,6 +192,11 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
return await route.renderView(on: request, projectID: projectID)
|
return await route.renderView(on: request, projectID: projectID)
|
||||||
case .frictionRate(let route):
|
case .frictionRate(let route):
|
||||||
return await route.renderView(on: request, projectID: projectID)
|
return await route.renderView(on: request, projectID: projectID)
|
||||||
|
case .pdf:
|
||||||
|
return await ResultView {
|
||||||
|
try await projectClient.toHTML(projectID)
|
||||||
|
}
|
||||||
|
// fatalError()
|
||||||
case .rooms(let route):
|
case .rooms(let route):
|
||||||
return await route.renderView(on: request, projectID: projectID)
|
return await route.renderView(on: request, projectID: projectID)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,14 @@ struct DuctSizingView: HTML, Sendable {
|
|||||||
.hidden(when: ductSizes.rooms.count > 0)
|
.hidden(when: ductSizes.rooms.count > 0)
|
||||||
.attributes(.class("text-error font-bold italic mt-4"))
|
.attributes(.class("text-error font-bold italic mt-4"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a(
|
||||||
|
.class("btn btn-primary"),
|
||||||
|
.href(route: .project(.detail(projectID, .pdf)))
|
||||||
|
) {
|
||||||
|
"PDF"
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ductSizes.rooms.count != 0 {
|
if ductSizes.rooms.count != 0 {
|
||||||
|
|||||||
Reference in New Issue
Block a user