WIP: Begins trunk sizing, adds database and core models.

This commit is contained in:
2026-01-13 11:45:27 -05:00
parent df600a5471
commit 930db145a8
3 changed files with 285 additions and 2 deletions

View File

@@ -20,6 +20,7 @@ public struct DatabaseClient: Sendable {
public var effectiveLength: EffectiveLengthClient public var effectiveLength: EffectiveLengthClient
public var users: Users public var users: Users
public var userProfile: UserProfile public var userProfile: UserProfile
public var trunkSizes: TrunkSizes
} }
extension DatabaseClient: TestDependencyKey { extension DatabaseClient: TestDependencyKey {
@@ -31,7 +32,8 @@ extension DatabaseClient: TestDependencyKey {
componentLoss: .testValue, componentLoss: .testValue,
effectiveLength: .testValue, effectiveLength: .testValue,
users: .testValue, users: .testValue,
userProfile: .testValue userProfile: .testValue,
trunkSizes: .testValue
) )
public static func live(database: any Database) -> Self { public static func live(database: any Database) -> Self {
@@ -43,7 +45,8 @@ extension DatabaseClient: TestDependencyKey {
componentLoss: .live(database: database), componentLoss: .live(database: database),
effectiveLength: .live(database: database), effectiveLength: .live(database: database),
users: .live(database: database), users: .live(database: database),
userProfile: .live(database: database) userProfile: .live(database: database),
trunkSizes: .live(database: database)
) )
} }
} }
@@ -71,6 +74,7 @@ extension DatabaseClient.Migrations: DependencyKey {
EquipmentInfo.Migrate(), EquipmentInfo.Migrate(),
Room.Migrate(), Room.Migrate(),
EffectiveLength.Migrate(), EffectiveLength.Migrate(),
DuctSizing.TrunkSize.Migrate(),
] ]
} }
) )

View File

@@ -0,0 +1,212 @@
import Dependencies
import DependenciesMacros
import Fluent
import Foundation
import ManualDCore
extension DatabaseClient {
@DependencyClient
public struct TrunkSizes: Sendable {
public var create: @Sendable (DuctSizing.TrunkSize.Create) async throws -> DuctSizing.TrunkSize
public var delete: @Sendable (DuctSizing.TrunkSize.ID) async throws -> Void
public var fetch: @Sendable (Project.ID) async throws -> [DuctSizing.TrunkSize]
public var get: @Sendable (DuctSizing.TrunkSize.ID) async throws -> DuctSizing.TrunkSize?
}
}
extension DatabaseClient.TrunkSizes: TestDependencyKey {
public static let testValue = Self()
public static func live(database: any Database) -> Self {
.init(
create: { request in
try request.validate()
let trunk = request.toModel()
var roomProxies = [DuctSizing.TrunkSize.RoomProxy]()
try await trunk.save(on: database)
for (roomID, registers) in request.rooms {
guard let room = try await RoomModel.find(roomID, on: database) else {
throw NotFoundError()
}
let model = try TrunkRoomModel(
trunkID: trunk.requireID(),
roomID: room.requireID(),
registers: registers
)
try await model.save(on: database)
try roomProxies.append(model.toDTO())
}
return try .init(
id: trunk.requireID(),
projectID: trunk.$project.id,
type: .init(rawValue: trunk.type)!,
rooms: roomProxies
)
},
delete: { id in
guard let model = try await TrunkModel.find(id, on: database) else {
throw NotFoundError()
}
try await model.delete(on: database)
},
fetch: { projectID in
try await TrunkModel.query(on: database)
.with(\.$rooms)
.with(\.$project)
.filter(\.$project.$id == projectID)
.all()
.map { try $0.toDTO() }
},
get: { id in
try await TrunkModel.find(id, on: database)
.map { try $0.toDTO() }
}
)
}
}
extension DuctSizing.TrunkSize.Create {
func validate() throws(ValidationError) {
guard rooms.count > 0 else {
throw ValidationError("Trunk size should have associated rooms / registers.")
}
if let height {
guard height > 0 else {
throw ValidationError("Trunk size height should be greater than 0.")
}
}
}
func toModel() -> TrunkModel {
.init(
projectID: projectID,
type: type,
height: height
)
}
}
extension DuctSizing.TrunkSize {
struct Migrate: AsyncMigration {
let name = "CreateTrunkSize"
func prepare(on database: any Database) async throws {
try await database.schema(TrunkModel.schema)
.id()
.field("height", .int8)
.field("type", .string, .required)
.field(
"projectID", .uuid, .required, .references(ProjectModel.schema, "id", onDelete: .cascade)
)
.create()
try await database.schema(TrunkRoomModel.schema)
.id()
.field("registers", .array(of: .int), .required)
.field(
"trunkID", .uuid, .required, .references(TrunkModel.schema, "id", onDelete: .cascade)
)
.field(
"roomID", .uuid, .required, .references(RoomModel.schema, "id", onDelete: .cascade)
)
.create()
}
func revert(on database: any Database) async throws {
try await database.schema(TrunkRoomModel.schema).delete()
try await database.schema(TrunkModel.schema).delete()
}
}
}
// Pivot table for associating rooms and trunks.
final class TrunkRoomModel: Model, @unchecked Sendable {
static let schema = "room+trunk"
@ID(key: .id)
var id: UUID?
@Parent(key: "trunkID")
var trunk: TrunkModel
@Parent(key: "roomID")
var room: RoomModel
@Field(key: "registers")
var registers: [Int]
init() {}
init(
id: UUID? = nil,
trunkID: TrunkModel.IDValue,
roomID: RoomModel.IDValue,
registers: [Int]
) {
self.id = id
$trunk.id = trunkID
$room.id = roomID
self.registers = registers
}
func toDTO() throws -> DuctSizing.TrunkSize.RoomProxy {
.init(
room: try room.toDTO(),
registers: registers
)
}
}
final class TrunkModel: Model, @unchecked Sendable {
static let schema = "trunk"
@ID(key: .id)
var id: UUID?
@Parent(key: "projectID")
var project: ProjectModel
@Field(key: "height")
var height: Int?
@Field(key: "type")
var type: String
@Children(for: \.$trunk)
var rooms: [TrunkRoomModel]
init() {}
init(
id: UUID? = nil,
projectID: Project.ID,
type: DuctSizing.TrunkSize.TrunkType,
height: Int? = nil,
) {
self.id = id
$project.id = projectID
self.height = height
self.type = type.rawValue
}
func toDTO() throws -> DuctSizing.TrunkSize {
try .init(
id: requireID(),
projectID: $project.id,
type: .init(rawValue: type)!,
rooms: rooms.map { try $0.toDTO() },
height: height
)
}
}

View File

@@ -91,3 +91,70 @@ public enum DuctSizing {
} }
} }
} }
extension DuctSizing {
public struct TrunkSize: Codable, Equatable, Identifiable, Sendable {
public let id: UUID
public let projectID: Project.ID
public let type: TrunkType
public let rooms: [RoomProxy]
public let height: Int?
public init(
id: UUID,
projectID: Project.ID,
type: DuctSizing.TrunkSize.TrunkType,
rooms: [DuctSizing.TrunkSize.RoomProxy],
height: Int? = nil
) {
self.id = id
self.projectID = projectID
self.type = type
self.rooms = rooms
self.height = height
}
}
}
extension DuctSizing.TrunkSize {
public struct Create: Codable, Equatable, Sendable {
public let projectID: Project.ID
public let type: TrunkType
public let rooms: [Room.ID: [Int]]
public let height: Int?
public init(
projectID: Project.ID,
type: DuctSizing.TrunkSize.TrunkType,
rooms: [Room.ID: [Int]],
height: Int? = nil
) {
self.projectID = projectID
self.type = type
self.rooms = rooms
self.height = height
}
}
// TODO: Make registers non-optional
public struct RoomProxy: Codable, Equatable, Identifiable, Sendable {
public var id: Room.ID { room.id }
public let room: Room
public let registers: [Int]?
public init(room: Room, registers: [Int]? = nil) {
self.room = room
self.registers = registers
}
}
public enum TrunkType: String, Codable, Equatable, Sendable {
case `return`
case supply
}
}