feat: Adds createMany for rooms, in prep for parsing / uploading a csv file of room loads.
All checks were successful
CI / Linux Tests (push) Successful in 7m0s

This commit is contained in:
2026-02-04 21:06:05 -05:00
parent 10dd0dac82
commit 5f03056534
6 changed files with 87 additions and 70 deletions

View File

@@ -6,16 +6,14 @@
"version": "os-provided", "version": "os-provided",
"ppa": "false" "ppa": "false"
}, },
"ghcr.io/swift-server-community/swift-devcontainer-features/jemalloc:1": { },
"ghcr.io/swift-server-community/swift-devcontainer-features/swift-format:0": { },
"ghcr.io/swift-server-community/swift-devcontainer-features/foundationnetworking:1": {},
"ghcr.io/devcontainers-extra/features/markdownlint-cli2:1": {},
"ghcr.io/jsburckhardt/devcontainer-features/just:1": {}, "ghcr.io/jsburckhardt/devcontainer-features/just:1": {},
"ghcr.io/rocker-org/devcontainer-features/pandoc:1": {}, "ghcr.io/rocker-org/devcontainer-features/pandoc:1": {},
//"ghcr.io/devcontainers/features/docker-in-docker:2": {}, //"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/wxw-matt/devcontainer-features/apt:latest": { "ghcr.io/wxw-matt/devcontainer-features/apt:latest": {
"packages": "weasyprint gnupg2 tmux" "packages": "weasyprint gnupg2 tmux"
} },
"ghcr.io/swift-server-community/swift-devcontainer-features/jemalloc:1": { },
}, },
"runArgs": [ "runArgs": [
"--cap-add=SYS_PTRACE", "--cap-add=SYS_PTRACE",
@@ -23,9 +21,20 @@
"seccomp=unconfined" "seccomp=unconfined"
], ],
"remoteEnv": { "remoteEnv": {
"TERM": "xterm-256color" // Set TERM, prevents problems when "xterm-ghostty" get's passed from host,
// which causes weird typing and display problems.
"TERM": "xterm-256color",
// Less backtrace error warnings from swift.
"SWIFT_BACKTRACE": "enable=no"
}, },
"remoteUser": "swift", "remoteUser": "swift",
"forwardPorts": [8080], "forwardPorts": [8080],
"mounts": [
{
"type": "bind",
"source": "${localEnv:HOME}/.local/share/nvim",
"target": "/home/swift/.local/share/nvim"
}
]
} }

View File

@@ -90,6 +90,7 @@ public struct DatabaseClient: Sendable {
@DependencyClient @DependencyClient
public struct Rooms: Sendable { public struct Rooms: Sendable {
public var create: @Sendable (Room.Create) async throws -> Room public var create: @Sendable (Room.Create) async throws -> Room
public var createMany: @Sendable ([Room.Create]) async throws -> [Room]
public var delete: @Sendable (Room.ID) async throws -> Void public var delete: @Sendable (Room.ID) async throws -> Void
public var deleteRectangularSize: public var deleteRectangularSize:
@Sendable (Room.ID, Room.RectangularSize.ID) async throws -> Room @Sendable (Room.ID, Room.RectangularSize.ID) async throws -> Room

View File

@@ -15,6 +15,13 @@ extension DatabaseClient.Rooms: TestDependencyKey {
try await model.validateAndSave(on: database) try await model.validateAndSave(on: database)
return try model.toDTO() return try model.toDTO()
}, },
createMany: { rooms in
try await rooms.asyncMap { request in
let model = try request.toModel()
try await model.validateAndSave(on: database)
return try model.toDTO()
}
},
delete: { id in delete: { id in
guard let model = try await RoomModel.find(id, on: database) else { guard let model = try await RoomModel.find(id, on: database) else {
throw NotFoundError() throw NotFoundError()

View File

@@ -0,0 +1,41 @@
import Foundation
extension Sequence {
// Taken from: https://forums.swift.org/t/are-there-any-kind-of-asyncmap-method-for-a-normal-sequence/77354/7
func asyncMap<Result: Sendable>(
_ transform: @escaping @Sendable (Element) async throws -> Result
) async rethrows -> [Result] where Element: Sendable {
try await withThrowingTaskGroup(of: (Int, Result).self) { group in
var i = 0
var iterator = self.makeIterator()
var results = [Result?]()
results.reserveCapacity(underestimatedCount)
func submitTask() throws {
try Task.checkCancellation()
if let element = iterator.next() {
results.append(nil)
group.addTask { [i] in try await (i, transform(element)) }
i += 1
}
}
// Add initial tasks
for _ in 0..<ProcessInfo.processInfo.processorCount {
try submitTask()
}
// Submit more tasks as results complete
while let (index, result) = try await group.next() {
results[index] = result
try submitTask()
}
return results.compactMap { $0 }
}
}
}

14
TODO.md
View File

@@ -5,14 +5,14 @@
- [x] Add postgres / mysql support - [x] 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)
- [ ] Logo / navbar name may have to change if it's not duct-calc. - [ ] Logo / navbar name may have to change if it's not duct-calc.
- [ ] MainPage meta items will have to change also - [ ] MainPage meta items will have to change also
- [ ] Add ability for either sensible or total load while specifying a room load. - [ ] Add ability for either sensible or total load while specifying a room load.
- CoolCalc current version specifies the sensible cooling for a room break down, - CoolCalc current version specifies the sensible cooling for a room break down,
and currently we require the total load and calculate sensible based on project and currently we require the total load and calculate sensible based on project
shr. shr.
- [ ] Add ability to associate room load / airflow with another room. - [ ] Add ability to associate room load / airflow with another room.
- [ ] Trunk size form, room / register selection is wonky when labels are long. - [ ] Trunk size form, room / register selection is wonky when labels are long.
- They will overlap each other making it difficult to read / decipher which checkbox belongs - They will overlap each other making it difficult to read / decipher which checkbox belongs
to which label. to which label.
- [ ] Add select all rooms for trunks, useful for sizing main supply or return trunks. - [ ] Add select all rooms for trunks, useful for sizing main supply or return trunks.

View File

@@ -43,6 +43,22 @@ struct RoomTests {
} }
} }
@Test
func createMany() async throws {
try await withTestUserAndProject { _, project in
@Dependency(\.database.rooms) var rooms
let created = try await rooms.createMany([
.init(projectID: project.id, name: "Test 1", heatingLoad: 1234, coolingTotal: 1234),
.init(projectID: project.id, name: "Test 2", heatingLoad: 1234, coolingTotal: 1234),
])
#expect(created.count == 2)
#expect(created[0].name == "Test 1")
#expect(created[1].name == "Test 2")
}
}
@Test @Test
func notFound() async throws { func notFound() async throws {
try await withDatabase { try await withDatabase {
@@ -128,61 +144,4 @@ struct RoomTests {
// } // }
} }
} }
// @Test(
// arguments: [
// Room.Update(
// name: "",
// heatingLoad: 12345,
// coolingTotal: 12344,
// coolingSensible: nil,
// registerCount: 1
// ),
// Room.Update(
// name: "Test",
// heatingLoad: -12345,
// coolingTotal: 12344,
// coolingSensible: nil,
// registerCount: 1
// ),
// Room.Update(
// name: "Test",
// heatingLoad: 12345,
// coolingTotal: -12344,
// coolingSensible: nil,
// registerCount: 1
// ),
// Room.Update(
// name: "Test",
// heatingLoad: 12345,
// coolingTotal: 12344,
// coolingSensible: -123,
// registerCount: 1
// ),
// Room.Update(
// name: "Test",
// heatingLoad: 12345,
// coolingTotal: 12344,
// coolingSensible: nil,
// registerCount: -1
// ),
// Room.Update(
// name: "",
// heatingLoad: -12345,
// coolingTotal: -12344,
// coolingSensible: -1,
// registerCount: -1
// ),
// ]
// )
// func updateValidations(room: Room.Update) throws {
// #expect(throws: (any Error).self) {
// // do {
// try room.validate()
// // } catch {
// // print("\(error)")
// // throw error
// // }
// }
// }
} }