feat: Fixes build issues, adds ApiController module.

This commit is contained in:
2025-12-30 11:03:43 -05:00
parent 4e1be161b1
commit 79ea188e07
11 changed files with 221 additions and 12 deletions

View File

@@ -6,6 +6,7 @@ let package = Package(
name: "swift-manual-d", name: "swift-manual-d",
products: [ products: [
.executable(name: "App", targets: ["App"]), .executable(name: "App", targets: ["App"]),
.library(name: "ApiController", targets: ["ApiController"]),
.library(name: "DatabaseClient", targets: ["DatabaseClient"]), .library(name: "DatabaseClient", targets: ["DatabaseClient"]),
.library(name: "ManualDCore", targets: ["ManualDCore"]), .library(name: "ManualDCore", targets: ["ManualDCore"]),
.library(name: "ManualDClient", targets: ["ManualDClient"]), .library(name: "ManualDClient", targets: ["ManualDClient"]),
@@ -28,6 +29,7 @@ let package = Package(
.executableTarget( .executableTarget(
name: "App", name: "App",
dependencies: [ dependencies: [
.target(name: "ApiController"),
.target(name: "DatabaseClient"), .target(name: "DatabaseClient"),
.target(name: "ViewController"), .target(name: "ViewController"),
.product(name: "Dependencies", package: "swift-dependencies"), .product(name: "Dependencies", package: "swift-dependencies"),
@@ -40,6 +42,16 @@ let package = Package(
.product(name: "VaporRouting", package: "vapor-routing"), .product(name: "VaporRouting", package: "vapor-routing"),
] ]
), ),
.target(
name: "ApiController",
dependencies: [
.target(name: "DatabaseClient"),
.target(name: "ManualDCore"),
.product(name: "Dependencies", package: "swift-dependencies"),
.product(name: "DependenciesMacros", package: "swift-dependencies"),
.product(name: "Vapor", package: "vapor"),
]
),
.target( .target(
name: "DatabaseClient", name: "DatabaseClient",
dependencies: [ dependencies: [

View File

@@ -0,0 +1,38 @@
import Dependencies
import DependenciesMacros
import Logging
import ManualDCore
extension DependencyValues {
public var apiController: ApiController {
get { self[ApiController.self] }
set { self[ApiController.self] = newValue }
}
}
@DependencyClient
public struct ApiController: Sendable {
public var json: @Sendable (Request) async throws -> (any Encodable)?
}
extension ApiController {
public struct Request: Sendable {
public let route: SiteRoute.Api
public let logger: Logger
public init(route: SiteRoute.Api, logger: Logger) {
self.route = route
self.logger = logger
}
}
}
extension ApiController: DependencyKey {
public static let testValue = Self()
public static let liveValue = Self(
json: { request in
try await request.route.respond(logger: request.logger)
}
)
}

View File

@@ -0,0 +1,119 @@
import DatabaseClient
import Dependencies
import Logging
import ManualDCore
extension SiteRoute.Api {
func respond(logger: Logger) async throws -> (any Encodable)? {
switch self {
case .project(let route):
return try await route.respond(logger: logger)
case .room(let route):
return try await route.respond(logger: logger)
case .equipment(let route):
return try await route.respond(logger: logger)
case .componentLoss(let route):
return try await route.respond(logger: logger)
}
}
}
extension SiteRoute.Api.ProjectRoute {
func respond(logger: Logger) async throws -> (any Encodable)? {
@Dependency(\.database) var database
switch self {
case .create(let request):
return try await database.projects.create(request)
case .delete(let id):
try await database.projects.delete(id)
return nil
case .get(let id):
guard let project = try await database.projects.get(id) else {
logger.error("Project not found for id: \(id)")
throw ApiError("Project not found.")
}
return project
case .index:
// FIX: Fix to return projects.
return [Project]()
}
}
}
extension SiteRoute.Api.RoomRoute {
func respond(logger: Logger) async throws -> (any Encodable)? {
@Dependency(\.database) var database
switch self {
case .create(let request):
return try await database.rooms.create(request)
case .delete(let id):
try await database.rooms.delete(id)
return nil
case .get(let id):
guard let room = try await database.rooms.get(id) else {
logger.error("Room not found for id: \(id)")
throw ApiError("Room not found.")
}
return room
}
}
}
extension SiteRoute.Api.EquipmentRoute {
func respond(logger: Logger) async throws -> (any Encodable)? {
@Dependency(\.database) var database
switch self {
case .create(let request):
return try await database.equipment.create(request)
case .delete(let id):
try await database.equipment.delete(id)
return nil
case .fetch(let projectID):
return try await database.equipment.fetch(projectID)
case .get(let id):
guard let room = try await database.equipment.get(id) else {
logger.error("Equipment not found for id: \(id)")
throw ApiError("Equipment not found.")
}
return room
}
}
}
extension SiteRoute.Api.ComponentLossRoute {
func respond(logger: Logger) async throws -> (any Encodable)? {
@Dependency(\.database) var database
switch self {
case .create(let request):
return try await database.componentLoss.create(request)
case .delete(let id):
try await database.componentLoss.delete(id)
return nil
case .fetch(let projectID):
return try await database.componentLoss.fetch(projectID)
case .get(let id):
guard let room = try await database.componentLoss.get(id) else {
logger.error("Component loss not found for id: \(id)")
throw ApiError("Component loss not found.")
}
return room
}
}
}
public struct ApiError: Error {
let message: String
init(_ message: String) {
self.message = message
}
}

View File

@@ -0,0 +1,37 @@
import ApiController
import ManualDCore
import Vapor
extension ApiController {
func respond(_ route: SiteRoute.Api, request: Vapor.Request) async throws
-> any AsyncResponseEncodable
{
guard let encodable = try await json(.init(route: route, logger: request.logger)) else {
return HTTPStatus.ok
}
return AnyJSONResponse(value: encodable)
}
}
struct AnyJSONResponse: AsyncResponseEncodable {
public var headers: HTTPHeaders = ["Content-Type": "application/json"]
let value: any Encodable
init(additionalHeaders: HTTPHeaders = [:], value: any Encodable) {
if additionalHeaders.contains(name: .contentType) {
self.headers = additionalHeaders
} else {
headers.add(contentsOf: additionalHeaders)
}
self.value = value
}
func encodeResponse(for request: Request) async throws -> Response {
try Response(
status: .ok,
headers: headers,
body: .init(data: JSONEncoder().encode(value))
)
}
}

View File

@@ -1,4 +1,4 @@
// import ApiControllerLive import ApiController
import DatabaseClient import DatabaseClient
import Dependencies import Dependencies
import Vapor import Vapor
@@ -10,17 +10,17 @@ import ViewController
struct DependenciesMiddleware: AsyncMiddleware { struct DependenciesMiddleware: AsyncMiddleware {
private let values: DependencyValues.Continuation private let values: DependencyValues.Continuation
// private let apiController: ApiController private let apiController: ApiController
private let database: DatabaseClient private let database: DatabaseClient
private let viewController: ViewController private let viewController: ViewController
init( init(
database: DatabaseClient, database: DatabaseClient,
// apiController: ApiController = .liveValue, apiController: ApiController = .liveValue,
viewController: ViewController = .testValue viewController: ViewController = .testValue
) { ) {
self.values = withEscapedDependencies { $0 } self.values = withEscapedDependencies { $0 }
// self.apiController = apiController self.apiController = apiController
self.database = database self.database = database
self.viewController = viewController self.viewController = viewController
} }
@@ -28,7 +28,7 @@ struct DependenciesMiddleware: AsyncMiddleware {
func respond(to request: Request, chainingTo next: any AsyncResponder) async throws -> Response { func respond(to request: Request, chainingTo next: any AsyncResponder) async throws -> Response {
try await values.yield { try await values.yield {
try await withDependencies { try await withDependencies {
// $0.apiController = apiController $0.apiController = apiController
$0.database = database $0.database = database
// $0.dateFormatter = .liveValue // $0.dateFormatter = .liveValue
$0.viewController = viewController $0.viewController = viewController

View File

@@ -117,13 +117,12 @@ private func siteHandler(
request: Request, request: Request,
route: SiteRoute route: SiteRoute
) async throws -> any AsyncResponseEncodable { ) async throws -> any AsyncResponseEncodable {
// @Dependency(\.apiController) var apiController @Dependency(\.apiController) var apiController
@Dependency(\.viewController) var viewController @Dependency(\.viewController) var viewController
switch route { switch route {
case .api(let route): case .api(let route):
return HTTPStatus.ok return try await apiController.respond(route, request: request)
// return try await apiController.respond(route, request: request)
case .health: case .health:
return HTTPStatus.ok return HTTPStatus.ok
case .view(let route): case .view(let route):

View File

@@ -82,7 +82,8 @@ extension ComponentPressureLoss {
.field("value", .double, .required) .field("value", .double, .required)
.field("createdAt", .datetime) .field("createdAt", .datetime)
.field("updatedAt", .datetime) .field("updatedAt", .datetime)
.foreignKey("projectID", references: ProjectModel.schema, "id", onDelete: .cascade) .field("projectID", .uuid, .required, .references(ProjectModel.schema, "id"))
// .foreignKey("projectID", references: ProjectModel.schema, "id", onDelete: .cascade)
.unique(on: "projectID", "name") .unique(on: "projectID", "name")
.create() .create()
} }

View File

@@ -91,7 +91,7 @@ extension EquipmentInfo {
.field("coolingCFM", .int16, .required) .field("coolingCFM", .int16, .required)
.field("createdAt", .datetime) .field("createdAt", .datetime)
.field("updatedAt", .datetime) .field("updatedAt", .datetime)
.foreignKey("projectID", references: ProjectModel.schema, "id", onDelete: .cascade) .field("projectID", .uuid, .required, .references(ProjectModel.schema, "id"))
.unique(on: "projectID") .unique(on: "projectID")
.create() .create()
} }

View File

@@ -54,9 +54,9 @@ extension DatabaseClient.Migrations: DependencyKey {
public static let liveValue = Self( public static let liveValue = Self(
run: { run: {
[ [
Project.Migrate(),
ComponentPressureLoss.Migrate(), ComponentPressureLoss.Migrate(),
EquipmentInfo.Migrate(), EquipmentInfo.Migrate(),
Project.Migrate(),
Room.Migrate(), Room.Migrate(),
] ]
} }

View File

@@ -123,6 +123,9 @@ final class ProjectModel: Model, @unchecked Sendable {
@Timestamp(key: "updatedAt", on: .update, format: .iso8601) @Timestamp(key: "updatedAt", on: .update, format: .iso8601)
var updatedAt: Date? var updatedAt: Date?
@Children(for: \.$project)
var componentLosses: [ComponentLossModel]
init() {} init() {}
init( init(

View File

@@ -83,7 +83,7 @@ extension Room {
.field("coolingTotal", .double, .required) .field("coolingTotal", .double, .required)
.field("coolingSensible", .double, .required) .field("coolingSensible", .double, .required)
.field("registerCount", .int8, .required) .field("registerCount", .int8, .required)
.foreignKey("projectID", references: ProjectModel.schema, "id", onDelete: .cascade) .field("projectID", .uuid, .required, .references(ProjectModel.schema, "id"))
.unique(on: "projectID", "name") .unique(on: "projectID", "name")
.create() .create()
} }