Compare commits
7 Commits
main
...
18a5ef06d3
| Author | SHA1 | Date | |
|---|---|---|---|
|
18a5ef06d3
|
|||
|
6723f7a410
|
|||
|
5440024038
|
|||
|
f005b43936
|
|||
|
f44b35ab3d
|
|||
|
bbf9a8b390
|
|||
|
c52cee212f
|
31
.gitea/workflows/ci.yaml
Normal file
31
.gitea/workflows/ci.yaml
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- dev
|
||||||
|
pull_request:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ubuntu:
|
||||||
|
name: Linux Tests
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Setup QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Setup buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
- name: Build test image
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/Dockerfile.test
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
tags: michael/ductcalc:test
|
||||||
|
- name: Run Tests
|
||||||
|
run: |
|
||||||
|
docker run --rm michael/ductcalc:test swift test
|
||||||
@@ -120,11 +120,10 @@ let package = Package(
|
|||||||
.target(name: "HTMLSnapshotTesting"),
|
.target(name: "HTMLSnapshotTesting"),
|
||||||
.target(name: "PdfClient"),
|
.target(name: "PdfClient"),
|
||||||
.product(name: "SnapshotTesting", package: "swift-snapshot-testing"),
|
.product(name: "SnapshotTesting", package: "swift-snapshot-testing"),
|
||||||
|
],
|
||||||
|
resources: [
|
||||||
|
.copy("__Snapshots__")
|
||||||
]
|
]
|
||||||
// ,
|
|
||||||
// resources: [
|
|
||||||
// .copy("__Snapshots__")
|
|
||||||
// ]
|
|
||||||
),
|
),
|
||||||
.target(
|
.target(
|
||||||
name: "ProjectClient",
|
name: "ProjectClient",
|
||||||
|
|||||||
@@ -104,14 +104,14 @@ extension SiteRoute.Api.ComponentLossRoute {
|
|||||||
|
|
||||||
switch self {
|
switch self {
|
||||||
case .create(let request):
|
case .create(let request):
|
||||||
return try await database.componentLoss.create(request)
|
return try await database.componentLosses.create(request)
|
||||||
case .delete(let id):
|
case .delete(let id):
|
||||||
try await database.componentLoss.delete(id)
|
try await database.componentLosses.delete(id)
|
||||||
return nil
|
return nil
|
||||||
case .fetch(let projectID):
|
case .fetch(let projectID):
|
||||||
return try await database.componentLoss.fetch(projectID)
|
return try await database.componentLosses.fetch(projectID)
|
||||||
case .get(let id):
|
case .get(let id):
|
||||||
guard let room = try await database.componentLoss.get(id) else {
|
guard let room = try await database.componentLosses.get(id) else {
|
||||||
logger.error("Component loss not found for id: \(id)")
|
logger.error("Component loss not found for id: \(id)")
|
||||||
throw ApiError("Component loss not found.")
|
throw ApiError("Component loss not found.")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,11 +16,108 @@ public struct DatabaseClient: Sendable {
|
|||||||
public var projects: Projects
|
public var projects: Projects
|
||||||
public var rooms: Rooms
|
public var rooms: Rooms
|
||||||
public var equipment: Equipment
|
public var equipment: Equipment
|
||||||
public var componentLoss: ComponentLoss
|
public var componentLosses: ComponentLosses
|
||||||
public var effectiveLength: EffectiveLengthClient
|
public var equivalentLengths: EquivalentLengths
|
||||||
public var users: Users
|
public var users: Users
|
||||||
public var userProfile: UserProfile
|
public var userProfiles: UserProfiles
|
||||||
public var trunkSizes: TrunkSizes
|
public var trunkSizes: TrunkSizes
|
||||||
|
|
||||||
|
@DependencyClient
|
||||||
|
public struct ComponentLosses: Sendable {
|
||||||
|
public var create:
|
||||||
|
@Sendable (ComponentPressureLoss.Create) async throws -> ComponentPressureLoss
|
||||||
|
public var delete: @Sendable (ComponentPressureLoss.ID) async throws -> Void
|
||||||
|
public var fetch: @Sendable (Project.ID) async throws -> [ComponentPressureLoss]
|
||||||
|
public var get: @Sendable (ComponentPressureLoss.ID) async throws -> ComponentPressureLoss?
|
||||||
|
public var update:
|
||||||
|
@Sendable (ComponentPressureLoss.ID, ComponentPressureLoss.Update) async throws ->
|
||||||
|
ComponentPressureLoss
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependencyClient
|
||||||
|
public struct EquivalentLengths: Sendable {
|
||||||
|
public var create: @Sendable (EquivalentLength.Create) async throws -> EquivalentLength
|
||||||
|
public var delete: @Sendable (EquivalentLength.ID) async throws -> Void
|
||||||
|
public var fetch: @Sendable (Project.ID) async throws -> [EquivalentLength]
|
||||||
|
public var fetchMax: @Sendable (Project.ID) async throws -> EquivalentLength.MaxContainer
|
||||||
|
public var get: @Sendable (EquivalentLength.ID) async throws -> EquivalentLength?
|
||||||
|
public var update:
|
||||||
|
@Sendable (EquivalentLength.ID, EquivalentLength.Update) async throws -> EquivalentLength
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependencyClient
|
||||||
|
public struct Equipment: Sendable {
|
||||||
|
public var create: @Sendable (EquipmentInfo.Create) async throws -> EquipmentInfo
|
||||||
|
public var delete: @Sendable (EquipmentInfo.ID) async throws -> Void
|
||||||
|
public var fetch: @Sendable (Project.ID) async throws -> EquipmentInfo?
|
||||||
|
public var get: @Sendable (EquipmentInfo.ID) async throws -> EquipmentInfo?
|
||||||
|
public var update:
|
||||||
|
@Sendable (EquipmentInfo.ID, EquipmentInfo.Update) async throws -> EquipmentInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependencyClient
|
||||||
|
public struct Migrations: Sendable {
|
||||||
|
public var all: @Sendable () async throws -> [any AsyncMigration]
|
||||||
|
|
||||||
|
public func callAsFunction() async throws -> [any AsyncMigration] {
|
||||||
|
try await self.all()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependencyClient
|
||||||
|
public struct Projects: Sendable {
|
||||||
|
public var create: @Sendable (User.ID, Project.Create) async throws -> Project
|
||||||
|
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 getCompletedSteps: @Sendable (Project.ID) async throws -> Project.CompletedSteps
|
||||||
|
public var getSensibleHeatRatio: @Sendable (Project.ID) async throws -> Double?
|
||||||
|
public var fetch: @Sendable (User.ID, PageRequest) async throws -> Page<Project>
|
||||||
|
public var update: @Sendable (Project.ID, Project.Update) async throws -> Project
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependencyClient
|
||||||
|
public struct Rooms: Sendable {
|
||||||
|
public var create: @Sendable (Room.Create) async throws -> Room
|
||||||
|
public var delete: @Sendable (Room.ID) async throws -> Void
|
||||||
|
public var deleteRectangularSize:
|
||||||
|
@Sendable (Room.ID, Room.RectangularSize.ID) async throws -> Room
|
||||||
|
public var get: @Sendable (Room.ID) async throws -> Room?
|
||||||
|
public var fetch: @Sendable (Project.ID) async throws -> [Room]
|
||||||
|
public var update: @Sendable (Room.ID, Room.Update) async throws -> Room
|
||||||
|
public var updateRectangularSize: @Sendable (Room.ID, Room.RectangularSize) async throws -> Room
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependencyClient
|
||||||
|
public struct TrunkSizes: Sendable {
|
||||||
|
public var create: @Sendable (TrunkSize.Create) async throws -> TrunkSize
|
||||||
|
public var delete: @Sendable (TrunkSize.ID) async throws -> Void
|
||||||
|
public var fetch: @Sendable (Project.ID) async throws -> [TrunkSize]
|
||||||
|
public var get: @Sendable (TrunkSize.ID) async throws -> TrunkSize?
|
||||||
|
public var update:
|
||||||
|
@Sendable (TrunkSize.ID, TrunkSize.Update) async throws ->
|
||||||
|
TrunkSize
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependencyClient
|
||||||
|
public struct UserProfiles: Sendable {
|
||||||
|
public var create: @Sendable (User.Profile.Create) async throws -> User.Profile
|
||||||
|
public var delete: @Sendable (User.Profile.ID) async throws -> Void
|
||||||
|
public var fetch: @Sendable (User.ID) async throws -> User.Profile?
|
||||||
|
public var get: @Sendable (User.Profile.ID) async throws -> User.Profile?
|
||||||
|
public var update: @Sendable (User.Profile.ID, User.Profile.Update) async throws -> User.Profile
|
||||||
|
}
|
||||||
|
|
||||||
|
@DependencyClient
|
||||||
|
public struct Users: Sendable {
|
||||||
|
public var create: @Sendable (User.Create) async throws -> User
|
||||||
|
public var delete: @Sendable (User.ID) async throws -> Void
|
||||||
|
public var get: @Sendable (User.ID) async throws -> User?
|
||||||
|
public var login: @Sendable (User.Login) async throws -> User.Token
|
||||||
|
public var logout: @Sendable (User.Token.ID) async throws -> Void
|
||||||
|
// public var token: @Sendable (User.ID) async throws -> User.Token
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DatabaseClient: TestDependencyKey {
|
extension DatabaseClient: TestDependencyKey {
|
||||||
@@ -29,10 +126,10 @@ extension DatabaseClient: TestDependencyKey {
|
|||||||
projects: .testValue,
|
projects: .testValue,
|
||||||
rooms: .testValue,
|
rooms: .testValue,
|
||||||
equipment: .testValue,
|
equipment: .testValue,
|
||||||
componentLoss: .testValue,
|
componentLosses: .testValue,
|
||||||
effectiveLength: .testValue,
|
equivalentLengths: .testValue,
|
||||||
users: .testValue,
|
users: .testValue,
|
||||||
userProfile: .testValue,
|
userProfiles: .testValue,
|
||||||
trunkSizes: .testValue
|
trunkSizes: .testValue
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -42,33 +139,20 @@ extension DatabaseClient: TestDependencyKey {
|
|||||||
projects: .live(database: database),
|
projects: .live(database: database),
|
||||||
rooms: .live(database: database),
|
rooms: .live(database: database),
|
||||||
equipment: .live(database: database),
|
equipment: .live(database: database),
|
||||||
componentLoss: .live(database: database),
|
componentLosses: .live(database: database),
|
||||||
effectiveLength: .live(database: database),
|
equivalentLengths: .live(database: database),
|
||||||
users: .live(database: database),
|
users: .live(database: database),
|
||||||
userProfile: .live(database: database),
|
userProfiles: .live(database: database),
|
||||||
trunkSizes: .live(database: database)
|
trunkSizes: .live(database: database)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DatabaseClient {
|
|
||||||
@DependencyClient
|
|
||||||
public struct Migrations: Sendable {
|
|
||||||
public var run: @Sendable () async throws -> [any AsyncMigration]
|
|
||||||
|
|
||||||
public func callAsFunction() async throws -> [any AsyncMigration] {
|
|
||||||
try await self.run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension DatabaseClient.Migrations: TestDependencyKey {
|
|
||||||
public static let testValue = Self()
|
|
||||||
}
|
|
||||||
|
|
||||||
extension DatabaseClient.Migrations: DependencyKey {
|
extension DatabaseClient.Migrations: DependencyKey {
|
||||||
|
public static let testValue = Self()
|
||||||
|
|
||||||
public static let liveValue = Self(
|
public static let liveValue = Self(
|
||||||
run: {
|
all: {
|
||||||
[
|
[
|
||||||
Project.Migrate(),
|
Project.Migrate(),
|
||||||
User.Migrate(),
|
User.Migrate(),
|
||||||
@@ -77,7 +161,7 @@ extension DatabaseClient.Migrations: DependencyKey {
|
|||||||
ComponentPressureLoss.Migrate(),
|
ComponentPressureLoss.Migrate(),
|
||||||
EquipmentInfo.Migrate(),
|
EquipmentInfo.Migrate(),
|
||||||
Room.Migrate(),
|
Room.Migrate(),
|
||||||
EffectiveLength.Migrate(),
|
EquivalentLength.Migrate(),
|
||||||
TrunkSize.Migrate(),
|
TrunkSize.Migrate(),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,25 +4,11 @@ import Fluent
|
|||||||
import Foundation
|
import Foundation
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
|
||||||
extension DatabaseClient {
|
extension DatabaseClient.ComponentLosses: TestDependencyKey {
|
||||||
@DependencyClient
|
|
||||||
public struct ComponentLoss: Sendable {
|
|
||||||
public var create:
|
|
||||||
@Sendable (ComponentPressureLoss.Create) async throws -> ComponentPressureLoss
|
|
||||||
public var delete: @Sendable (ComponentPressureLoss.ID) async throws -> Void
|
|
||||||
public var fetch: @Sendable (Project.ID) async throws -> [ComponentPressureLoss]
|
|
||||||
public var get: @Sendable (ComponentPressureLoss.ID) async throws -> ComponentPressureLoss?
|
|
||||||
public var update:
|
|
||||||
@Sendable (ComponentPressureLoss.ID, ComponentPressureLoss.Update) async throws ->
|
|
||||||
ComponentPressureLoss
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension DatabaseClient.ComponentLoss: TestDependencyKey {
|
|
||||||
public static let testValue = Self()
|
public static let testValue = Self()
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DatabaseClient.ComponentLoss {
|
extension DatabaseClient.ComponentLosses {
|
||||||
public static func live(database: any Database) -> Self {
|
public static func live(database: any Database) -> Self {
|
||||||
.init(
|
.init(
|
||||||
create: { request in
|
create: { request in
|
||||||
@@ -4,20 +4,7 @@ import Fluent
|
|||||||
import Foundation
|
import Foundation
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
|
||||||
extension DatabaseClient {
|
extension DatabaseClient.EquivalentLengths: TestDependencyKey {
|
||||||
@DependencyClient
|
|
||||||
public struct EffectiveLengthClient: Sendable {
|
|
||||||
public var create: @Sendable (EffectiveLength.Create) async throws -> EffectiveLength
|
|
||||||
public var delete: @Sendable (EffectiveLength.ID) async throws -> Void
|
|
||||||
public var fetch: @Sendable (Project.ID) async throws -> [EffectiveLength]
|
|
||||||
public var fetchMax: @Sendable (Project.ID) async throws -> EffectiveLength.MaxContainer
|
|
||||||
public var get: @Sendable (EffectiveLength.ID) async throws -> EffectiveLength?
|
|
||||||
public var update:
|
|
||||||
@Sendable (EffectiveLength.ID, EffectiveLength.Update) async throws -> EffectiveLength
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension DatabaseClient.EffectiveLengthClient: TestDependencyKey {
|
|
||||||
public static let testValue = Self()
|
public static let testValue = Self()
|
||||||
|
|
||||||
public static func live(database: any Database) -> Self {
|
public static func live(database: any Database) -> Self {
|
||||||
@@ -74,7 +61,7 @@ extension DatabaseClient.EffectiveLengthClient: TestDependencyKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension EffectiveLength.Create {
|
extension EquivalentLength.Create {
|
||||||
|
|
||||||
func toModel() throws -> EffectiveLengthModel {
|
func toModel() throws -> EffectiveLengthModel {
|
||||||
try validate()
|
try validate()
|
||||||
@@ -94,7 +81,7 @@ extension EffectiveLength.Create {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension EffectiveLength {
|
extension EquivalentLength {
|
||||||
|
|
||||||
struct Migrate: AsyncMigration {
|
struct Migrate: AsyncMigration {
|
||||||
let name = "CreateEffectiveLength"
|
let name = "CreateEffectiveLength"
|
||||||
@@ -173,20 +160,20 @@ final class EffectiveLengthModel: Model, @unchecked Sendable {
|
|||||||
$project.id = projectID
|
$project.id = projectID
|
||||||
}
|
}
|
||||||
|
|
||||||
func toDTO() throws -> EffectiveLength {
|
func toDTO() throws -> EquivalentLength {
|
||||||
try .init(
|
try .init(
|
||||||
id: requireID(),
|
id: requireID(),
|
||||||
projectID: $project.id,
|
projectID: $project.id,
|
||||||
name: name,
|
name: name,
|
||||||
type: .init(rawValue: type)!,
|
type: .init(rawValue: type)!,
|
||||||
straightLengths: straightLengths,
|
straightLengths: straightLengths,
|
||||||
groups: JSONDecoder().decode([EffectiveLength.Group].self, from: groups),
|
groups: JSONDecoder().decode([EquivalentLength.FittingGroup].self, from: groups),
|
||||||
createdAt: createdAt!,
|
createdAt: createdAt!,
|
||||||
updatedAt: updatedAt!
|
updatedAt: updatedAt!
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyUpdates(_ updates: EffectiveLength.Update) throws {
|
func applyUpdates(_ updates: EquivalentLength.Update) throws {
|
||||||
if let name = updates.name, name != self.name {
|
if let name = updates.name, name != self.name {
|
||||||
self.name = name
|
self.name = name
|
||||||
}
|
}
|
||||||
@@ -4,23 +4,9 @@ import Fluent
|
|||||||
import Foundation
|
import Foundation
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
|
||||||
extension DatabaseClient {
|
|
||||||
@DependencyClient
|
|
||||||
public struct Equipment: Sendable {
|
|
||||||
public var create: @Sendable (EquipmentInfo.Create) async throws -> EquipmentInfo
|
|
||||||
public var delete: @Sendable (EquipmentInfo.ID) async throws -> Void
|
|
||||||
public var fetch: @Sendable (Project.ID) async throws -> EquipmentInfo?
|
|
||||||
public var get: @Sendable (EquipmentInfo.ID) async throws -> EquipmentInfo?
|
|
||||||
public var update:
|
|
||||||
@Sendable (EquipmentInfo.ID, EquipmentInfo.Update) async throws -> EquipmentInfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension DatabaseClient.Equipment: TestDependencyKey {
|
extension DatabaseClient.Equipment: TestDependencyKey {
|
||||||
public static let testValue = Self()
|
public static let testValue = Self()
|
||||||
}
|
|
||||||
|
|
||||||
extension DatabaseClient.Equipment {
|
|
||||||
public static func live(database: any Database) -> Self {
|
public static func live(database: any Database) -> Self {
|
||||||
.init(
|
.init(
|
||||||
create: { request in
|
create: { request in
|
||||||
@@ -4,20 +4,6 @@ import Fluent
|
|||||||
import Foundation
|
import Foundation
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
|
||||||
extension DatabaseClient {
|
|
||||||
@DependencyClient
|
|
||||||
public struct Projects: Sendable {
|
|
||||||
public var create: @Sendable (User.ID, Project.Create) async throws -> Project
|
|
||||||
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 getCompletedSteps: @Sendable (Project.ID) async throws -> Project.CompletedSteps
|
|
||||||
public var getSensibleHeatRatio: @Sendable (Project.ID) async throws -> Double?
|
|
||||||
public var fetch: @Sendable (User.ID, PageRequest) async throws -> Page<Project>
|
|
||||||
public var update: @Sendable (Project.ID, Project.Update) async throws -> Project
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension DatabaseClient.Projects: TestDependencyKey {
|
extension DatabaseClient.Projects: TestDependencyKey {
|
||||||
public static let testValue = Self()
|
public static let testValue = Self()
|
||||||
|
|
||||||
@@ -4,20 +4,6 @@ import Fluent
|
|||||||
import Foundation
|
import Foundation
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
|
||||||
extension DatabaseClient {
|
|
||||||
@DependencyClient
|
|
||||||
public struct Rooms: Sendable {
|
|
||||||
public var create: @Sendable (Room.Create) async throws -> Room
|
|
||||||
public var delete: @Sendable (Room.ID) async throws -> Void
|
|
||||||
public var deleteRectangularSize:
|
|
||||||
@Sendable (Room.ID, Room.RectangularSize.ID) async throws -> Room
|
|
||||||
public var get: @Sendable (Room.ID) async throws -> Room?
|
|
||||||
public var fetch: @Sendable (Project.ID) async throws -> [Room]
|
|
||||||
public var update: @Sendable (Room.ID, Room.Update) async throws -> Room
|
|
||||||
public var updateRectangularSize: @Sendable (Room.ID, Room.RectangularSize) async throws -> Room
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension DatabaseClient.Rooms: TestDependencyKey {
|
extension DatabaseClient.Rooms: TestDependencyKey {
|
||||||
public static let testValue = Self()
|
public static let testValue = Self()
|
||||||
|
|
||||||
@@ -4,19 +4,6 @@ import Fluent
|
|||||||
import Foundation
|
import Foundation
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
|
||||||
extension DatabaseClient {
|
|
||||||
@DependencyClient
|
|
||||||
public struct TrunkSizes: Sendable {
|
|
||||||
public var create: @Sendable (TrunkSize.Create) async throws -> TrunkSize
|
|
||||||
public var delete: @Sendable (TrunkSize.ID) async throws -> Void
|
|
||||||
public var fetch: @Sendable (Project.ID) async throws -> [TrunkSize]
|
|
||||||
public var get: @Sendable (TrunkSize.ID) async throws -> TrunkSize?
|
|
||||||
public var update:
|
|
||||||
@Sendable (TrunkSize.ID, TrunkSize.Update) async throws ->
|
|
||||||
TrunkSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension DatabaseClient.TrunkSizes: TestDependencyKey {
|
extension DatabaseClient.TrunkSizes: TestDependencyKey {
|
||||||
public static let testValue = Self()
|
public static let testValue = Self()
|
||||||
|
|
||||||
@@ -4,18 +4,7 @@ import Fluent
|
|||||||
import ManualDCore
|
import ManualDCore
|
||||||
import Vapor
|
import Vapor
|
||||||
|
|
||||||
extension DatabaseClient {
|
extension DatabaseClient.UserProfiles: TestDependencyKey {
|
||||||
@DependencyClient
|
|
||||||
public struct UserProfile: Sendable {
|
|
||||||
public var create: @Sendable (User.Profile.Create) async throws -> User.Profile
|
|
||||||
public var delete: @Sendable (User.Profile.ID) async throws -> Void
|
|
||||||
public var fetch: @Sendable (User.ID) async throws -> User.Profile?
|
|
||||||
public var get: @Sendable (User.Profile.ID) async throws -> User.Profile?
|
|
||||||
public var update: @Sendable (User.Profile.ID, User.Profile.Update) async throws -> User.Profile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension DatabaseClient.UserProfile: TestDependencyKey {
|
|
||||||
|
|
||||||
public static let testValue = Self()
|
public static let testValue = Self()
|
||||||
|
|
||||||
@@ -4,19 +4,6 @@ import Fluent
|
|||||||
import ManualDCore
|
import ManualDCore
|
||||||
import Vapor
|
import Vapor
|
||||||
|
|
||||||
extension DatabaseClient {
|
|
||||||
@DependencyClient
|
|
||||||
public struct Users: Sendable {
|
|
||||||
public var create: @Sendable (User.Create) async throws -> User
|
|
||||||
public var delete: @Sendable (User.ID) async throws -> Void
|
|
||||||
public var get: @Sendable (User.ID) async throws -> User?
|
|
||||||
public var login: @Sendable (User.Login) async throws -> User.Token
|
|
||||||
public var logout: @Sendable (User.Token.ID) async throws -> Void
|
|
||||||
// public var token: @Sendable (User.ID) async throws -> User.Token
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension DatabaseClient.Users: TestDependencyKey {
|
extension DatabaseClient.Users: TestDependencyKey {
|
||||||
public static let testValue = Self()
|
public static let testValue = Self()
|
||||||
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
// import Dependencies
|
|
||||||
// import DependenciesMacros
|
|
||||||
// import Fluent
|
|
||||||
// import Foundation
|
|
||||||
// import ManualDCore
|
|
||||||
//
|
|
||||||
// extension DatabaseClient {
|
|
||||||
// @DependencyClient
|
|
||||||
// public struct RectangularDuct: Sendable {
|
|
||||||
// public var create:
|
|
||||||
// @Sendable (DuctSizing.RectangularDuct.Create) async throws -> DuctSizing.RectangularDuct
|
|
||||||
// public var delete: @Sendable (DuctSizing.RectangularDuct.ID) async throws -> Void
|
|
||||||
// public var fetch: @Sendable (Room.ID) async throws -> [DuctSizing.RectangularDuct]
|
|
||||||
// public var get:
|
|
||||||
// @Sendable (DuctSizing.RectangularDuct.ID) async throws -> DuctSizing.RectangularDuct?
|
|
||||||
// public var update:
|
|
||||||
// @Sendable (DuctSizing.RectangularDuct.ID, DuctSizing.RectangularDuct.Update) async throws ->
|
|
||||||
// DuctSizing.RectangularDuct
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// extension DatabaseClient.RectangularDuct: TestDependencyKey {
|
|
||||||
// public static let testValue = Self()
|
|
||||||
//
|
|
||||||
// public static func live(database: any Database) -> Self {
|
|
||||||
// .init(
|
|
||||||
// create: { request in
|
|
||||||
// try request.validate()
|
|
||||||
// let model = request.toModel()
|
|
||||||
// try await model.save(on: database)
|
|
||||||
// return try model.toDTO()
|
|
||||||
// },
|
|
||||||
// delete: { id in
|
|
||||||
// guard let model = try await RectangularDuctModel.find(id, on: database) else {
|
|
||||||
// throw NotFoundError()
|
|
||||||
// }
|
|
||||||
// try await model.delete(on: database)
|
|
||||||
// },
|
|
||||||
// fetch: { roomID in
|
|
||||||
// try await RectangularDuctModel.query(on: database)
|
|
||||||
// .with(\.$room)
|
|
||||||
// .filter(\.$room.$id == roomID)
|
|
||||||
// .all()
|
|
||||||
// .map { try $0.toDTO() }
|
|
||||||
// },
|
|
||||||
// get: { id in
|
|
||||||
// try await RectangularDuctModel.find(id, on: database)
|
|
||||||
// .map { try $0.toDTO() }
|
|
||||||
// },
|
|
||||||
// update: { id, updates in
|
|
||||||
// guard let model = try await RectangularDuctModel.find(id, on: database) else {
|
|
||||||
// throw NotFoundError()
|
|
||||||
// }
|
|
||||||
// try updates.validate()
|
|
||||||
// model.applyUpdates(updates)
|
|
||||||
// if model.hasChanges {
|
|
||||||
// try await model.save(on: database)
|
|
||||||
// }
|
|
||||||
// return try model.toDTO()
|
|
||||||
// }
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// extension DuctSizing.RectangularDuct.Create {
|
|
||||||
//
|
|
||||||
// func validate() throws(ValidationError) {
|
|
||||||
// guard height > 0 else {
|
|
||||||
// throw ValidationError("Rectangular duct size height should be greater than 0.")
|
|
||||||
// }
|
|
||||||
// if let register {
|
|
||||||
// guard register > 0 else {
|
|
||||||
// throw ValidationError("Rectangular duct size register should be greater than 0.")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func toModel() -> RectangularDuctModel {
|
|
||||||
// .init(roomID: roomID, height: height)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// extension DuctSizing.RectangularDuct.Update {
|
|
||||||
//
|
|
||||||
// func validate() throws(ValidationError) {
|
|
||||||
// if let height {
|
|
||||||
// guard height > 0 else {
|
|
||||||
// throw ValidationError("Rectangular duct size height should be greater than 0.")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if let register {
|
|
||||||
// guard register > 0 else {
|
|
||||||
// throw ValidationError("Rectangular duct size register should be greater than 0.")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// extension DuctSizing.RectangularDuct {
|
|
||||||
// struct Migrate: AsyncMigration {
|
|
||||||
// let name = "CreateRectangularDuct"
|
|
||||||
//
|
|
||||||
// func prepare(on database: any Database) async throws {
|
|
||||||
// try await database.schema(RectangularDuctModel.schema)
|
|
||||||
// .id()
|
|
||||||
// .field("register", .int8)
|
|
||||||
// .field("height", .int8, .required)
|
|
||||||
// .field("roomID", .uuid, .required, .references(RoomModel.schema, "id", onDelete: .cascade))
|
|
||||||
// .field("createdAt", .datetime)
|
|
||||||
// .field("updatedAt", .datetime)
|
|
||||||
// .create()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func revert(on database: any Database) async throws {
|
|
||||||
// try await database.schema(RectangularDuctModel.schema).delete()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// final class RectangularDuctModel: Model, @unchecked Sendable {
|
|
||||||
//
|
|
||||||
// static let schema = "rectangularDuct"
|
|
||||||
//
|
|
||||||
// @ID(key: .id)
|
|
||||||
// var id: UUID?
|
|
||||||
//
|
|
||||||
// @Parent(key: "roomID")
|
|
||||||
// var room: RoomModel
|
|
||||||
//
|
|
||||||
// @Field(key: "height")
|
|
||||||
// var height: Int
|
|
||||||
//
|
|
||||||
// @Field(key: "register")
|
|
||||||
// var register: Int?
|
|
||||||
//
|
|
||||||
// @Timestamp(key: "createdAt", on: .create, format: .iso8601)
|
|
||||||
// var createdAt: Date?
|
|
||||||
//
|
|
||||||
// @Timestamp(key: "updatedAt", on: .update, format: .iso8601)
|
|
||||||
// var updatedAt: Date?
|
|
||||||
//
|
|
||||||
// init() {}
|
|
||||||
//
|
|
||||||
// init(
|
|
||||||
// id: UUID? = nil,
|
|
||||||
// roomID: Room.ID,
|
|
||||||
// register: Int? = nil,
|
|
||||||
// height: Int
|
|
||||||
// ) {
|
|
||||||
// self.id = id
|
|
||||||
// $room.id = roomID
|
|
||||||
// self.register = register
|
|
||||||
// self.height = height
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func toDTO() throws -> DuctSizing.RectangularDuct {
|
|
||||||
// return try .init(
|
|
||||||
// id: requireID(),
|
|
||||||
// roomID: $room.id,
|
|
||||||
// register: register,
|
|
||||||
// height: height,
|
|
||||||
// createdAt: createdAt!,
|
|
||||||
// updatedAt: updatedAt!
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// func applyUpdates(_ updates: DuctSizing.RectangularDuct.Update) {
|
|
||||||
// if let height = updates.height, height != self.height {
|
|
||||||
// self.height = height
|
|
||||||
// }
|
|
||||||
// if let register = updates.register, register != self.register {
|
|
||||||
// self.register = register
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
@@ -46,10 +46,6 @@ extension TrunkSize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension ComponentPressureLosses {
|
|
||||||
var totalLosses: Double { values.reduce(0) { $0 + $1 } }
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Array where Element == EffectiveLengthGroup {
|
extension Array where Element == EffectiveLengthGroup {
|
||||||
var totalEffectiveLength: Int {
|
var totalEffectiveLength: Int {
|
||||||
reduce(0) { $0 + $1.effectiveLength }
|
reduce(0) { $0 + $1.effectiveLength }
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import Dependencies
|
import Dependencies
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
/// Represents component pressure losses used in the friction rate worksheet.
|
||||||
|
///
|
||||||
|
/// These are items such as filter, evaporator-coils, balance-dampers, etc. that
|
||||||
|
/// need to be overcome by the system fan.
|
||||||
public struct ComponentPressureLoss: Codable, Equatable, Identifiable, Sendable {
|
public struct ComponentPressureLoss: Codable, Equatable, Identifiable, Sendable {
|
||||||
|
|
||||||
public let id: UUID
|
public let id: UUID
|
||||||
@@ -44,7 +48,7 @@ extension ComponentPressureLoss {
|
|||||||
self.value = value
|
self.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return's commonly used default component pressure losses.
|
/// Commonly used default component pressure losses.
|
||||||
public static func `default`(projectID: Project.ID) -> [Self] {
|
public static func `default`(projectID: Project.ID) -> [Self] {
|
||||||
[
|
[
|
||||||
.init(projectID: projectID, name: "supply-outlet", value: 0.03),
|
.init(projectID: projectID, name: "supply-outlet", value: 0.03),
|
||||||
@@ -75,20 +79,7 @@ extension Array where Element == ComponentPressureLoss {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public typealias ComponentPressureLosses = [String: Double]
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
extension ComponentPressureLosses {
|
|
||||||
public static var mock: Self {
|
|
||||||
[
|
|
||||||
"evaporator-coil": 0.2,
|
|
||||||
"filter": 0.1,
|
|
||||||
"supply-outlet": 0.03,
|
|
||||||
"return-grille": 0.03,
|
|
||||||
"balancing-damper": 0.03,
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Array where Element == ComponentPressureLoss {
|
extension Array where Element == ComponentPressureLoss {
|
||||||
public static func mock(projectID: Project.ID) -> Self {
|
public static func mock(projectID: Project.ID) -> Self {
|
||||||
@@ -1,215 +0,0 @@
|
|||||||
import Dependencies
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
// TODO: Not sure how to model effective length groups in the database.
|
|
||||||
// thinking perhaps just have a 'data' field that encoded / decodes
|
|
||||||
// to swift types??
|
|
||||||
public struct EffectiveLength: Codable, Equatable, Identifiable, Sendable {
|
|
||||||
|
|
||||||
public let id: UUID
|
|
||||||
public let projectID: Project.ID
|
|
||||||
public let name: String
|
|
||||||
public let type: EffectiveLengthType
|
|
||||||
public let straightLengths: [Int]
|
|
||||||
public let groups: [Group]
|
|
||||||
public let createdAt: Date
|
|
||||||
public let updatedAt: Date
|
|
||||||
|
|
||||||
public init(
|
|
||||||
id: UUID,
|
|
||||||
projectID: Project.ID,
|
|
||||||
name: String,
|
|
||||||
type: EffectiveLength.EffectiveLengthType,
|
|
||||||
straightLengths: [Int],
|
|
||||||
groups: [EffectiveLength.Group],
|
|
||||||
createdAt: Date,
|
|
||||||
updatedAt: Date
|
|
||||||
) {
|
|
||||||
self.id = id
|
|
||||||
self.projectID = projectID
|
|
||||||
self.name = name
|
|
||||||
self.type = type
|
|
||||||
self.straightLengths = straightLengths
|
|
||||||
self.groups = groups
|
|
||||||
self.createdAt = createdAt
|
|
||||||
self.updatedAt = updatedAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension EffectiveLength {
|
|
||||||
|
|
||||||
public struct Create: Codable, Equatable, Sendable {
|
|
||||||
|
|
||||||
public let projectID: Project.ID
|
|
||||||
public let name: String
|
|
||||||
public let type: EffectiveLengthType
|
|
||||||
public let straightLengths: [Int]
|
|
||||||
public let groups: [Group]
|
|
||||||
|
|
||||||
public init(
|
|
||||||
projectID: Project.ID,
|
|
||||||
name: String,
|
|
||||||
type: EffectiveLength.EffectiveLengthType,
|
|
||||||
straightLengths: [Int],
|
|
||||||
groups: [EffectiveLength.Group]
|
|
||||||
) {
|
|
||||||
self.projectID = projectID
|
|
||||||
self.name = name
|
|
||||||
self.type = type
|
|
||||||
self.straightLengths = straightLengths
|
|
||||||
self.groups = groups
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct Update: Codable, Equatable, Sendable {
|
|
||||||
|
|
||||||
public let name: String?
|
|
||||||
public let type: EffectiveLengthType?
|
|
||||||
public let straightLengths: [Int]?
|
|
||||||
public let groups: [Group]?
|
|
||||||
|
|
||||||
public init(
|
|
||||||
name: String? = nil,
|
|
||||||
type: EffectiveLength.EffectiveLengthType? = nil,
|
|
||||||
straightLengths: [Int]? = nil,
|
|
||||||
groups: [EffectiveLength.Group]? = nil
|
|
||||||
) {
|
|
||||||
self.name = name
|
|
||||||
self.type = type
|
|
||||||
self.straightLengths = straightLengths
|
|
||||||
self.groups = groups
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum EffectiveLengthType: String, CaseIterable, Codable, Sendable {
|
|
||||||
case `return`
|
|
||||||
case supply
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct Group: Codable, Equatable, Sendable {
|
|
||||||
|
|
||||||
public let group: Int
|
|
||||||
public let letter: String
|
|
||||||
public let value: Double
|
|
||||||
public let quantity: Int
|
|
||||||
|
|
||||||
public init(
|
|
||||||
group: Int,
|
|
||||||
letter: String,
|
|
||||||
value: Double,
|
|
||||||
quantity: Int = 1
|
|
||||||
) {
|
|
||||||
self.group = group
|
|
||||||
self.letter = letter
|
|
||||||
self.value = value
|
|
||||||
self.quantity = quantity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct MaxContainer: Codable, Equatable, Sendable {
|
|
||||||
public let supply: EffectiveLength?
|
|
||||||
public let `return`: EffectiveLength?
|
|
||||||
|
|
||||||
public var total: Double? {
|
|
||||||
guard let supply else { return nil }
|
|
||||||
guard let `return` else { return nil }
|
|
||||||
return supply.totalEquivalentLength + `return`.totalEquivalentLength
|
|
||||||
}
|
|
||||||
|
|
||||||
public init(supply: EffectiveLength? = nil, return: EffectiveLength? = nil) {
|
|
||||||
self.supply = supply
|
|
||||||
self.return = `return`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension EffectiveLength {
|
|
||||||
public var totalEquivalentLength: Double {
|
|
||||||
straightLengths.reduce(into: 0.0) { $0 += Double($1) }
|
|
||||||
+ groups.totalEquivalentLength
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension Array where Element == EffectiveLength.Group {
|
|
||||||
|
|
||||||
public var totalEquivalentLength: Double {
|
|
||||||
reduce(into: 0.0) {
|
|
||||||
$0 += ($1.value * Double($1.quantity))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
|
|
||||||
extension EffectiveLength {
|
|
||||||
|
|
||||||
public static func mock(projectID: Project.ID) -> [Self] {
|
|
||||||
@Dependency(\.uuid) var uuid
|
|
||||||
@Dependency(\.date.now) var now
|
|
||||||
|
|
||||||
return [
|
|
||||||
.init(
|
|
||||||
id: uuid(),
|
|
||||||
projectID: projectID,
|
|
||||||
name: "Supply - 1",
|
|
||||||
type: .supply,
|
|
||||||
straightLengths: [10, 25],
|
|
||||||
groups: [
|
|
||||||
.init(group: 1, letter: "a", value: 20),
|
|
||||||
.init(group: 2, letter: "b", value: 30, quantity: 1),
|
|
||||||
.init(group: 3, letter: "a", value: 10, quantity: 1),
|
|
||||||
.init(group: 12, letter: "a", value: 10, quantity: 1),
|
|
||||||
],
|
|
||||||
createdAt: now,
|
|
||||||
updatedAt: now
|
|
||||||
),
|
|
||||||
.init(
|
|
||||||
id: uuid(),
|
|
||||||
projectID: projectID,
|
|
||||||
name: "Return - 1",
|
|
||||||
type: .return,
|
|
||||||
straightLengths: [10, 20, 5],
|
|
||||||
groups: [
|
|
||||||
.init(group: 5, letter: "a", value: 10),
|
|
||||||
.init(group: 6, letter: "a", value: 15, quantity: 1),
|
|
||||||
.init(group: 7, letter: "a", value: 20, quantity: 1),
|
|
||||||
],
|
|
||||||
createdAt: now,
|
|
||||||
updatedAt: now
|
|
||||||
),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
public static let mocks: [Self] = [
|
|
||||||
.init(
|
|
||||||
id: UUID(0),
|
|
||||||
projectID: UUID(0),
|
|
||||||
name: "Test Supply - 1",
|
|
||||||
type: .supply,
|
|
||||||
straightLengths: [10, 20, 25],
|
|
||||||
groups: [
|
|
||||||
.init(group: 1, letter: "a", value: 20),
|
|
||||||
.init(group: 2, letter: "b", value: 15, quantity: 2),
|
|
||||||
.init(group: 3, letter: "c", value: 10, quantity: 1),
|
|
||||||
],
|
|
||||||
createdAt: Date(),
|
|
||||||
updatedAt: Date()
|
|
||||||
),
|
|
||||||
.init(
|
|
||||||
id: UUID(1),
|
|
||||||
projectID: UUID(0),
|
|
||||||
name: "Test Return - 1",
|
|
||||||
type: .return,
|
|
||||||
straightLengths: [10, 20, 25],
|
|
||||||
groups: [
|
|
||||||
.init(group: 1, letter: "a", value: 20),
|
|
||||||
.init(group: 2, letter: "b", value: 15, quantity: 2),
|
|
||||||
.init(group: 3, letter: "c", value: 10, quantity: 1),
|
|
||||||
],
|
|
||||||
createdAt: Date(),
|
|
||||||
updatedAt: Date()
|
|
||||||
),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
// TODO: These are not used, should they be removed??
|
||||||
|
|
||||||
// TODO: Add other description / label for items that have same group & letter, but
|
// TODO: Add other description / label for items that have same group & letter, but
|
||||||
// different effective length.
|
// different effective length.
|
||||||
public struct EffectiveLengthGroup: Codable, Equatable, Sendable {
|
public struct EffectiveLengthGroup: Codable, Equatable, Sendable {
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import Dependencies
|
import Dependencies
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
/// Represents the equipment information for a project.
|
||||||
|
///
|
||||||
|
/// This is used in the friction rate worksheet and sizing ducts. It holds on to items
|
||||||
|
/// such as the target static pressure, cooling CFM, and heating CFM for the project.
|
||||||
public struct EquipmentInfo: Codable, Equatable, Identifiable, Sendable {
|
public struct EquipmentInfo: Codable, Equatable, Identifiable, Sendable {
|
||||||
public let id: UUID
|
public let id: UUID
|
||||||
public let projectID: Project.ID
|
public let projectID: Project.ID
|
||||||
|
|||||||
239
Sources/ManualDCore/EquivalentLength.swift
Normal file
239
Sources/ManualDCore/EquivalentLength.swift
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
import Dependencies
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Represents the equivalent length of a single duct path.
|
||||||
|
///
|
||||||
|
/// These consist of both straight lengths of duct / trunks, as well as the
|
||||||
|
/// equivalent length of duct fittings. They are used to determine the worst
|
||||||
|
/// case total equivalent length of duct that the system fan has to move air
|
||||||
|
/// through.
|
||||||
|
///
|
||||||
|
/// There can be many equivalent lengths saved for a project, however the only
|
||||||
|
/// ones that matter in most calculations are the longest supply path and the
|
||||||
|
/// the longest return path.
|
||||||
|
///
|
||||||
|
/// It is required that project has at least one equivalent length saved for
|
||||||
|
/// the supply and one saved for return, otherwise duct sizes can not be calculated.
|
||||||
|
public struct EquivalentLength: Codable, Equatable, Identifiable, Sendable {
|
||||||
|
|
||||||
|
/// The id of the equivalent length.
|
||||||
|
public let id: UUID
|
||||||
|
/// The project that this equivalent length is associated with.
|
||||||
|
public let projectID: Project.ID
|
||||||
|
/// A unique name / label for this equivalent length.
|
||||||
|
public let name: String
|
||||||
|
/// The type (supply or return) of the equivalent length.
|
||||||
|
public let type: EffectiveLengthType
|
||||||
|
/// The straight lengths of duct for this equivalent length.
|
||||||
|
public let straightLengths: [Int]
|
||||||
|
/// The fitting groups associated with this equivalent length.
|
||||||
|
public let groups: [FittingGroup]
|
||||||
|
/// When this equivalent length was created in the database.
|
||||||
|
public let createdAt: Date
|
||||||
|
/// When this equivalent length was updated in the database.
|
||||||
|
public let updatedAt: Date
|
||||||
|
|
||||||
|
public init(
|
||||||
|
id: UUID,
|
||||||
|
projectID: Project.ID,
|
||||||
|
name: String,
|
||||||
|
type: EquivalentLength.EffectiveLengthType,
|
||||||
|
straightLengths: [Int],
|
||||||
|
groups: [EquivalentLength.FittingGroup],
|
||||||
|
createdAt: Date,
|
||||||
|
updatedAt: Date
|
||||||
|
) {
|
||||||
|
self.id = id
|
||||||
|
self.projectID = projectID
|
||||||
|
self.name = name
|
||||||
|
self.type = type
|
||||||
|
self.straightLengths = straightLengths
|
||||||
|
self.groups = groups
|
||||||
|
self.createdAt = createdAt
|
||||||
|
self.updatedAt = updatedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension EquivalentLength {
|
||||||
|
|
||||||
|
/// Represents the data needed to create a new ``EquivalentLength`` in the database.
|
||||||
|
public struct Create: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
/// The project that this equivalent length is associated with.
|
||||||
|
public let projectID: Project.ID
|
||||||
|
/// A unique name / label for this equivalent length.
|
||||||
|
public let name: String
|
||||||
|
/// The type (supply or return) of the equivalent length.
|
||||||
|
public let type: EffectiveLengthType
|
||||||
|
/// The straight lengths of duct for this equivalent length.
|
||||||
|
public let straightLengths: [Int]
|
||||||
|
/// The fitting groups associated with this equivalent length.
|
||||||
|
public let groups: [FittingGroup]
|
||||||
|
|
||||||
|
public init(
|
||||||
|
projectID: Project.ID,
|
||||||
|
name: String,
|
||||||
|
type: EquivalentLength.EffectiveLengthType,
|
||||||
|
straightLengths: [Int],
|
||||||
|
groups: [EquivalentLength.FittingGroup]
|
||||||
|
) {
|
||||||
|
self.projectID = projectID
|
||||||
|
self.name = name
|
||||||
|
self.type = type
|
||||||
|
self.straightLengths = straightLengths
|
||||||
|
self.groups = groups
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the data needed to update an ``EquivalentLength`` in the database.
|
||||||
|
///
|
||||||
|
/// Only the supplied fields are updated.
|
||||||
|
public struct Update: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
/// A unique name / label for this equivalent length.
|
||||||
|
public let name: String?
|
||||||
|
/// The type (supply or return) of the equivalent length.
|
||||||
|
public let type: EffectiveLengthType?
|
||||||
|
/// The straight lengths of duct for this equivalent length.
|
||||||
|
public let straightLengths: [Int]?
|
||||||
|
/// The fitting groups associated with this equivalent length.
|
||||||
|
public let groups: [FittingGroup]?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
name: String? = nil,
|
||||||
|
type: EquivalentLength.EffectiveLengthType? = nil,
|
||||||
|
straightLengths: [Int]? = nil,
|
||||||
|
groups: [EquivalentLength.FittingGroup]? = nil
|
||||||
|
) {
|
||||||
|
self.name = name
|
||||||
|
self.type = type
|
||||||
|
self.straightLengths = straightLengths
|
||||||
|
self.groups = groups
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the type of equivalent length, either supply or return.
|
||||||
|
public enum EffectiveLengthType: String, CaseIterable, Codable, Sendable {
|
||||||
|
case `return`
|
||||||
|
case supply
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a Manual-D fitting group.
|
||||||
|
///
|
||||||
|
/// These are defined by Manual-D and convert different types of fittings into
|
||||||
|
/// an equivalent length of straight duct.
|
||||||
|
public struct FittingGroup: Codable, Equatable, Sendable {
|
||||||
|
/// The fitting group number.
|
||||||
|
public let group: Int
|
||||||
|
/// The fitting group letter.
|
||||||
|
public let letter: String
|
||||||
|
/// The equivalent length of the fitting.
|
||||||
|
public let value: Double
|
||||||
|
/// The quantity of the fittings in the path.
|
||||||
|
public let quantity: Int
|
||||||
|
|
||||||
|
public init(
|
||||||
|
group: Int,
|
||||||
|
letter: String,
|
||||||
|
value: Double,
|
||||||
|
quantity: Int = 1
|
||||||
|
) {
|
||||||
|
self.group = group
|
||||||
|
self.letter = letter
|
||||||
|
self.value = value
|
||||||
|
self.quantity = quantity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Should these not be optional and we just throw an error or return nil from
|
||||||
|
// a database query.
|
||||||
|
|
||||||
|
/// Represents the max ``EquivalentLength``'s for a project.
|
||||||
|
///
|
||||||
|
/// Calculating the duct sizes for a project requires there to be a max supply
|
||||||
|
/// and a max return equivalent length, so this container represents those values
|
||||||
|
/// when they exist in the database.
|
||||||
|
public struct MaxContainer: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
/// The longest supply equivalent length.
|
||||||
|
public let supply: EquivalentLength?
|
||||||
|
/// The longest return equivalent length.
|
||||||
|
public let `return`: EquivalentLength?
|
||||||
|
|
||||||
|
public var totalEquivalentLength: Double? {
|
||||||
|
guard let supply else { return nil }
|
||||||
|
guard let `return` else { return nil }
|
||||||
|
return supply.totalEquivalentLength + `return`.totalEquivalentLength
|
||||||
|
}
|
||||||
|
|
||||||
|
public init(supply: EquivalentLength? = nil, return: EquivalentLength? = nil) {
|
||||||
|
self.supply = supply
|
||||||
|
self.return = `return`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension EquivalentLength {
|
||||||
|
|
||||||
|
/// The calculated total equivalent length.
|
||||||
|
///
|
||||||
|
/// This is the sum of all the straigth lengths and fitting groups (with quantities).
|
||||||
|
public var totalEquivalentLength: Double {
|
||||||
|
straightLengths.reduce(into: 0.0) { $0 += Double($1) }
|
||||||
|
+ groups.totalEquivalentLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Array where Element == EquivalentLength.FittingGroup {
|
||||||
|
|
||||||
|
/// The calculated total equivalent length for the fitting groups.
|
||||||
|
public var totalEquivalentLength: Double {
|
||||||
|
reduce(into: 0.0) {
|
||||||
|
$0 += ($1.value * Double($1.quantity))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
|
||||||
|
extension EquivalentLength {
|
||||||
|
|
||||||
|
public static func mock(projectID: Project.ID) -> [Self] {
|
||||||
|
@Dependency(\.uuid) var uuid
|
||||||
|
@Dependency(\.date.now) var now
|
||||||
|
|
||||||
|
return [
|
||||||
|
.init(
|
||||||
|
id: uuid(),
|
||||||
|
projectID: projectID,
|
||||||
|
name: "Supply - 1",
|
||||||
|
type: .supply,
|
||||||
|
straightLengths: [10, 25],
|
||||||
|
groups: [
|
||||||
|
.init(group: 1, letter: "a", value: 20),
|
||||||
|
.init(group: 2, letter: "b", value: 30, quantity: 1),
|
||||||
|
.init(group: 3, letter: "a", value: 10, quantity: 1),
|
||||||
|
.init(group: 12, letter: "a", value: 10, quantity: 1),
|
||||||
|
],
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now
|
||||||
|
),
|
||||||
|
.init(
|
||||||
|
id: uuid(),
|
||||||
|
projectID: projectID,
|
||||||
|
name: "Return - 1",
|
||||||
|
type: .return,
|
||||||
|
straightLengths: [10, 20, 5],
|
||||||
|
groups: [
|
||||||
|
.init(group: 5, letter: "a", value: 10),
|
||||||
|
.init(group: 6, letter: "a", value: 15, quantity: 1),
|
||||||
|
.init(group: 7, letter: "a", value: 20, quantity: 1),
|
||||||
|
],
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now
|
||||||
|
),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
/// Holds onto values returned when calculating the design
|
/// Holds onto values returned when calculating the design
|
||||||
/// friction rate for a project.
|
/// friction rate for a project.
|
||||||
|
///
|
||||||
|
/// **NOTE:** This is not stored in the database, it is calculated on the fly.
|
||||||
public struct FrictionRate: Codable, Equatable, Sendable {
|
public struct FrictionRate: Codable, Equatable, Sendable {
|
||||||
|
/// The available static pressure is the equipment's design static pressure
|
||||||
|
/// minus the ``ComponentPressureLoss``es for the project.
|
||||||
public let availableStaticPressure: Double
|
public let availableStaticPressure: Double
|
||||||
|
/// The calculated design friction rate value.
|
||||||
public let value: Double
|
public let value: Double
|
||||||
|
/// Whether the design friction rate is within a valid range.
|
||||||
public var hasErrors: Bool { error != nil }
|
public var hasErrors: Bool { error != nil }
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@@ -13,6 +19,7 @@ public struct FrictionRate: Codable, Equatable, Sendable {
|
|||||||
self.value = value
|
self.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The error if the design friction rate is out of a valid range.
|
||||||
public var error: FrictionRateError? {
|
public var error: FrictionRateError? {
|
||||||
if value >= 0.18 {
|
if value >= 0.18 {
|
||||||
return .init(
|
return .init(
|
||||||
@@ -37,8 +44,13 @@ public struct FrictionRate: Codable, Equatable, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents an error when the ``FrictionRate`` is out of a valid range.
|
||||||
|
///
|
||||||
|
/// This holds onto the reason for the error as well as possible resolutions.
|
||||||
public struct FrictionRateError: Error, Equatable, Sendable {
|
public struct FrictionRateError: Error, Equatable, Sendable {
|
||||||
|
/// The reason for the error.
|
||||||
public let reason: String
|
public let reason: String
|
||||||
|
/// The possible resolutions to the error.
|
||||||
public let resolutions: [String]
|
public let resolutions: [String]
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
|
|||||||
@@ -1,16 +1,29 @@
|
|||||||
import Dependencies
|
import Dependencies
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
/// Represents a single duct design project / system.
|
||||||
|
///
|
||||||
|
/// Holds items such as project name and address.
|
||||||
public struct Project: Codable, Equatable, Identifiable, Sendable {
|
public struct Project: Codable, Equatable, Identifiable, Sendable {
|
||||||
|
/// The unique ID of the project.
|
||||||
public let id: UUID
|
public let id: UUID
|
||||||
|
/// The name of the project.
|
||||||
public let name: String
|
public let name: String
|
||||||
|
/// The street address of the project.
|
||||||
public let streetAddress: String
|
public let streetAddress: String
|
||||||
|
/// The city of the project.
|
||||||
public let city: String
|
public let city: String
|
||||||
|
/// The state of the project.
|
||||||
public let state: String
|
public let state: String
|
||||||
|
/// The zip code of the project.
|
||||||
public let zipCode: String
|
public let zipCode: String
|
||||||
|
/// The global sensible heat ratio for the project.
|
||||||
|
///
|
||||||
|
/// **NOTE:** This is used for calculating the sensible cooling load for rooms.
|
||||||
public let sensibleHeatRatio: Double?
|
public let sensibleHeatRatio: Double?
|
||||||
|
/// When the project was created in the database.
|
||||||
public let createdAt: Date
|
public let createdAt: Date
|
||||||
|
/// When the project was updated in the database.
|
||||||
public let updatedAt: Date
|
public let updatedAt: Date
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@@ -37,14 +50,20 @@ public struct Project: Codable, Equatable, Identifiable, Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension Project {
|
extension Project {
|
||||||
|
/// Represents the data needed to create a new project.
|
||||||
public struct Create: Codable, Equatable, Sendable {
|
public struct Create: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
/// The name of the project.
|
||||||
public let name: String
|
public let name: String
|
||||||
|
/// The street address of the project.
|
||||||
public let streetAddress: String
|
public let streetAddress: String
|
||||||
|
/// The city of the project.
|
||||||
public let city: String
|
public let city: String
|
||||||
|
/// The state of the project.
|
||||||
public let state: String
|
public let state: String
|
||||||
|
/// The zip code of the project.
|
||||||
public let zipCode: String
|
public let zipCode: String
|
||||||
|
/// The global sensible heat ratio for the project.
|
||||||
public let sensibleHeatRatio: Double?
|
public let sensibleHeatRatio: Double?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@@ -64,11 +83,19 @@ extension Project {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents steps that are completed in order to calculate the duct sizes
|
||||||
|
/// for a project.
|
||||||
|
///
|
||||||
|
/// This is primarily used on the web pages to display errors or color of the
|
||||||
|
/// different steps of a project.
|
||||||
public struct CompletedSteps: Codable, Equatable, Sendable {
|
public struct CompletedSteps: Codable, Equatable, Sendable {
|
||||||
|
/// Whether there is ``EquipmentInfo`` for a project.
|
||||||
public let equipmentInfo: Bool
|
public let equipmentInfo: Bool
|
||||||
|
/// Whether there are ``Room``'s for a project.
|
||||||
public let rooms: Bool
|
public let rooms: Bool
|
||||||
|
/// Whether there are ``EquivalentLength``'s for a project.
|
||||||
public let equivalentLength: Bool
|
public let equivalentLength: Bool
|
||||||
|
/// Whether there is a ``FrictionRate`` for a project.
|
||||||
public let frictionRate: Bool
|
public let frictionRate: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@@ -84,20 +111,30 @@ extension Project {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents project details loaded from the database.
|
||||||
|
///
|
||||||
|
/// This is generally used to perform duct sizing calculations for the
|
||||||
|
/// project, once all the steps have been completed.
|
||||||
public struct Detail: Codable, Equatable, Sendable {
|
public struct Detail: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
/// The project.
|
||||||
public let project: Project
|
public let project: Project
|
||||||
|
/// The component pressure losses for the project.
|
||||||
public let componentLosses: [ComponentPressureLoss]
|
public let componentLosses: [ComponentPressureLoss]
|
||||||
|
/// The equipment info for the project.
|
||||||
public let equipmentInfo: EquipmentInfo
|
public let equipmentInfo: EquipmentInfo
|
||||||
public let equivalentLengths: [EffectiveLength]
|
/// The equivalent lengths for the project.
|
||||||
|
public let equivalentLengths: [EquivalentLength]
|
||||||
|
/// The rooms in the project.
|
||||||
public let rooms: [Room]
|
public let rooms: [Room]
|
||||||
|
/// The trunk sizes in the project.
|
||||||
public let trunks: [TrunkSize]
|
public let trunks: [TrunkSize]
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
project: Project,
|
project: Project,
|
||||||
componentLosses: [ComponentPressureLoss],
|
componentLosses: [ComponentPressureLoss],
|
||||||
equipmentInfo: EquipmentInfo,
|
equipmentInfo: EquipmentInfo,
|
||||||
equivalentLengths: [EffectiveLength],
|
equivalentLengths: [EquivalentLength],
|
||||||
rooms: [Room],
|
rooms: [Room],
|
||||||
trunks: [TrunkSize]
|
trunks: [TrunkSize]
|
||||||
) {
|
) {
|
||||||
@@ -110,13 +147,22 @@ extension Project {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents fields that can be updated for a project that has already been created.
|
||||||
|
///
|
||||||
|
/// Only fields that are supplied get updated in the database.
|
||||||
public struct Update: Codable, Equatable, Sendable {
|
public struct Update: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
/// The name of the project.
|
||||||
public let name: String?
|
public let name: String?
|
||||||
|
/// The street address of the project.
|
||||||
public let streetAddress: String?
|
public let streetAddress: String?
|
||||||
|
/// The city of the project.
|
||||||
public let city: String?
|
public let city: String?
|
||||||
|
/// The state of the project.
|
||||||
public let state: String?
|
public let state: String?
|
||||||
|
/// The zip code of the project.
|
||||||
public let zipCode: String?
|
public let zipCode: String?
|
||||||
|
/// The global sensible heat ratio for the project.
|
||||||
public let sensibleHeatRatio: Double?
|
public let sensibleHeatRatio: Double?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
|
|||||||
@@ -1,16 +1,37 @@
|
|||||||
import Dependencies
|
import Dependencies
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
/// Represents a room in a project.
|
||||||
|
///
|
||||||
|
/// This contains data such as the heating and cooling load for the
|
||||||
|
/// room, the number of registers in the room, and any rectangular
|
||||||
|
/// duct size calculations stored for the room.
|
||||||
public struct Room: Codable, Equatable, Identifiable, Sendable {
|
public struct Room: Codable, Equatable, Identifiable, Sendable {
|
||||||
|
/// The unique id of the room.
|
||||||
public let id: UUID
|
public let id: UUID
|
||||||
|
/// The project this room is associated with.
|
||||||
public let projectID: Project.ID
|
public let projectID: Project.ID
|
||||||
|
/// A unique name for the room in the project.
|
||||||
public let name: String
|
public let name: String
|
||||||
|
/// The heating load required for the room (from Manual-J).
|
||||||
public let heatingLoad: Double
|
public let heatingLoad: Double
|
||||||
|
/// The total cooling load required for the room (from Manual-J).
|
||||||
public let coolingTotal: Double
|
public let coolingTotal: Double
|
||||||
|
/// An optional sensible cooling load for the room.
|
||||||
|
///
|
||||||
|
/// **NOTE:** This is generally not set, but calculated from the project wide
|
||||||
|
/// sensible heat ratio.
|
||||||
public let coolingSensible: Double?
|
public let coolingSensible: Double?
|
||||||
|
/// The number of registers for the room.
|
||||||
public let registerCount: Int
|
public let registerCount: Int
|
||||||
|
/// The rectangular duct size calculations for a room.
|
||||||
|
///
|
||||||
|
/// **NOTE:** These are optionally set after the round sizes have been calculate
|
||||||
|
/// for a room.
|
||||||
public let rectangularSizes: [RectangularSize]?
|
public let rectangularSizes: [RectangularSize]?
|
||||||
|
/// When the room was created in the database.
|
||||||
public let createdAt: Date
|
public let createdAt: Date
|
||||||
|
/// When the room was updated in the database.
|
||||||
public let updatedAt: Date
|
public let updatedAt: Date
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@@ -39,13 +60,19 @@ public struct Room: Codable, Equatable, Identifiable, Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension Room {
|
extension Room {
|
||||||
|
/// Represents the data required to create a new room for a project.
|
||||||
public struct Create: Codable, Equatable, Sendable {
|
public struct Create: Codable, Equatable, Sendable {
|
||||||
|
/// The project this room is associated with.
|
||||||
public let projectID: Project.ID
|
public let projectID: Project.ID
|
||||||
|
/// A unique name for the room in the project.
|
||||||
public let name: String
|
public let name: String
|
||||||
|
/// The heating load required for the room (from Manual-J).
|
||||||
public let heatingLoad: Double
|
public let heatingLoad: Double
|
||||||
|
/// The total cooling load required for the room (from Manual-J).
|
||||||
public let coolingTotal: Double
|
public let coolingTotal: Double
|
||||||
|
/// An optional sensible cooling load for the room.
|
||||||
public let coolingSensible: Double?
|
public let coolingSensible: Double?
|
||||||
|
/// The number of registers for the room.
|
||||||
public let registerCount: Int
|
public let registerCount: Int
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@@ -65,10 +92,17 @@ extension Room {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a rectangular size calculation that is stored in the
|
||||||
|
/// database for a given room.
|
||||||
|
///
|
||||||
|
/// These are done after the round duct sizes have been calculated and
|
||||||
|
/// can be used to calculate the equivalent rectangular size for a given run.
|
||||||
public struct RectangularSize: Codable, Equatable, Identifiable, Sendable {
|
public struct RectangularSize: Codable, Equatable, Identifiable, Sendable {
|
||||||
|
/// The unique id of the rectangular size.
|
||||||
public let id: UUID
|
public let id: UUID
|
||||||
|
/// The register the rectangular size is associated with.
|
||||||
public let register: Int?
|
public let register: Int?
|
||||||
|
/// The height of the rectangular size, the width gets calculated.
|
||||||
public let height: Int
|
public let height: Int
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@@ -82,12 +116,21 @@ extension Room {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents field that can be updated on a room after it's been created in the database.
|
||||||
|
///
|
||||||
|
/// Only fields that are supplied get updated.
|
||||||
public struct Update: Codable, Equatable, Sendable {
|
public struct Update: Codable, Equatable, Sendable {
|
||||||
|
/// A unique name for the room in the project.
|
||||||
public let name: String?
|
public let name: String?
|
||||||
|
/// The heating load required for the room (from Manual-J).
|
||||||
public let heatingLoad: Double?
|
public let heatingLoad: Double?
|
||||||
|
/// The total cooling load required for the room (from Manual-J).
|
||||||
public let coolingTotal: Double?
|
public let coolingTotal: Double?
|
||||||
|
/// An optional sensible cooling load for the room.
|
||||||
public let coolingSensible: Double?
|
public let coolingSensible: Double?
|
||||||
|
/// The number of registers for the room.
|
||||||
public let registerCount: Int?
|
public let registerCount: Int?
|
||||||
|
/// The rectangular duct size calculations for a room.
|
||||||
public let rectangularSizes: [RectangularSize]?
|
public let rectangularSizes: [RectangularSize]?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@@ -120,14 +163,20 @@ extension Room {
|
|||||||
|
|
||||||
extension Array where Element == Room {
|
extension Array where Element == Room {
|
||||||
|
|
||||||
|
/// The sum of heating loads for an array of rooms.
|
||||||
public var totalHeatingLoad: Double {
|
public var totalHeatingLoad: Double {
|
||||||
reduce(into: 0) { $0 += $1.heatingLoad }
|
reduce(into: 0) { $0 += $1.heatingLoad }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The sum of total cooling loads for an array of rooms.
|
||||||
public var totalCoolingLoad: Double {
|
public var totalCoolingLoad: Double {
|
||||||
reduce(into: 0) { $0 += $1.coolingTotal }
|
reduce(into: 0) { $0 += $1.coolingTotal }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The sum of sensible cooling loads for an array of rooms.
|
||||||
|
///
|
||||||
|
/// - Parameters:
|
||||||
|
/// - shr: The project wide sensible heat ratio.
|
||||||
public func totalCoolingSensible(shr: Double) -> Double {
|
public func totalCoolingSensible(shr: Double) -> Double {
|
||||||
reduce(into: 0) {
|
reduce(into: 0) {
|
||||||
let sensible = $1.coolingSensible ?? ($1.coolingTotal * shr)
|
let sensible = $1.coolingSensible ?? ($1.coolingTotal * shr)
|
||||||
@@ -139,38 +188,6 @@ extension Array where Element == Room {
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
|
||||||
extension Room {
|
extension Room {
|
||||||
public static let mocks = [
|
|
||||||
Room(
|
|
||||||
id: UUID(0),
|
|
||||||
projectID: UUID(0),
|
|
||||||
name: "Kitchen",
|
|
||||||
heatingLoad: 12345,
|
|
||||||
coolingTotal: 1234,
|
|
||||||
registerCount: 2,
|
|
||||||
createdAt: Date(),
|
|
||||||
updatedAt: Date()
|
|
||||||
),
|
|
||||||
Room(
|
|
||||||
id: UUID(1),
|
|
||||||
projectID: UUID(1),
|
|
||||||
name: "Bedroom - 1",
|
|
||||||
heatingLoad: 12345,
|
|
||||||
coolingTotal: 1456,
|
|
||||||
registerCount: 1,
|
|
||||||
createdAt: Date(),
|
|
||||||
updatedAt: Date()
|
|
||||||
),
|
|
||||||
Room(
|
|
||||||
id: UUID(2),
|
|
||||||
projectID: UUID(2),
|
|
||||||
name: "Family Room",
|
|
||||||
heatingLoad: 12345,
|
|
||||||
coolingTotal: 1673,
|
|
||||||
registerCount: 3,
|
|
||||||
createdAt: Date(),
|
|
||||||
updatedAt: Date()
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
public static func mock(projectID: Project.ID) -> [Self] {
|
public static func mock(projectID: Project.ID) -> [Self] {
|
||||||
@Dependency(\.uuid) var uuid
|
@Dependency(\.uuid) var uuid
|
||||||
|
|||||||
@@ -226,10 +226,10 @@ extension SiteRoute.Api {
|
|||||||
|
|
||||||
extension SiteRoute.Api {
|
extension SiteRoute.Api {
|
||||||
public enum EffectiveLengthRoute: Equatable, Sendable {
|
public enum EffectiveLengthRoute: Equatable, Sendable {
|
||||||
case create(EffectiveLength.Create)
|
case create(EquivalentLength.Create)
|
||||||
case delete(id: EffectiveLength.ID)
|
case delete(id: EquivalentLength.ID)
|
||||||
case fetch(projectID: Project.ID)
|
case fetch(projectID: Project.ID)
|
||||||
case get(id: EffectiveLength.ID)
|
case get(id: EquivalentLength.ID)
|
||||||
|
|
||||||
static let rootPath = "effectiveLength"
|
static let rootPath = "effectiveLength"
|
||||||
|
|
||||||
@@ -240,12 +240,12 @@ extension SiteRoute.Api {
|
|||||||
"create"
|
"create"
|
||||||
}
|
}
|
||||||
Method.post
|
Method.post
|
||||||
Body(.json(EffectiveLength.Create.self))
|
Body(.json(EquivalentLength.Create.self))
|
||||||
}
|
}
|
||||||
Route(.case(Self.delete(id:))) {
|
Route(.case(Self.delete(id:))) {
|
||||||
Path {
|
Path {
|
||||||
rootPath
|
rootPath
|
||||||
EffectiveLength.ID.parser()
|
EquivalentLength.ID.parser()
|
||||||
}
|
}
|
||||||
Method.delete
|
Method.delete
|
||||||
}
|
}
|
||||||
@@ -261,7 +261,7 @@ extension SiteRoute.Api {
|
|||||||
Route(.case(Self.get(id:))) {
|
Route(.case(Self.get(id:))) {
|
||||||
Path {
|
Path {
|
||||||
rootPath
|
rootPath
|
||||||
EffectiveLength.ID.parser()
|
EquivalentLength.ID.parser()
|
||||||
}
|
}
|
||||||
Method.get
|
Method.get
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -394,11 +394,11 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public enum EquivalentLengthRoute: Equatable, Sendable {
|
public enum EquivalentLengthRoute: Equatable, Sendable {
|
||||||
case delete(id: EffectiveLength.ID)
|
case delete(id: EquivalentLength.ID)
|
||||||
case field(FieldType, style: EffectiveLength.EffectiveLengthType? = nil)
|
case field(FieldType, style: EquivalentLength.EffectiveLengthType? = nil)
|
||||||
case index
|
case index
|
||||||
case submit(FormStep)
|
case submit(FormStep)
|
||||||
case update(EffectiveLength.ID, StepThree)
|
case update(EquivalentLength.ID, StepThree)
|
||||||
|
|
||||||
static let rootPath = "effective-lengths"
|
static let rootPath = "effective-lengths"
|
||||||
|
|
||||||
@@ -406,7 +406,7 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
Route(.case(Self.delete(id:))) {
|
Route(.case(Self.delete(id:))) {
|
||||||
Path {
|
Path {
|
||||||
rootPath
|
rootPath
|
||||||
EffectiveLength.ID.parser()
|
EquivalentLength.ID.parser()
|
||||||
}
|
}
|
||||||
Method.delete
|
Method.delete
|
||||||
}
|
}
|
||||||
@@ -424,7 +424,7 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
Field("type") { FieldType.parser() }
|
Field("type") { FieldType.parser() }
|
||||||
Optionally {
|
Optionally {
|
||||||
Field("style", default: nil) {
|
Field("style", default: nil) {
|
||||||
EffectiveLength.EffectiveLengthType.parser()
|
EquivalentLength.EffectiveLengthType.parser()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -437,16 +437,16 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
Route(.case(Self.update)) {
|
Route(.case(Self.update)) {
|
||||||
Path {
|
Path {
|
||||||
rootPath
|
rootPath
|
||||||
EffectiveLength.ID.parser()
|
EquivalentLength.ID.parser()
|
||||||
}
|
}
|
||||||
Method.patch
|
Method.patch
|
||||||
Body {
|
Body {
|
||||||
FormData {
|
FormData {
|
||||||
Optionally {
|
Optionally {
|
||||||
Field("id", default: nil) { EffectiveLength.ID.parser() }
|
Field("id", default: nil) { EquivalentLength.ID.parser() }
|
||||||
}
|
}
|
||||||
Field("name", .string)
|
Field("name", .string)
|
||||||
Field("type") { EffectiveLength.EffectiveLengthType.parser() }
|
Field("type") { EquivalentLength.EffectiveLengthType.parser() }
|
||||||
Many {
|
Many {
|
||||||
Field("straightLengths") {
|
Field("straightLengths") {
|
||||||
Int.parser()
|
Int.parser()
|
||||||
@@ -490,10 +490,10 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
Body {
|
Body {
|
||||||
FormData {
|
FormData {
|
||||||
Optionally {
|
Optionally {
|
||||||
Field("id", default: nil) { EffectiveLength.ID.parser() }
|
Field("id", default: nil) { EquivalentLength.ID.parser() }
|
||||||
}
|
}
|
||||||
Field("name", .string)
|
Field("name", .string)
|
||||||
Field("type") { EffectiveLength.EffectiveLengthType.parser() }
|
Field("type") { EquivalentLength.EffectiveLengthType.parser() }
|
||||||
}
|
}
|
||||||
.map(.memberwise(StepOne.init))
|
.map(.memberwise(StepOne.init))
|
||||||
}
|
}
|
||||||
@@ -505,10 +505,10 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
Body {
|
Body {
|
||||||
FormData {
|
FormData {
|
||||||
Optionally {
|
Optionally {
|
||||||
Field("id", default: nil) { EffectiveLength.ID.parser() }
|
Field("id", default: nil) { EquivalentLength.ID.parser() }
|
||||||
}
|
}
|
||||||
Field("name", .string)
|
Field("name", .string)
|
||||||
Field("type") { EffectiveLength.EffectiveLengthType.parser() }
|
Field("type") { EquivalentLength.EffectiveLengthType.parser() }
|
||||||
Many {
|
Many {
|
||||||
Field("straightLengths") {
|
Field("straightLengths") {
|
||||||
Int.parser()
|
Int.parser()
|
||||||
@@ -525,10 +525,10 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
Body {
|
Body {
|
||||||
FormData {
|
FormData {
|
||||||
Optionally {
|
Optionally {
|
||||||
Field("id", default: nil) { EffectiveLength.ID.parser() }
|
Field("id", default: nil) { EquivalentLength.ID.parser() }
|
||||||
}
|
}
|
||||||
Field("name", .string)
|
Field("name", .string)
|
||||||
Field("type") { EffectiveLength.EffectiveLengthType.parser() }
|
Field("type") { EquivalentLength.EffectiveLengthType.parser() }
|
||||||
Many {
|
Many {
|
||||||
Field("straightLengths") {
|
Field("straightLengths") {
|
||||||
Int.parser()
|
Int.parser()
|
||||||
@@ -567,22 +567,22 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public struct StepOne: Codable, Equatable, Sendable {
|
public struct StepOne: Codable, Equatable, Sendable {
|
||||||
public let id: EffectiveLength.ID?
|
public let id: EquivalentLength.ID?
|
||||||
public let name: String
|
public let name: String
|
||||||
public let type: EffectiveLength.EffectiveLengthType
|
public let type: EquivalentLength.EffectiveLengthType
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct StepTwo: Codable, Equatable, Sendable {
|
public struct StepTwo: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
public let id: EffectiveLength.ID?
|
public let id: EquivalentLength.ID?
|
||||||
public let name: String
|
public let name: String
|
||||||
public let type: EffectiveLength.EffectiveLengthType
|
public let type: EquivalentLength.EffectiveLengthType
|
||||||
public let straightLengths: [Int]
|
public let straightLengths: [Int]
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
id: EffectiveLength.ID? = nil,
|
id: EquivalentLength.ID? = nil,
|
||||||
name: String,
|
name: String,
|
||||||
type: EffectiveLength.EffectiveLengthType,
|
type: EquivalentLength.EffectiveLengthType,
|
||||||
straightLengths: [Int]
|
straightLengths: [Int]
|
||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
@@ -593,9 +593,9 @@ extension SiteRoute.View.ProjectRoute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public struct StepThree: Codable, Equatable, Sendable {
|
public struct StepThree: Codable, Equatable, Sendable {
|
||||||
public let id: EffectiveLength.ID?
|
public let id: EquivalentLength.ID?
|
||||||
public let name: String
|
public let name: String
|
||||||
public let type: EffectiveLength.EffectiveLengthType
|
public let type: EquivalentLength.EffectiveLengthType
|
||||||
public let straightLengths: [Int]
|
public let straightLengths: [Int]
|
||||||
public let groupGroups: [Int]
|
public let groupGroups: [Int]
|
||||||
public let groupLetters: [String]
|
public let groupLetters: [String]
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
/// Represents supported color themes for the website.
|
||||||
public enum Theme: String, CaseIterable, Codable, Equatable, Sendable {
|
public enum Theme: String, CaseIterable, Codable, Equatable, Sendable {
|
||||||
case aqua
|
case aqua
|
||||||
case cupcake
|
case cupcake
|
||||||
@@ -13,6 +14,7 @@ public enum Theme: String, CaseIterable, Codable, Equatable, Sendable {
|
|||||||
case retro
|
case retro
|
||||||
case synthwave
|
case synthwave
|
||||||
|
|
||||||
|
/// Represents dark color themes.
|
||||||
public static let darkThemes = [
|
public static let darkThemes = [
|
||||||
Self.aqua,
|
Self.aqua,
|
||||||
Self.cyberpunk,
|
Self.cyberpunk,
|
||||||
@@ -22,6 +24,7 @@ public enum Theme: String, CaseIterable, Codable, Equatable, Sendable {
|
|||||||
Self.synthwave,
|
Self.synthwave,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/// Represents light color themes.
|
||||||
public static let lightThemes = [
|
public static let lightThemes = [
|
||||||
Self.cupcake,
|
Self.cupcake,
|
||||||
Self.light,
|
Self.light,
|
||||||
|
|||||||
@@ -1,14 +1,23 @@
|
|||||||
import Dependencies
|
import Dependencies
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
// Represents the database model.
|
/// Represents trunk calculations for a project.
|
||||||
|
///
|
||||||
|
/// These are used to size trunk ducts / runs for multiple rooms or registers.
|
||||||
public struct TrunkSize: Codable, Equatable, Identifiable, Sendable {
|
public struct TrunkSize: Codable, Equatable, Identifiable, Sendable {
|
||||||
|
|
||||||
|
/// The unique identifier of the trunk size.
|
||||||
public let id: UUID
|
public let id: UUID
|
||||||
|
/// The project the trunk size is for.
|
||||||
public let projectID: Project.ID
|
public let projectID: Project.ID
|
||||||
|
/// The type of the trunk size (supply or return).
|
||||||
public let type: TrunkType
|
public let type: TrunkType
|
||||||
|
/// The rooms / registers associated with the trunk size.
|
||||||
public let rooms: [RoomProxy]
|
public let rooms: [RoomProxy]
|
||||||
|
/// An optional rectangular height used to calculate the equivalent
|
||||||
|
/// rectangular size of the trunk.
|
||||||
public let height: Int?
|
public let height: Int?
|
||||||
|
/// An optional name / label used for identifying the trunk.
|
||||||
public let name: String?
|
public let name: String?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@@ -29,12 +38,19 @@ public struct TrunkSize: Codable, Equatable, Identifiable, Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension TrunkSize {
|
extension TrunkSize {
|
||||||
|
/// Represents the data needed to create a new ``TrunkSize`` in the database.
|
||||||
public struct Create: Codable, Equatable, Sendable {
|
public struct Create: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
/// The project the trunk size is for.
|
||||||
public let projectID: Project.ID
|
public let projectID: Project.ID
|
||||||
|
/// The type of the trunk size (supply or return).
|
||||||
public let type: TrunkType
|
public let type: TrunkType
|
||||||
|
/// The rooms / registers associated with the trunk size.
|
||||||
public let rooms: [Room.ID: [Int]]
|
public let rooms: [Room.ID: [Int]]
|
||||||
|
/// An optional rectangular height used to calculate the equivalent
|
||||||
|
/// rectangular size of the trunk.
|
||||||
public let height: Int?
|
public let height: Int?
|
||||||
|
/// An optional name / label used for identifying the trunk.
|
||||||
public let name: String?
|
public let name: String?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@@ -52,11 +68,19 @@ extension TrunkSize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents the fields that can be updated on a ``TrunkSize`` in the database.
|
||||||
|
///
|
||||||
|
/// Only supplied fields are updated.
|
||||||
public struct Update: Codable, Equatable, Sendable {
|
public struct Update: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
/// The type of the trunk size (supply or return).
|
||||||
public let type: TrunkType?
|
public let type: TrunkType?
|
||||||
|
/// The rooms / registers associated with the trunk size.
|
||||||
public let rooms: [Room.ID: [Int]]?
|
public let rooms: [Room.ID: [Int]]?
|
||||||
|
/// An optional rectangular height used to calculate the equivalent
|
||||||
|
/// rectangular size of the trunk.
|
||||||
public let height: Int?
|
public let height: Int?
|
||||||
|
/// An optional name / label used for identifying the trunk.
|
||||||
public let name: String?
|
public let name: String?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@@ -72,18 +96,29 @@ extension TrunkSize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct RoomProxy: Codable, Equatable, Identifiable, Sendable {
|
/// A container / wrapper around a ``Room`` that is used with a ``TrunkSize``.
|
||||||
|
///
|
||||||
|
/// This is needed because a room can have multiple registers and it is possible
|
||||||
|
/// that a trunk does not serve all registers in that room.
|
||||||
|
@dynamicMemberLookup
|
||||||
|
public struct RoomProxy: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
public var id: Room.ID { room.id }
|
/// The room associated with the ``TrunkSize``.
|
||||||
public let room: Room
|
public let room: Room
|
||||||
|
/// The specific room registers associated with the ``TrunkSize``.
|
||||||
public let registers: [Int]
|
public let registers: [Int]
|
||||||
|
|
||||||
public init(room: Room, registers: [Int]) {
|
public init(room: Room, registers: [Int]) {
|
||||||
self.room = room
|
self.room = room
|
||||||
self.registers = registers
|
self.registers = registers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public subscript<T>(dynamicMember keyPath: KeyPath<Room, T>) -> T {
|
||||||
|
room[keyPath: keyPath]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents the type of a ``TrunkSize``, either supply or return.
|
||||||
public enum TrunkType: String, CaseIterable, Codable, Equatable, Sendable {
|
public enum TrunkType: String, CaseIterable, Codable, Equatable, Sendable {
|
||||||
case `return`
|
case `return`
|
||||||
case supply
|
case supply
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
import Dependencies
|
import Dependencies
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
// FIX: Remove username.
|
/// Represents a user of the site.
|
||||||
|
///
|
||||||
public struct User: Codable, Equatable, Identifiable, Sendable {
|
public struct User: Codable, Equatable, Identifiable, Sendable {
|
||||||
|
|
||||||
|
/// The unique id of the user.
|
||||||
public let id: UUID
|
public let id: UUID
|
||||||
|
/// The user's email address.
|
||||||
public let email: String
|
public let email: String
|
||||||
|
/// When the user was created in the database.
|
||||||
public let createdAt: Date
|
public let createdAt: Date
|
||||||
|
/// When the user was updated in the database.
|
||||||
public let updatedAt: Date
|
public let updatedAt: Date
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@@ -23,10 +28,14 @@ public struct User: Codable, Equatable, Identifiable, Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension User {
|
extension User {
|
||||||
|
/// Represents the data required to create a new user.
|
||||||
public struct Create: Codable, Equatable, Sendable {
|
public struct Create: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
|
/// The user's email address.
|
||||||
public let email: String
|
public let email: String
|
||||||
|
/// The password for the user.
|
||||||
public let password: String
|
public let password: String
|
||||||
|
/// The password confirmation, must match the password.
|
||||||
public let confirmPassword: String
|
public let confirmPassword: String
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@@ -40,9 +49,13 @@ extension User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents data required to login a user.
|
||||||
public struct Login: Codable, Equatable, Sendable {
|
public struct Login: Codable, Equatable, Sendable {
|
||||||
|
/// The user's email address.
|
||||||
public let email: String
|
public let email: String
|
||||||
|
/// The password for the user.
|
||||||
public let password: String
|
public let password: String
|
||||||
|
/// An optional page / route to navigate to after logging in the user.
|
||||||
public let next: String?
|
public let next: String?
|
||||||
|
|
||||||
public init(email: String, password: String, next: String? = nil) {
|
public init(email: String, password: String, next: String? = nil) {
|
||||||
@@ -52,10 +65,13 @@ extension User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents a user session token, for a logged in user.
|
||||||
public struct Token: Codable, Equatable, Identifiable, Sendable {
|
public struct Token: Codable, Equatable, Identifiable, Sendable {
|
||||||
|
/// The unique id of the token.
|
||||||
public let id: UUID
|
public let id: UUID
|
||||||
|
/// The user id the token is for.
|
||||||
public let userID: User.ID
|
public let userID: User.ID
|
||||||
|
/// The token value.
|
||||||
public let value: String
|
public let value: String
|
||||||
|
|
||||||
public init(id: UUID, userID: User.ID, value: String) {
|
public init(id: UUID, userID: User.ID, value: String) {
|
||||||
|
|||||||
@@ -2,19 +2,32 @@ import Dependencies
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension User {
|
extension User {
|
||||||
|
/// Represents a user's profile. Which contains extra information about a user of the site.
|
||||||
public struct Profile: Codable, Equatable, Identifiable, Sendable {
|
public struct Profile: Codable, Equatable, Identifiable, Sendable {
|
||||||
|
|
||||||
|
/// The unique id of the profile
|
||||||
public let id: UUID
|
public let id: UUID
|
||||||
|
/// The user id the profile is for.
|
||||||
public let userID: User.ID
|
public let userID: User.ID
|
||||||
|
/// The user's first name.
|
||||||
public let firstName: String
|
public let firstName: String
|
||||||
|
/// The user's last name.
|
||||||
public let lastName: String
|
public let lastName: String
|
||||||
|
/// The user's company name.
|
||||||
public let companyName: String
|
public let companyName: String
|
||||||
|
/// The user's street address.
|
||||||
public let streetAddress: String
|
public let streetAddress: String
|
||||||
|
/// The user's city.
|
||||||
public let city: String
|
public let city: String
|
||||||
|
/// The user's state.
|
||||||
public let state: String
|
public let state: String
|
||||||
|
/// The user's zip code.
|
||||||
public let zipCode: String
|
public let zipCode: String
|
||||||
|
/// An optional theme that the user prefers.
|
||||||
public let theme: Theme?
|
public let theme: Theme?
|
||||||
|
/// When the profile was created in the database.
|
||||||
public let createdAt: Date
|
public let createdAt: Date
|
||||||
|
/// When the profile was updated in the database.
|
||||||
public let updatedAt: Date
|
public let updatedAt: Date
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@@ -49,15 +62,25 @@ extension User {
|
|||||||
|
|
||||||
extension User.Profile {
|
extension User.Profile {
|
||||||
|
|
||||||
|
/// Represents the data required to create a user profile.
|
||||||
public struct Create: Codable, Equatable, Sendable {
|
public struct Create: Codable, Equatable, Sendable {
|
||||||
|
/// The user id the profile is for.
|
||||||
public let userID: User.ID
|
public let userID: User.ID
|
||||||
|
/// The user's first name.
|
||||||
public let firstName: String
|
public let firstName: String
|
||||||
|
/// The user's last name.
|
||||||
public let lastName: String
|
public let lastName: String
|
||||||
|
/// The user's company name.
|
||||||
public let companyName: String
|
public let companyName: String
|
||||||
|
/// The user's street address.
|
||||||
public let streetAddress: String
|
public let streetAddress: String
|
||||||
|
/// The user's city.
|
||||||
public let city: String
|
public let city: String
|
||||||
|
/// The user's state.
|
||||||
public let state: String
|
public let state: String
|
||||||
|
/// The user's zip code.
|
||||||
public let zipCode: String
|
public let zipCode: String
|
||||||
|
/// An optional theme that the user prefers.
|
||||||
public let theme: Theme?
|
public let theme: Theme?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
@@ -83,14 +106,25 @@ extension User.Profile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents the fields that can be updated on a user's profile.
|
||||||
|
///
|
||||||
|
/// Only fields that are supplied get updated.
|
||||||
public struct Update: Codable, Equatable, Sendable {
|
public struct Update: Codable, Equatable, Sendable {
|
||||||
|
/// The user's first name.
|
||||||
public let firstName: String?
|
public let firstName: String?
|
||||||
|
/// The user's last name.
|
||||||
public let lastName: String?
|
public let lastName: String?
|
||||||
|
/// The user's company name.
|
||||||
public let companyName: String?
|
public let companyName: String?
|
||||||
|
/// The user's street address.
|
||||||
public let streetAddress: String?
|
public let streetAddress: String?
|
||||||
|
/// The user's city.
|
||||||
public let city: String?
|
public let city: String?
|
||||||
|
/// The user's state.
|
||||||
public let state: String?
|
public let state: String?
|
||||||
|
/// The user's zip code.
|
||||||
public let zipCode: String?
|
public let zipCode: String?
|
||||||
|
/// An optional theme that the user prefers.
|
||||||
public let theme: Theme?
|
public let theme: Theme?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ extension PdfClient {
|
|||||||
public let componentLosses: [ComponentPressureLoss]
|
public let componentLosses: [ComponentPressureLoss]
|
||||||
public let ductSizes: DuctSizes
|
public let ductSizes: DuctSizes
|
||||||
public let equipmentInfo: EquipmentInfo
|
public let equipmentInfo: EquipmentInfo
|
||||||
public let maxSupplyTEL: EffectiveLength
|
public let maxSupplyTEL: EquivalentLength
|
||||||
public let maxReturnTEL: EffectiveLength
|
public let maxReturnTEL: EquivalentLength
|
||||||
public let frictionRate: FrictionRate
|
public let frictionRate: FrictionRate
|
||||||
public let projectSHR: Double
|
public let projectSHR: Double
|
||||||
|
|
||||||
@@ -99,8 +99,8 @@ extension PdfClient {
|
|||||||
componentLosses: [ComponentPressureLoss],
|
componentLosses: [ComponentPressureLoss],
|
||||||
ductSizes: DuctSizes,
|
ductSizes: DuctSizes,
|
||||||
equipmentInfo: EquipmentInfo,
|
equipmentInfo: EquipmentInfo,
|
||||||
maxSupplyTEL: EffectiveLength,
|
maxSupplyTEL: EquivalentLength,
|
||||||
maxReturnTEL: EffectiveLength,
|
maxReturnTEL: EquivalentLength,
|
||||||
frictionRate: FrictionRate,
|
frictionRate: FrictionRate,
|
||||||
projectSHR: Double
|
projectSHR: Double
|
||||||
) {
|
) {
|
||||||
@@ -134,7 +134,7 @@ extension PdfClient {
|
|||||||
let rooms = Room.mock(projectID: project.id)
|
let rooms = Room.mock(projectID: project.id)
|
||||||
let trunks = TrunkSize.mock(projectID: project.id, rooms: rooms)
|
let trunks = TrunkSize.mock(projectID: project.id, rooms: rooms)
|
||||||
let equipmentInfo = EquipmentInfo.mock(projectID: project.id)
|
let equipmentInfo = EquipmentInfo.mock(projectID: project.id)
|
||||||
let equivalentLengths = EffectiveLength.mock(projectID: project.id)
|
let equivalentLengths = EquivalentLength.mock(projectID: project.id)
|
||||||
|
|
||||||
return .init(
|
return .init(
|
||||||
project: project,
|
project: project,
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import Elementary
|
|||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
|
||||||
struct EffectiveLengthsTable: HTML, Sendable {
|
struct EffectiveLengthsTable: HTML, Sendable {
|
||||||
let effectiveLengths: [EffectiveLength]
|
let effectiveLengths: [EquivalentLength]
|
||||||
|
|
||||||
var body: some HTML<HTMLTag.table> {
|
var body: some HTML<HTMLTag.table> {
|
||||||
table {
|
table {
|
||||||
@@ -41,7 +41,7 @@ struct EffectiveLengthsTable: HTML, Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct EffectiveLengthGroupTable: HTML, Sendable {
|
struct EffectiveLengthGroupTable: HTML, Sendable {
|
||||||
let groups: [EffectiveLength.Group]
|
let groups: [EquivalentLength.FittingGroup]
|
||||||
|
|
||||||
var body: some HTML<HTMLTag.table> {
|
var body: some HTML<HTMLTag.table> {
|
||||||
table {
|
table {
|
||||||
|
|||||||
@@ -60,12 +60,12 @@ extension ProjectClient {
|
|||||||
public struct FrictionRateResponse: Codable, Equatable, Sendable {
|
public struct FrictionRateResponse: Codable, Equatable, Sendable {
|
||||||
|
|
||||||
public let componentLosses: [ComponentPressureLoss]
|
public let componentLosses: [ComponentPressureLoss]
|
||||||
public let equivalentLengths: EffectiveLength.MaxContainer
|
public let equivalentLengths: EquivalentLength.MaxContainer
|
||||||
public let frictionRate: FrictionRate?
|
public let frictionRate: FrictionRate?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
componentLosses: [ComponentPressureLoss],
|
componentLosses: [ComponentPressureLoss],
|
||||||
equivalentLengths: EffectiveLength.MaxContainer,
|
equivalentLengths: EquivalentLength.MaxContainer,
|
||||||
frictionRate: FrictionRate? = nil
|
frictionRate: FrictionRate? = nil
|
||||||
) {
|
) {
|
||||||
self.componentLosses = componentLosses
|
self.componentLosses = componentLosses
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import DatabaseClient
|
import DatabaseClient
|
||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
|
||||||
extension DatabaseClient.ComponentLoss {
|
extension DatabaseClient.ComponentLosses {
|
||||||
|
|
||||||
func createDefaults(projectID: Project.ID) async throws {
|
func createDefaults(projectID: Project.ID) async throws {
|
||||||
let defaults = ComponentPressureLoss.Create.default(projectID: projectID)
|
let defaults = ComponentPressureLoss.Create.default(projectID: projectID)
|
||||||
|
|||||||
@@ -144,11 +144,11 @@ extension DatabaseClient {
|
|||||||
// Internal container.
|
// Internal container.
|
||||||
struct DesignFrictionRateResponse: Equatable, Sendable {
|
struct DesignFrictionRateResponse: Equatable, Sendable {
|
||||||
|
|
||||||
typealias EnsuredTEL = (supply: EffectiveLength, return: EffectiveLength)
|
typealias EnsuredTEL = (supply: EquivalentLength, return: EquivalentLength)
|
||||||
|
|
||||||
let designFrictionRate: Double
|
let designFrictionRate: Double
|
||||||
let equipmentInfo: EquipmentInfo
|
let equipmentInfo: EquipmentInfo
|
||||||
let telMaxContainer: EffectiveLength.MaxContainer
|
let telMaxContainer: EquivalentLength.MaxContainer
|
||||||
|
|
||||||
func ensureMaxContainer() throws -> EnsuredTEL {
|
func ensureMaxContainer() throws -> EnsuredTEL {
|
||||||
|
|
||||||
@@ -167,9 +167,9 @@ extension DatabaseClient {
|
|||||||
func designFrictionRate(
|
func designFrictionRate(
|
||||||
componentLosses: [ComponentPressureLoss],
|
componentLosses: [ComponentPressureLoss],
|
||||||
equipmentInfo: EquipmentInfo,
|
equipmentInfo: EquipmentInfo,
|
||||||
equivalentLengths: EffectiveLength.MaxContainer
|
equivalentLengths: EquivalentLength.MaxContainer
|
||||||
) -> DesignFrictionRateResponse? {
|
) -> DesignFrictionRateResponse? {
|
||||||
guard let tel = equivalentLengths.total,
|
guard let tel = equivalentLengths.totalEquivalentLength,
|
||||||
componentLosses.count > 0
|
componentLosses.count > 0
|
||||||
else { return nil }
|
else { return nil }
|
||||||
|
|
||||||
@@ -192,9 +192,9 @@ extension DatabaseClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return try await designFrictionRate(
|
return try await designFrictionRate(
|
||||||
componentLosses: componentLoss.fetch(projectID),
|
componentLosses: componentLosses.fetch(projectID),
|
||||||
equipmentInfo: equipmentInfo,
|
equipmentInfo: equipmentInfo,
|
||||||
equivalentLengths: effectiveLength.fetchMax(projectID)
|
equivalentLengths: equivalentLengths.fetchMax(projectID)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import ManualDCore
|
|||||||
|
|
||||||
struct DuctSizeSharedRequest {
|
struct DuctSizeSharedRequest {
|
||||||
let equipmentInfo: EquipmentInfo
|
let equipmentInfo: EquipmentInfo
|
||||||
let maxSupplyLength: EffectiveLength
|
let maxSupplyLength: EquivalentLength
|
||||||
let maxReturnLenght: EffectiveLength
|
let maxReturnLenght: EquivalentLength
|
||||||
let designFrictionRate: Double
|
let designFrictionRate: Double
|
||||||
let projectSHR: Double
|
let projectSHR: Double
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ extension ManualDClient {
|
|||||||
func frictionRate(details: Project.Detail) async throws -> ProjectClient.FrictionRateResponse {
|
func frictionRate(details: Project.Detail) async throws -> ProjectClient.FrictionRateResponse {
|
||||||
|
|
||||||
let maxContainer = details.maxContainer
|
let maxContainer = details.maxContainer
|
||||||
guard let totalEquivalentLength = maxContainer.total else {
|
guard let totalEquivalentLength = maxContainer.totalEquivalentLength else {
|
||||||
return .init(componentLosses: details.componentLosses, equivalentLengths: maxContainer)
|
return .init(componentLosses: details.componentLosses, equivalentLengths: maxContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,15 +28,15 @@ extension ManualDClient {
|
|||||||
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.componentLosses.fetch(projectID)
|
||||||
let lengths = try await database.effectiveLength.fetchMax(projectID)
|
let lengths = try await database.equivalentLengths.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.totalEquivalentLength else {
|
||||||
return .init(componentLosses: componentLosses, equivalentLengths: lengths)
|
return .init(componentLosses: componentLosses, equivalentLengths: lengths)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ extension ManualDClient {
|
|||||||
frictionRate: frictionRate(
|
frictionRate: frictionRate(
|
||||||
.init(
|
.init(
|
||||||
externalStaticPressure: staticPressure,
|
externalStaticPressure: staticPressure,
|
||||||
componentPressureLosses: database.componentLoss.fetch(projectID),
|
componentPressureLosses: componentLosses,
|
||||||
totalEffectiveLength: Int(totalEquivalentLength)
|
totalEffectiveLength: Int(totalEquivalentLength)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import ManualDCore
|
import ManualDCore
|
||||||
|
|
||||||
extension Project.Detail {
|
extension Project.Detail {
|
||||||
var maxContainer: EffectiveLength.MaxContainer {
|
var maxContainer: EquivalentLength.MaxContainer {
|
||||||
.init(
|
.init(
|
||||||
supply: equivalentLengths.filter({ $0.type == .supply })
|
supply: equivalentLengths.filter({ $0.type == .supply })
|
||||||
.sorted(by: { $0.totalEquivalentLength > $1.totalEquivalentLength })
|
.sorted(by: { $0.totalEquivalentLength > $1.totalEquivalentLength })
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ extension ProjectClient: DependencyKey {
|
|||||||
},
|
},
|
||||||
createProject: { userID, request in
|
createProject: { userID, request in
|
||||||
let project = try await database.projects.create(userID, request)
|
let project = try await database.projects.create(userID, request)
|
||||||
try await database.componentLoss.createDefaults(projectID: project.id)
|
try await database.componentLosses.createDefaults(projectID: project.id)
|
||||||
return try await .init(
|
return try await .init(
|
||||||
projectID: project.id,
|
projectID: project.id,
|
||||||
rooms: database.rooms.fetch(project.id),
|
rooms: database.rooms.fetch(project.id),
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ extension SiteRoute.View.ProjectRoute.EquivalentLengthRoute.StepThree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var groups: [EffectiveLength.Group] {
|
var groups: [EquivalentLength.FittingGroup] {
|
||||||
var groups = [EffectiveLength.Group]()
|
var groups = [EquivalentLength.FittingGroup]()
|
||||||
for (n, group) in groupGroups.enumerated() {
|
for (n, group) in groupGroups.enumerated() {
|
||||||
groups.append(
|
groups.append(
|
||||||
.init(
|
.init(
|
||||||
@@ -29,7 +29,7 @@ extension SiteRoute.View.ProjectRoute.EquivalentLengthRoute.StepThree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension EffectiveLength.Create {
|
extension EquivalentLength.Create {
|
||||||
|
|
||||||
init(
|
init(
|
||||||
form: SiteRoute.View.ProjectRoute.EquivalentLengthRoute.StepThree,
|
form: SiteRoute.View.ProjectRoute.EquivalentLengthRoute.StepThree,
|
||||||
@@ -45,7 +45,7 @@ extension EffectiveLength.Create {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension EffectiveLength.Update {
|
extension EquivalentLength.Update {
|
||||||
init(
|
init(
|
||||||
form: SiteRoute.View.ProjectRoute.EquivalentLengthRoute.StepThree,
|
form: SiteRoute.View.ProjectRoute.EquivalentLengthRoute.StepThree,
|
||||||
projectID: Project.ID
|
projectID: Project.ID
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ extension ViewController.Request {
|
|||||||
case .submitProfile(let profile):
|
case .submitProfile(let profile):
|
||||||
return await view {
|
return await view {
|
||||||
await ResultView {
|
await ResultView {
|
||||||
_ = try await database.userProfile.create(profile)
|
_ = try await database.userProfiles.create(profile)
|
||||||
let userID = profile.userID
|
let userID = profile.userID
|
||||||
// let user = try currentUser()
|
// let user = try currentUser()
|
||||||
return (
|
return (
|
||||||
@@ -108,7 +108,7 @@ extension ViewController.Request {
|
|||||||
get async {
|
get async {
|
||||||
@Dependency(\.database) var database
|
@Dependency(\.database) var database
|
||||||
guard let user = try? currentUser() else { return nil }
|
guard let user = try? currentUser() else { return nil }
|
||||||
return try? await database.userProfile.fetch(user.id)?.theme
|
return try? await database.userProfiles.fetch(user.id)?.theme
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,8 +366,8 @@ extension SiteRoute.View.ProjectRoute.FrictionRateRoute {
|
|||||||
return await request.view {
|
return await request.view {
|
||||||
await ResultView {
|
await ResultView {
|
||||||
let equipment = try await database.equipment.fetch(projectID)
|
let equipment = try await database.equipment.fetch(projectID)
|
||||||
let componentLosses = try await database.componentLoss.fetch(projectID)
|
let componentLosses = try await database.componentLosses.fetch(projectID)
|
||||||
let lengths = try await database.effectiveLength.fetchMax(projectID)
|
let lengths = try await database.equivalentLengths.fetchMax(projectID)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
try await database.projects.getCompletedSteps(projectID),
|
try await database.projects.getCompletedSteps(projectID),
|
||||||
@@ -407,16 +407,16 @@ extension SiteRoute.View.ProjectRoute.ComponentLossRoute {
|
|||||||
return EmptyHTML()
|
return EmptyHTML()
|
||||||
case .delete(let id):
|
case .delete(let id):
|
||||||
return await view(on: request, projectID: projectID) {
|
return await view(on: request, projectID: projectID) {
|
||||||
_ = try await database.componentLoss.delete(id)
|
_ = try await database.componentLosses.delete(id)
|
||||||
}
|
}
|
||||||
case .submit(let form):
|
case .submit(let form):
|
||||||
return await view(on: request, projectID: projectID) {
|
return await view(on: request, projectID: projectID) {
|
||||||
_ = try await database.componentLoss.create(form)
|
_ = try await database.componentLosses.create(form)
|
||||||
}
|
}
|
||||||
|
|
||||||
case .update(let id, let form):
|
case .update(let id, let form):
|
||||||
return await view(on: request, projectID: projectID) {
|
return await view(on: request, projectID: projectID) {
|
||||||
_ = try await database.componentLoss.update(id, form)
|
_ = try await database.componentLosses.update(id, form)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -464,7 +464,7 @@ extension SiteRoute.View.ProjectRoute.EquivalentLengthRoute {
|
|||||||
|
|
||||||
case .delete(let id):
|
case .delete(let id):
|
||||||
return await ResultView {
|
return await ResultView {
|
||||||
try await database.effectiveLength.delete(id)
|
try await database.equivalentLengths.delete(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
case .index:
|
case .index:
|
||||||
@@ -481,16 +481,16 @@ extension SiteRoute.View.ProjectRoute.EquivalentLengthRoute {
|
|||||||
|
|
||||||
case .update(let id, let form):
|
case .update(let id, let form):
|
||||||
return await view(on: request, projectID: projectID) {
|
return await view(on: request, projectID: projectID) {
|
||||||
_ = try await database.effectiveLength.update(id, .init(form: form, projectID: projectID))
|
_ = try await database.equivalentLengths.update(id, .init(form: form, projectID: projectID))
|
||||||
}
|
}
|
||||||
|
|
||||||
case .submit(let step):
|
case .submit(let step):
|
||||||
switch step {
|
switch step {
|
||||||
case .one(let stepOne):
|
case .one(let stepOne):
|
||||||
return await ResultView {
|
return await ResultView {
|
||||||
var effectiveLength: EffectiveLength? = nil
|
var effectiveLength: EquivalentLength? = nil
|
||||||
if let id = stepOne.id {
|
if let id = stepOne.id {
|
||||||
effectiveLength = try await database.effectiveLength.get(id)
|
effectiveLength = try await database.equivalentLengths.get(id)
|
||||||
}
|
}
|
||||||
return effectiveLength
|
return effectiveLength
|
||||||
} onSuccess: { effectiveLength in
|
} onSuccess: { effectiveLength in
|
||||||
@@ -503,9 +503,9 @@ extension SiteRoute.View.ProjectRoute.EquivalentLengthRoute {
|
|||||||
case .two(let stepTwo):
|
case .two(let stepTwo):
|
||||||
return await ResultView {
|
return await ResultView {
|
||||||
request.logger.debug("ViewController: Got step two...")
|
request.logger.debug("ViewController: Got step two...")
|
||||||
var effectiveLength: EffectiveLength? = nil
|
var effectiveLength: EquivalentLength? = nil
|
||||||
if let id = stepTwo.id {
|
if let id = stepTwo.id {
|
||||||
effectiveLength = try await database.effectiveLength.get(id)
|
effectiveLength = try await database.equivalentLengths.get(id)
|
||||||
}
|
}
|
||||||
return effectiveLength
|
return effectiveLength
|
||||||
} onSuccess: { effectiveLength in
|
} onSuccess: { effectiveLength in
|
||||||
@@ -515,7 +515,7 @@ extension SiteRoute.View.ProjectRoute.EquivalentLengthRoute {
|
|||||||
}
|
}
|
||||||
case .three(let stepThree):
|
case .three(let stepThree):
|
||||||
return await view(on: request, projectID: projectID) {
|
return await view(on: request, projectID: projectID) {
|
||||||
_ = try await database.effectiveLength.create(
|
_ = try await database.equivalentLengths.create(
|
||||||
.init(form: stepThree, projectID: projectID)
|
.init(form: stepThree, projectID: projectID)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -536,7 +536,7 @@ extension SiteRoute.View.ProjectRoute.EquivalentLengthRoute {
|
|||||||
try await catching()
|
try await catching()
|
||||||
return (
|
return (
|
||||||
try await database.projects.getCompletedSteps(projectID),
|
try await database.projects.getCompletedSteps(projectID),
|
||||||
try await database.effectiveLength.fetch(projectID)
|
try await database.equivalentLengths.fetch(projectID)
|
||||||
)
|
)
|
||||||
} onSuccess: { (steps, equivalentLengths) in
|
} onSuccess: { (steps, equivalentLengths) in
|
||||||
ProjectView(projectID: projectID, activeTab: .equivalentLength, completedSteps: steps) {
|
ProjectView(projectID: projectID, activeTab: .equivalentLength, completedSteps: steps) {
|
||||||
@@ -652,11 +652,11 @@ extension SiteRoute.View.UserRoute.Profile {
|
|||||||
return await view(on: request)
|
return await view(on: request)
|
||||||
case .submit(let form):
|
case .submit(let form):
|
||||||
return await view(on: request) {
|
return await view(on: request) {
|
||||||
_ = try await database.userProfile.create(form)
|
_ = try await database.userProfiles.create(form)
|
||||||
}
|
}
|
||||||
case .update(let id, let updates):
|
case .update(let id, let updates):
|
||||||
return await view(on: request) {
|
return await view(on: request) {
|
||||||
_ = try await database.userProfile.update(id, updates)
|
_ = try await database.userProfiles.update(id, updates)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -673,7 +673,7 @@ extension SiteRoute.View.UserRoute.Profile {
|
|||||||
let user = try request.currentUser()
|
let user = try request.currentUser()
|
||||||
return (
|
return (
|
||||||
user,
|
user,
|
||||||
try await database.userProfile.fetch(user.id)
|
try await database.userProfiles.fetch(user.id)
|
||||||
)
|
)
|
||||||
} onSuccess: { (user, profile) in
|
} onSuccess: { (user, profile) in
|
||||||
UserView(user: user, profile: profile)
|
UserView(user: user, profile: profile)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Styleguide
|
|||||||
|
|
||||||
struct EffectiveLengthForm: HTML, Sendable {
|
struct EffectiveLengthForm: HTML, Sendable {
|
||||||
|
|
||||||
static func id(_ equivalentLength: EffectiveLength?) -> String {
|
static func id(_ equivalentLength: EquivalentLength?) -> String {
|
||||||
let base = "equivalentLengthForm"
|
let base = "equivalentLengthForm"
|
||||||
guard let equivalentLength else { return base }
|
guard let equivalentLength else { return base }
|
||||||
return "\(base)_\(equivalentLength.id.uuidString.replacing("-", with: ""))"
|
return "\(base)_\(equivalentLength.id.uuidString.replacing("-", with: ""))"
|
||||||
@@ -15,15 +15,15 @@ struct EffectiveLengthForm: HTML, Sendable {
|
|||||||
|
|
||||||
let projectID: Project.ID
|
let projectID: Project.ID
|
||||||
let dismiss: Bool
|
let dismiss: Bool
|
||||||
let type: EffectiveLength.EffectiveLengthType
|
let type: EquivalentLength.EffectiveLengthType
|
||||||
let effectiveLength: EffectiveLength?
|
let effectiveLength: EquivalentLength?
|
||||||
|
|
||||||
var id: String { Self.id(effectiveLength) }
|
var id: String { Self.id(effectiveLength) }
|
||||||
|
|
||||||
init(
|
init(
|
||||||
projectID: Project.ID,
|
projectID: Project.ID,
|
||||||
dismiss: Bool,
|
dismiss: Bool,
|
||||||
type: EffectiveLength.EffectiveLengthType = .supply
|
type: EquivalentLength.EffectiveLengthType = .supply
|
||||||
) {
|
) {
|
||||||
self.projectID = projectID
|
self.projectID = projectID
|
||||||
self.dismiss = dismiss
|
self.dismiss = dismiss
|
||||||
@@ -32,7 +32,7 @@ struct EffectiveLengthForm: HTML, Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(
|
init(
|
||||||
effectiveLength: EffectiveLength
|
effectiveLength: EquivalentLength
|
||||||
) {
|
) {
|
||||||
self.dismiss = true
|
self.dismiss = true
|
||||||
self.type = effectiveLength.type
|
self.type = effectiveLength.type
|
||||||
@@ -55,7 +55,7 @@ struct EffectiveLengthForm: HTML, Sendable {
|
|||||||
|
|
||||||
struct StepOne: HTML, Sendable {
|
struct StepOne: HTML, Sendable {
|
||||||
let projectID: Project.ID
|
let projectID: Project.ID
|
||||||
let effectiveLength: EffectiveLength?
|
let effectiveLength: EquivalentLength?
|
||||||
|
|
||||||
var route: String {
|
var route: String {
|
||||||
let baseRoute = SiteRoute.View.router.path(
|
let baseRoute = SiteRoute.View.router.path(
|
||||||
@@ -97,7 +97,7 @@ struct EffectiveLengthForm: HTML, Sendable {
|
|||||||
struct StepTwo: HTML, Sendable {
|
struct StepTwo: HTML, Sendable {
|
||||||
let projectID: Project.ID
|
let projectID: Project.ID
|
||||||
let stepOne: SiteRoute.View.ProjectRoute.EquivalentLengthRoute.StepOne
|
let stepOne: SiteRoute.View.ProjectRoute.EquivalentLengthRoute.StepOne
|
||||||
let effectiveLength: EffectiveLength?
|
let effectiveLength: EquivalentLength?
|
||||||
|
|
||||||
var route: String {
|
var route: String {
|
||||||
let baseRoute = SiteRoute.View.router.path(
|
let baseRoute = SiteRoute.View.router.path(
|
||||||
@@ -152,7 +152,7 @@ struct EffectiveLengthForm: HTML, Sendable {
|
|||||||
|
|
||||||
struct StepThree: HTML, Sendable {
|
struct StepThree: HTML, Sendable {
|
||||||
let projectID: Project.ID
|
let projectID: Project.ID
|
||||||
let effectiveLength: EffectiveLength?
|
let effectiveLength: EquivalentLength?
|
||||||
let stepTwo: SiteRoute.View.ProjectRoute.EquivalentLengthRoute.StepTwo
|
let stepTwo: SiteRoute.View.ProjectRoute.EquivalentLengthRoute.StepTwo
|
||||||
|
|
||||||
var route: String {
|
var route: String {
|
||||||
@@ -254,10 +254,10 @@ struct StraightLengthField: HTML, Sendable {
|
|||||||
|
|
||||||
struct GroupField: HTML, Sendable {
|
struct GroupField: HTML, Sendable {
|
||||||
|
|
||||||
let style: EffectiveLength.EffectiveLengthType
|
let style: EquivalentLength.EffectiveLengthType
|
||||||
let group: EffectiveLength.Group?
|
let group: EquivalentLength.FittingGroup?
|
||||||
|
|
||||||
init(style: EffectiveLength.EffectiveLengthType, group: EffectiveLength.Group? = nil) {
|
init(style: EquivalentLength.EffectiveLengthType, group: EquivalentLength.FittingGroup? = nil) {
|
||||||
self.style = style
|
self.style = style
|
||||||
self.group = group
|
self.group = group
|
||||||
}
|
}
|
||||||
@@ -307,7 +307,7 @@ struct GroupField: HTML, Sendable {
|
|||||||
|
|
||||||
struct GroupSelect: HTML, Sendable {
|
struct GroupSelect: HTML, Sendable {
|
||||||
|
|
||||||
let style: EffectiveLength.EffectiveLengthType
|
let style: EquivalentLength.EffectiveLengthType
|
||||||
|
|
||||||
var body: some HTML {
|
var body: some HTML {
|
||||||
label(.class("select")) {
|
label(.class("select")) {
|
||||||
@@ -328,13 +328,13 @@ struct GroupSelect: HTML, Sendable {
|
|||||||
struct GroupTypeSelect: HTML, Sendable {
|
struct GroupTypeSelect: HTML, Sendable {
|
||||||
|
|
||||||
let projectID: Project.ID
|
let projectID: Project.ID
|
||||||
let selected: EffectiveLength.EffectiveLengthType
|
let selected: EquivalentLength.EffectiveLengthType
|
||||||
|
|
||||||
var body: some HTML<HTMLTag.label> {
|
var body: some HTML<HTMLTag.label> {
|
||||||
label(.class("select w-full")) {
|
label(.class("select w-full")) {
|
||||||
span(.class("label")) { "Type" }
|
span(.class("label")) { "Type" }
|
||||||
select(.name("type"), .id("type")) {
|
select(.name("type"), .id("type")) {
|
||||||
for value in EffectiveLength.EffectiveLengthType.allCases {
|
for value in EquivalentLength.EffectiveLengthType.allCases {
|
||||||
option(
|
option(
|
||||||
.value("\(value.rawValue)"),
|
.value("\(value.rawValue)"),
|
||||||
) { value.title }
|
) { value.title }
|
||||||
@@ -345,7 +345,7 @@ struct GroupTypeSelect: HTML, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension EffectiveLength.EffectiveLengthType {
|
extension EquivalentLength.EffectiveLengthType {
|
||||||
|
|
||||||
var title: String { rawValue.capitalized }
|
var title: String { rawValue.capitalized }
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import Styleguide
|
|||||||
|
|
||||||
struct EffectiveLengthsTable: HTML, Sendable {
|
struct EffectiveLengthsTable: HTML, Sendable {
|
||||||
|
|
||||||
let effectiveLengths: [EffectiveLength]
|
let effectiveLengths: [EquivalentLength]
|
||||||
|
|
||||||
private var sortedLengths: [EffectiveLength] {
|
private var sortedLengths: [EquivalentLength] {
|
||||||
effectiveLengths.sorted {
|
effectiveLengths.sorted {
|
||||||
$0.totalEquivalentLength > $1.totalEquivalentLength
|
$0.totalEquivalentLength > $1.totalEquivalentLength
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ struct EffectiveLengthsTable: HTML, Sendable {
|
|||||||
|
|
||||||
struct EffectiveLenghtRow: HTML, Sendable {
|
struct EffectiveLenghtRow: HTML, Sendable {
|
||||||
|
|
||||||
let effectiveLength: EffectiveLength
|
let effectiveLength: EquivalentLength
|
||||||
|
|
||||||
private var deleteRoute: SiteRoute.View {
|
private var deleteRoute: SiteRoute.View {
|
||||||
.project(
|
.project(
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ struct EffectiveLengthsView: HTML, Sendable {
|
|||||||
|
|
||||||
@Environment(ProjectViewValue.$projectID) var projectID
|
@Environment(ProjectViewValue.$projectID) var projectID
|
||||||
|
|
||||||
let effectiveLengths: [EffectiveLength]
|
let effectiveLengths: [EquivalentLength]
|
||||||
|
|
||||||
var supplies: [EffectiveLength] {
|
var supplies: [EquivalentLength] {
|
||||||
effectiveLengths.filter({ $0.type == .supply })
|
effectiveLengths.filter({ $0.type == .supply })
|
||||||
.sorted { $0.totalEquivalentLength > $1.totalEquivalentLength }
|
.sorted { $0.totalEquivalentLength > $1.totalEquivalentLength }
|
||||||
}
|
}
|
||||||
|
|
||||||
var returns: [EffectiveLength] {
|
var returns: [EquivalentLength] {
|
||||||
effectiveLengths.filter({ $0.type == .return })
|
effectiveLengths.filter({ $0.type == .return })
|
||||||
.sorted { $0.totalEquivalentLength > $1.totalEquivalentLength }
|
.sorted { $0.totalEquivalentLength > $1.totalEquivalentLength }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ struct FrictionRateView: HTML, Sendable {
|
|||||||
@Environment(ProjectViewValue.$projectID) var projectID
|
@Environment(ProjectViewValue.$projectID) var projectID
|
||||||
|
|
||||||
let componentLosses: [ComponentPressureLoss]
|
let componentLosses: [ComponentPressureLoss]
|
||||||
let equivalentLengths: EffectiveLength.MaxContainer
|
let equivalentLengths: EquivalentLength.MaxContainer
|
||||||
let frictionRate: FrictionRate?
|
let frictionRate: FrictionRate?
|
||||||
|
|
||||||
private var availableStaticPressure: Double? {
|
private var availableStaticPressure: Double? {
|
||||||
|
|||||||
@@ -233,12 +233,12 @@ extension ManualDClient {
|
|||||||
func frictionRate(
|
func frictionRate(
|
||||||
equipmentInfo: EquipmentInfo?,
|
equipmentInfo: EquipmentInfo?,
|
||||||
componentLosses: [ComponentPressureLoss],
|
componentLosses: [ComponentPressureLoss],
|
||||||
effectiveLength: EffectiveLength.MaxContainer
|
effectiveLength: EquivalentLength.MaxContainer
|
||||||
) async throws -> FrictionRate? {
|
) async throws -> FrictionRate? {
|
||||||
guard let staticPressure = equipmentInfo?.staticPressure else {
|
guard let staticPressure = equipmentInfo?.staticPressure else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
guard let totalEquivalentLength = effectiveLength.total else {
|
guard let totalEquivalentLength = effectiveLength.totalEquivalentLength else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return try await self.frictionRate(
|
return try await self.frictionRate(
|
||||||
|
|||||||
2
TODO.md
2
TODO.md
@@ -1,7 +1,7 @@
|
|||||||
# TODO's
|
# TODO's
|
||||||
|
|
||||||
- [x] Fix theme not working when selected upon signup.
|
- [x] Fix theme not working when selected upon signup.
|
||||||
- [ ] Pdf generation
|
- [x] Pdf generation
|
||||||
- [ ] Add postgres / mysql support
|
- [ ] Add postgres / mysql support
|
||||||
- [ ] Opensource / license ??
|
- [ ] Opensource / license ??
|
||||||
- [ ] Figure out domain to host (currently thinking ductcalc.pro)
|
- [ ] Figure out domain to host (currently thinking ductcalc.pro)
|
||||||
|
|||||||
@@ -78,10 +78,10 @@ struct ProjectRouteTests {
|
|||||||
let p = Body {
|
let p = Body {
|
||||||
FormData {
|
FormData {
|
||||||
Optionally {
|
Optionally {
|
||||||
Field("id", default: nil) { EffectiveLength.ID.parser() }
|
Field("id", default: nil) { EquivalentLength.ID.parser() }
|
||||||
}
|
}
|
||||||
Field("name", .string)
|
Field("name", .string)
|
||||||
Field("type") { EffectiveLength.EffectiveLengthType.parser() }
|
Field("type") { EquivalentLength.EffectiveLengthType.parser() }
|
||||||
Many {
|
Many {
|
||||||
Field("straightLengths") {
|
Field("straightLengths") {
|
||||||
Int.parser()
|
Int.parser()
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ struct ViewControllerTests {
|
|||||||
let project = Project.mock
|
let project = Project.mock
|
||||||
let rooms = Room.mock(projectID: project.id)
|
let rooms = Room.mock(projectID: project.id)
|
||||||
let equipment = EquipmentInfo.mock(projectID: project.id)
|
let equipment = EquipmentInfo.mock(projectID: project.id)
|
||||||
let tels = EffectiveLength.mock(projectID: project.id)
|
let tels = EquivalentLength.mock(projectID: project.id)
|
||||||
let componentLosses = ComponentPressureLoss.mock(projectID: project.id)
|
let componentLosses = ComponentPressureLoss.mock(projectID: project.id)
|
||||||
let trunks = TrunkSize.mock(projectID: project.id, rooms: rooms)
|
let trunks = TrunkSize.mock(projectID: project.id, rooms: rooms)
|
||||||
|
|
||||||
@@ -108,11 +108,11 @@ struct ViewControllerTests {
|
|||||||
$0.database.projects.getSensibleHeatRatio = { _ in 0.83 }
|
$0.database.projects.getSensibleHeatRatio = { _ in 0.83 }
|
||||||
$0.database.rooms.fetch = { _ in rooms }
|
$0.database.rooms.fetch = { _ in rooms }
|
||||||
$0.database.equipment.fetch = { _ in equipment }
|
$0.database.equipment.fetch = { _ in equipment }
|
||||||
$0.database.effectiveLength.fetch = { _ in tels }
|
$0.database.equivalentLengths.fetch = { _ in tels }
|
||||||
$0.database.effectiveLength.fetchMax = { _ in
|
$0.database.equivalentLengths.fetchMax = { _ in
|
||||||
.init(supply: tels.first, return: tels.last)
|
.init(supply: tels.first, return: tels.last)
|
||||||
}
|
}
|
||||||
$0.database.componentLoss.fetch = { _ in componentLosses }
|
$0.database.componentLosses.fetch = { _ in componentLosses }
|
||||||
$0.projectClient.calculateDuctSizes = { _ in
|
$0.projectClient.calculateDuctSizes = { _ in
|
||||||
.mock(equipmentInfo: equipment, rooms: rooms, trunks: trunks)
|
.mock(equipmentInfo: equipment, rooms: rooms, trunks: trunks)
|
||||||
}
|
}
|
||||||
@@ -164,7 +164,7 @@ struct ViewControllerTests {
|
|||||||
return try await withDependencies {
|
return try await withDependencies {
|
||||||
$0.viewController = .liveValue
|
$0.viewController = .liveValue
|
||||||
$0.authClient.currentUser = { user }
|
$0.authClient.currentUser = { user }
|
||||||
$0.database.userProfile.fetch = { _ in profile }
|
$0.database.userProfiles.fetch = { _ in profile }
|
||||||
$0.manualD = .liveValue
|
$0.manualD = .liveValue
|
||||||
try await updateDependencies(&$0)
|
try await updateDependencies(&$0)
|
||||||
} operation: {
|
} operation: {
|
||||||
|
|||||||
36
docker/Dockerfile.test
Normal file
36
docker/Dockerfile.test
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
FROM docker.io/swift:6.2-noble
|
||||||
|
|
||||||
|
# Make sure all system packages are up to date, and install only essential packages.
|
||||||
|
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
|
||||||
|
&& apt-get -q update \
|
||||||
|
&& apt-get -q dist-upgrade -y \
|
||||||
|
&& apt-get -q install -y \
|
||||||
|
libjemalloc2 \
|
||||||
|
ca-certificates \
|
||||||
|
tzdata \
|
||||||
|
# If your app or its dependencies import FoundationNetworking, also install `libcurl4`.
|
||||||
|
libcurl4 \
|
||||||
|
# If your app or its dependencies import FoundationXML, also install `libxml2`.
|
||||||
|
# libxml2 \
|
||||||
|
sqlite3 \
|
||||||
|
&& rm -r /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Set up a build area
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# First just resolve dependencies.
|
||||||
|
# This creates a cached layer that can be reused
|
||||||
|
# as long as your Package.swift/Package.resolved
|
||||||
|
# files do not change.
|
||||||
|
COPY ./Package.* ./
|
||||||
|
RUN swift package resolve \
|
||||||
|
$([ -f ./Package.resolved ] && echo "--force-resolved-versions" || true)
|
||||||
|
|
||||||
|
# Copy entire repo into container
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ENV SWIFT_BACKTRACE=enable=no
|
||||||
|
ENV LOG_LEVEL=debug
|
||||||
|
|
||||||
|
CMD ["swift", "test"]
|
||||||
|
|
||||||
7
justfile
7
justfile
@@ -14,7 +14,10 @@ run:
|
|||||||
@swift run App serve --log debug
|
@swift run App serve --log debug
|
||||||
|
|
||||||
build-docker file="docker/Dockerfile":
|
build-docker file="docker/Dockerfile":
|
||||||
@podman build -f {{file}} -t {{docker_image}}:{{docker_tag}} .
|
@docker build -f {{file}} -t {{docker_image}}:{{docker_tag}} .
|
||||||
|
|
||||||
run-docker:
|
run-docker:
|
||||||
@podman run -it --rm -v $PWD:/app -p 8080:8080 {{docker_image}}:{{docker_tag}}
|
@docker run -it --rm -v $PWD:/app -p 8080:8080 {{docker_image}}:{{docker_tag}}
|
||||||
|
|
||||||
|
test-docker: (build-docker "docker/Dockerfile.test")
|
||||||
|
@docker run --rm {{docker_image}}:{{docker_tag}} swift test
|
||||||
|
|||||||
Reference in New Issue
Block a user