WIP: Adds initial effective length to database client.

This commit is contained in:
2026-01-01 11:21:15 -05:00
parent 7c37392390
commit b116c3011b
4 changed files with 296 additions and 2 deletions

View File

@@ -0,0 +1,158 @@
import Dependencies
import DependenciesMacros
import Fluent
import Foundation
import ManualDCore
extension DatabaseClient {
@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 get: @Sendable (EffectiveLength.ID) async throws -> EffectiveLength?
}
}
extension DatabaseClient.EffectiveLengthClient: TestDependencyKey {
public static let testValue = Self()
public static func live(database: any Database) -> Self {
.init(
create: { request in
let model = try request.toModel()
try await model.save(on: database)
return try model.toDTO()
},
delete: { id in
guard let model = try await EffectiveLengthModel.find(id, on: database) else {
throw NotFoundError()
}
try await model.delete(on: database)
},
fetch: { projectID in
guard
let model = try await EffectiveLengthModel.query(on: database)
.filter("projectID", .equal, projectID)
.first()
else {
throw NotFoundError()
}
return try model.toDTO()
},
get: { id in
try await EffectiveLengthModel.find(id, on: database).map { try $0.toDTO() }
}
)
}
}
extension EffectiveLength.Create {
func toModel() throws -> EffectiveLengthModel {
try validate()
return try .init(
name: name,
type: type.rawValue,
straightLengths: straightLengths,
groups: JSONEncoder().encode(groups),
projectID: projectID
)
}
func validate() throws(ValidationError) {
guard !name.isEmpty else {
throw ValidationError("Effective length name can not be empty.")
}
}
}
extension EffectiveLength {
struct Migrate: AsyncMigration {
let name = "CreateEffectiveLength"
func prepare(on database: any Database) async throws {
try await database.schema(EffectiveLengthModel.schema)
.id()
.field("name", .string, .required)
.field("type", .string, .required)
.field("straightLengths", .array(of: .int))
.field("groups", .data)
.field("createdAt", .datetime)
.field("updatedAt", .datetime)
.field("projectID", .uuid, .required, .references(ProjectModel.schema, "id"))
.unique(on: "projectID", "name", "type")
.create()
}
func revert(on database: any Database) async throws {
try await database.schema(EffectiveLengthModel.schema).delete()
}
}
}
final class EffectiveLengthModel: Model, @unchecked Sendable {
static let schema = "effective_length"
@ID(key: .id)
var id: UUID?
@Field(key: "name")
var name: String
@Field(key: "type")
var type: String
@Field(key: "straightLengths")
var straightLengths: [Int]
@Field(key: "groups")
var groups: Data
@Timestamp(key: "createdAt", on: .create, format: .iso8601)
var createdAt: Date?
@Timestamp(key: "updatedAt", on: .update, format: .iso8601)
var updatedAt: Date?
@Parent(key: "projectID")
var project: ProjectModel
init() {}
init(
id: UUID? = nil,
name: String,
type: String,
straightLengths: [Int],
groups: Data,
createdAt: Date? = nil,
updatedAt: Date? = nil,
projectID: Project.ID
) {
self.id = id
self.name = name
self.type = type
self.straightLengths = straightLengths
self.groups = groups
self.createdAt = createdAt
self.updatedAt = updatedAt
$project.id = projectID
}
func toDTO() throws -> EffectiveLength {
try .init(
id: requireID(),
projectID: $project.id,
name: name,
type: .init(rawValue: type)!,
straightLengths: straightLengths,
groups: JSONDecoder().decode([EffectiveLength.Group].self, from: groups),
createdAt: createdAt!,
updatedAt: updatedAt!
)
}
}

View File

@@ -17,6 +17,7 @@ public struct DatabaseClient: Sendable {
public var rooms: Rooms public var rooms: Rooms
public var equipment: Equipment public var equipment: Equipment
public var componentLoss: ComponentLoss public var componentLoss: ComponentLoss
public var effectiveLength: EffectiveLengthClient
} }
extension DatabaseClient: TestDependencyKey { extension DatabaseClient: TestDependencyKey {
@@ -25,7 +26,8 @@ extension DatabaseClient: TestDependencyKey {
projects: .testValue, projects: .testValue,
rooms: .testValue, rooms: .testValue,
equipment: .testValue, equipment: .testValue,
componentLoss: .testValue componentLoss: .testValue,
effectiveLength: .testValue
) )
public static func live(database: any Database) -> Self { public static func live(database: any Database) -> Self {
@@ -34,7 +36,8 @@ 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) componentLoss: .live(database: database),
effectiveLength: .live(database: database)
) )
} }
} }
@@ -58,6 +61,7 @@ extension DatabaseClient.Migrations: DependencyKey {
ComponentPressureLoss.Migrate(), ComponentPressureLoss.Migrate(),
EquipmentInfo.Migrate(), EquipmentInfo.Migrate(),
Room.Migrate(), Room.Migrate(),
EffectiveLength.Migrate(),
] ]
} }
) )

View File

@@ -0,0 +1,87 @@
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 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
}
}
}

View File

@@ -192,3 +192,48 @@ extension SiteRoute.Api {
} }
} }
} }
extension SiteRoute.Api {
public enum EffectiveLengthRoute: Equatable, Sendable {
case create(EffectiveLength.Create)
case delete(id: EffectiveLength.ID)
case fetch(projectID: Project.ID)
case get(id: EffectiveLength.ID)
static let rootPath = "effectiveLength"
public static let router = OneOf {
Route(.case(Self.create)) {
Path {
rootPath
"create"
}
Method.post
Body(.json(EffectiveLength.Create.self))
}
Route(.case(Self.delete(id:))) {
Path {
rootPath
EffectiveLength.ID.parser()
}
Method.delete
}
Route(.case(Self.fetch(projectID:))) {
Path {
rootPath
}
Method.get
Query {
Field("projectID") { Project.ID.parser() }
}
}
Route(.case(Self.get(id:))) {
Path {
rootPath
EffectiveLength.ID.parser()
}
Method.get
}
}
}
}