feat: Adds rooms database model.

This commit is contained in:
2025-12-29 15:21:59 -05:00
parent ccf0dfabaa
commit 31930cd399
5 changed files with 276 additions and 12 deletions

View File

@@ -14,6 +14,23 @@ extension DependencyValues {
public struct DatabaseClient: Sendable {
public var migrations: Migrations
public var projects: Projects
public var rooms: Rooms
}
extension DatabaseClient: TestDependencyKey {
public static let testValue: DatabaseClient = Self(
migrations: .testValue,
projects: .testValue,
rooms: .testValue
)
public static func live(database: any Database) -> Self {
.init(
migrations: .liveValue,
projects: .live(database: database),
rooms: .live(database: database)
)
}
}
extension DatabaseClient {
@@ -23,13 +40,17 @@ extension DatabaseClient {
}
}
extension DatabaseClient: TestDependencyKey {
public static let testValue: DatabaseClient = Self(
migrations: .testValue,
projects: .testValue
)
}
extension DatabaseClient.Migrations: TestDependencyKey {
public static let testValue = Self()
}
extension DatabaseClient.Migrations: DependencyKey {
public static let liveValue = Self(
run: {
[
Project.Migrate(),
Room.Migrate(),
]
}
)
}

View File

@@ -26,13 +26,13 @@ extension DatabaseClient.Projects {
return try model.toDTO()
},
delete: { id in
guard let model = ProjectModel.find(id, on: database) else {
guard let model = try await ProjectModel.find(id, on: database) else {
throw NotFoundError()
}
try await model.delete(on: database)
},
get: { id in
ProjectModel.find(id, on: database).map { try $0.toDTO() }
try await ProjectModel.find(id, on: database).map { try $0.toDTO() }
}
)
}

View File

@@ -0,0 +1,165 @@
import Dependencies
import DependenciesMacros
import Fluent
import Foundation
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 get: @Sendable (Room.ID) async throws -> Room?
}
}
extension DatabaseClient.Rooms: TestDependencyKey {
public static let testValue = Self()
}
extension DatabaseClient.Rooms {
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 RoomModel.find(id, on: database) else {
throw NotFoundError()
}
try await model.delete(on: database)
},
get: { id in
try await RoomModel.find(id, on: database).map { try $0.toDTO() }
}
)
}
}
extension Room.Create {
func toModel() throws(ValidationError) -> RoomModel {
try validate()
return .init(
name: name,
heatingLoad: heatingLoad,
coolingTotal: coolingTotal,
coolingSensible: coolingSensible,
registerCount: registerCount,
projectID: projectID
)
}
func validate() throws(ValidationError) {
guard !name.isEmpty else {
throw ValidationError("Room name should not be empty.")
}
guard heatingLoad >= 0 else {
throw ValidationError("Room heating load should not be less than 0.")
}
guard coolingTotal >= 0 else {
throw ValidationError("Room cooling total should not be less than 0.")
}
guard coolingSensible >= 0 else {
throw ValidationError("Room cooling sensible should not be less than 0.")
}
guard registerCount >= 1 else {
throw ValidationError("Room cooling sensible should not be less than 1.")
}
}
}
extension Room {
struct Migrate: AsyncMigration {
let name = "CreateRoom"
func prepare(on database: any Database) async throws {
try await database.schema(RoomModel.schema)
.id()
.field("name", .string, .required)
.field("heatingLoad", .double, .required)
.field("coolingTotal", .double, .required)
.field("coolingSensible", .double, .required)
.field("registerCount", .int8, .required)
.foreignKey("projectID", references: ProjectModel.schema, "id", onDelete: .cascade)
.unique(on: "projectID", "name")
.create()
}
func revert(on database: any Database) async throws {
try await database.schema(RoomModel.schema).delete()
}
}
}
final class RoomModel: Model, @unchecked Sendable {
static let schema = "room"
@ID(key: .id)
var id: UUID?
@Field(key: "name")
var name: String
@Field(key: "heatingLoad")
var heatingLoad: Double
@Field(key: "coolingTotal")
var coolingTotal: Double
@Field(key: "coolingSensible")
var coolingSensible: Double
@Field(key: "registerCount")
var registerCount: Int
@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,
heatingLoad: Double,
coolingTotal: Double,
coolingSensible: Double,
registerCount: Int,
createdAt: Date? = nil,
updatedAt: Date? = nil,
projectID: Project.ID
) {
self.id = id
self.name = name
self.heatingLoad = heatingLoad
self.coolingTotal = coolingTotal
self.coolingSensible = coolingSensible
self.registerCount = registerCount
self.createdAt = createdAt
self.updatedAt = updatedAt
$project.id = projectID
}
func toDTO() throws -> Room {
try .init(
id: requireID(),
projectID: $project.id,
name: name,
heatingLoad: heatingLoad,
coolingLoad: .init(total: coolingTotal, sensible: coolingSensible),
registerCount: registerCount,
createdAt: createdAt!,
updatedAt: updatedAt!
)
}
}

View File

@@ -1,20 +1,61 @@
import Foundation
public struct Room: Codable, Equatable, Sendable {
public struct Room: Codable, Equatable, Identifiable, Sendable {
public let id: UUID
public let projectID: Project.ID
public let name: String
public let heatingLoad: Double
public let coolingLoad: CoolingLoad
public let registerCount: Int
public let createdAt: Date
public let updatedAt: Date
public init(
id: UUID,
projectID: Project.ID,
name: String,
heatingLoad: Double,
coolingLoad: CoolingLoad,
registerCount: Int = 1
registerCount: Int = 1,
createdAt: Date,
updatedAt: Date
) {
self.id = id
self.projectID = projectID
self.name = name
self.heatingLoad = heatingLoad
self.coolingLoad = coolingLoad
self.registerCount = registerCount
self.createdAt = createdAt
self.updatedAt = updatedAt
}
}
extension Room {
// TODO: Maybe remove project ID, and make dependencies that retrieves current project id??
public struct Create: Codable, Equatable, Sendable {
public let projectID: Project.ID
public let name: String
public let heatingLoad: Double
public let coolingTotal: Double
public let coolingSensible: Double
public let registerCount: Int
public init(
projectID: Project.ID,
name: String,
heatingLoad: Double,
coolingTotal: Double,
coolingSensible: Double,
registerCount: Int = 1
) {
self.projectID = projectID
self.name = name
self.heatingLoad = heatingLoad
self.coolingTotal = coolingTotal
self.coolingSensible = coolingSensible
self.registerCount = registerCount
}
}
}

View File

@@ -9,6 +9,7 @@ extension SiteRoute {
public enum Api: Sendable, Equatable {
case project(Self.ProjectRoute)
case room(Self.RoomRoute)
public static let rootPath = Path {
"api"
@@ -20,6 +21,10 @@ extension SiteRoute {
rootPath
ProjectRoute.router
}
Route(.case(Self.room)) {
rootPath
RoomRoute.router
}
}
}
@@ -61,5 +66,37 @@ extension SiteRoute.Api {
}
}
}
}
extension SiteRoute.Api {
public enum RoomRoute: Sendable, Equatable {
case create(Room.Create)
case delete(id: Room.ID)
case get(id: Room.ID)
static let rootPath = "rooms"
public static let router = OneOf {
Route(.case(Self.create)) {
Path { rootPath }
Method.post
Body(.json(Room.Create.self))
}
Route(.case(Self.delete(id:))) {
Path {
rootPath
Room.ID.parser()
}
Method.delete
}
Route(.case(Self.get(id:))) {
Path {
rootPath
Room.ID.parser()
}
Method.get
}
}
}
}