Compare commits
3 Commits
861ff3bfd6
...
ccf0dfabaa
| Author | SHA1 | Date | |
|---|---|---|---|
|
ccf0dfabaa
|
|||
|
dce358d85e
|
|||
|
6bc6a7d7fa
|
335
Package.resolved
335
Package.resolved
@@ -1,6 +1,24 @@
|
||||
{
|
||||
"originHash" : "426df0aee89a834f20c1c804ecbfbed0bc19ef629c2a1fd2e6260702b97b6f31",
|
||||
"originHash" : "f8ca659e4ec9041ea590b94c8dc267718be8941a4594eb74964b6396e987ca95",
|
||||
"pins" : [
|
||||
{
|
||||
"identity" : "async-http-client",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/swift-server/async-http-client.git",
|
||||
"state" : {
|
||||
"revision" : "5dd84c7bb48b348751d7bbe7ba94a17bafdcef37",
|
||||
"version" : "1.30.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "async-kit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/async-kit.git",
|
||||
"state" : {
|
||||
"revision" : "6f3615ccf2ac3c2ae0c8087d527546e9544a43dd",
|
||||
"version" : "1.21.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "combine-schedulers",
|
||||
"kind" : "remoteSourceControl",
|
||||
@@ -10,6 +28,51 @@
|
||||
"version" : "1.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "console-kit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/console-kit.git",
|
||||
"state" : {
|
||||
"revision" : "742f624a998cba2a9e653d9b1e91ad3f3a5dff6b",
|
||||
"version" : "4.15.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "fluent",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/fluent.git",
|
||||
"state" : {
|
||||
"revision" : "2fe9e36daf4bdb5edcf193e0d0806ba2074d2864",
|
||||
"version" : "4.13.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "fluent-kit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/fluent-kit.git",
|
||||
"state" : {
|
||||
"revision" : "0272fdaf7cf6f482c2799026c0695f5fe40e3e8c",
|
||||
"version" : "1.53.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "fluent-sqlite-driver",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/fluent-sqlite-driver.git",
|
||||
"state" : {
|
||||
"revision" : "73529a63ab11c7fe87da17b5a67a1b1f58c020f8",
|
||||
"version" : "4.8.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "multipart-kit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/multipart-kit.git",
|
||||
"state" : {
|
||||
"revision" : "3498e60218e6003894ff95192d756e238c01f44e",
|
||||
"version" : "4.7.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "opencombine",
|
||||
"kind" : "remoteSourceControl",
|
||||
@@ -19,6 +82,96 @@
|
||||
"version" : "0.14.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "routing-kit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/routing-kit.git",
|
||||
"state" : {
|
||||
"revision" : "1a10ccea61e4248effd23b6e814999ce7bdf0ee0",
|
||||
"version" : "4.9.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sql-kit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/sql-kit.git",
|
||||
"state" : {
|
||||
"revision" : "c0ea243ffeb8b5ff9e20a281e44003c6abb8896f",
|
||||
"version" : "3.34.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sqlite-kit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/sqlite-kit.git",
|
||||
"state" : {
|
||||
"revision" : "f35a863ecc2da5d563b836a9a696b148b0f4169f",
|
||||
"version" : "4.5.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "sqlite-nio",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/sqlite-nio.git",
|
||||
"state" : {
|
||||
"revision" : "2ab61385b70da8ed74958ce62fa9ebf0359cb08b",
|
||||
"version" : "1.12.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-algorithms",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-algorithms.git",
|
||||
"state" : {
|
||||
"revision" : "87e50f483c54e6efd60e885f7f5aa946cee68023",
|
||||
"version" : "1.2.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-asn1",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-asn1.git",
|
||||
"state" : {
|
||||
"revision" : "810496cf121e525d660cd0ea89a758740476b85f",
|
||||
"version" : "1.5.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-async-algorithms",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-async-algorithms.git",
|
||||
"state" : {
|
||||
"revision" : "6c050d5ef8e1aa6342528460db614e9770d7f804",
|
||||
"version" : "1.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-atomics",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-atomics.git",
|
||||
"state" : {
|
||||
"revision" : "b601256eab081c0f92f059e12818ac1d4f178ff7",
|
||||
"version" : "1.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-case-paths",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swift-case-paths.git",
|
||||
"state" : {
|
||||
"revision" : "6989976265be3f8d2b5802c722f9ba168e227c71",
|
||||
"version" : "1.7.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-certificates",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-certificates.git",
|
||||
"state" : {
|
||||
"revision" : "133a347911b6ad0fc8fe3bf46ca90c66cff97130",
|
||||
"version" : "1.17.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-clocks",
|
||||
"kind" : "remoteSourceControl",
|
||||
@@ -28,6 +181,15 @@
|
||||
"version" : "1.0.6"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-collections",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-collections",
|
||||
"state" : {
|
||||
"revision" : "7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e",
|
||||
"version" : "1.3.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-concurrency-extras",
|
||||
"kind" : "remoteSourceControl",
|
||||
@@ -37,6 +199,15 @@
|
||||
"version" : "1.3.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-crypto",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-crypto.git",
|
||||
"state" : {
|
||||
"revision" : "6f70fa9eab24c1fd982af18c281c4525d05e3095",
|
||||
"version" : "4.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-dependencies",
|
||||
"kind" : "remoteSourceControl",
|
||||
@@ -46,6 +217,132 @@
|
||||
"version" : "1.10.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-distributed-tracing",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-distributed-tracing.git",
|
||||
"state" : {
|
||||
"revision" : "baa932c1336f7894145cbaafcd34ce2dd0b77c97",
|
||||
"version" : "1.3.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-http-structured-headers",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-http-structured-headers.git",
|
||||
"state" : {
|
||||
"revision" : "76d7627bd88b47bf5a0f8497dd244885960dde0b",
|
||||
"version" : "1.6.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-http-types",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-http-types.git",
|
||||
"state" : {
|
||||
"revision" : "45eb0224913ea070ec4fba17291b9e7ecf4749ca",
|
||||
"version" : "1.5.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-log",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-log.git",
|
||||
"state" : {
|
||||
"revision" : "bc386b95f2a16ccd0150a8235e7c69eab2b866ca",
|
||||
"version" : "1.8.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-metrics",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-metrics.git",
|
||||
"state" : {
|
||||
"revision" : "0743a9364382629da3bf5677b46a2c4b1ce5d2a6",
|
||||
"version" : "2.7.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-nio",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-nio.git",
|
||||
"state" : {
|
||||
"revision" : "a1605a3303a28e14d822dec8aaa53da8a9490461",
|
||||
"version" : "2.92.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-nio-extras",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-nio-extras.git",
|
||||
"state" : {
|
||||
"revision" : "1c90641b02b6ab47c6d0db2063a12198b04e83e2",
|
||||
"version" : "1.31.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-nio-http2",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-nio-http2.git",
|
||||
"state" : {
|
||||
"revision" : "c2ba4cfbb83f307c66f5a6df6bb43e3c88dfbf80",
|
||||
"version" : "1.39.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-nio-ssl",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-nio-ssl.git",
|
||||
"state" : {
|
||||
"revision" : "173cc69a058623525a58ae6710e2f5727c663793",
|
||||
"version" : "2.36.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-nio-transport-services",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-nio-transport-services.git",
|
||||
"state" : {
|
||||
"revision" : "60c3e187154421171721c1a38e800b390680fb5d",
|
||||
"version" : "1.26.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-numerics",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-numerics.git",
|
||||
"state" : {
|
||||
"revision" : "0c0290ff6b24942dadb83a929ffaaa1481df04a2",
|
||||
"version" : "1.1.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-parsing",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swift-parsing",
|
||||
"state" : {
|
||||
"revision" : "3432cb81164dd3d69a75d0d63205be5fbae2c34b",
|
||||
"version" : "0.14.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-service-context",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-service-context.git",
|
||||
"state" : {
|
||||
"revision" : "1983448fefc717a2bc2ebde5490fe99873c5b8a6",
|
||||
"version" : "1.2.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-service-lifecycle",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/swift-server/swift-service-lifecycle.git",
|
||||
"state" : {
|
||||
"revision" : "1de37290c0ab3c5a96028e0f02911b672fd42348",
|
||||
"version" : "2.9.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-syntax",
|
||||
"kind" : "remoteSourceControl",
|
||||
@@ -55,6 +352,42 @@
|
||||
"version" : "602.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-system",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/apple/swift-system.git",
|
||||
"state" : {
|
||||
"revision" : "395a77f0aa927f0ff73941d7ac35f2b46d47c9db",
|
||||
"version" : "1.6.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "swift-url-routing",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/pointfreeco/swift-url-routing.git",
|
||||
"state" : {
|
||||
"revision" : "1cfd564259ecb1d324bb718a8f03e513dab738d2",
|
||||
"version" : "0.6.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "vapor",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/vapor.git",
|
||||
"state" : {
|
||||
"revision" : "f7090db27390ebc4cadbff06d76fe8ce79d6ece6",
|
||||
"version" : "4.120.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "websocket-kit",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/vapor/websocket-kit.git",
|
||||
"state" : {
|
||||
"revision" : "8666c92dbbb3c8eefc8008c9c8dcf50bfd302167",
|
||||
"version" : "2.16.1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "xctest-dynamic-overlay",
|
||||
"kind" : "remoteSourceControl",
|
||||
|
||||
@@ -5,19 +5,40 @@ import PackageDescription
|
||||
let package = Package(
|
||||
name: "swift-manual-d",
|
||||
products: [
|
||||
.library(name: "swift-manual-d", targets: ["swift-manual-d"]),
|
||||
.library(name: "ManualDCore", targets: ["ManualDCore"]),
|
||||
.library(name: "ManualDClient", targets: ["ManualDClient"]),
|
||||
],
|
||||
dependencies: [
|
||||
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.0.0")
|
||||
// 💧 A server-side Swift web framework.
|
||||
.package(url: "https://github.com/vapor/vapor.git", from: "4.110.1"),
|
||||
// 🗄 An ORM for SQL and NoSQL databases.
|
||||
.package(url: "https://github.com/vapor/fluent.git", from: "4.9.0"),
|
||||
// 🪶 Fluent driver for SQLite.
|
||||
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.6.0"),
|
||||
// 🔵 Non-blocking, event-driven networking Swift. Used for, custom executors
|
||||
.package(url: "https://github.com/apple/swift-nio.git", from: "2.65.0"),
|
||||
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "1.0.0"),
|
||||
.package(url: "https://github.com/pointfreeco/swift-url-routing.git", from: "0.6.2"),
|
||||
.package(url: "https://github.com/pointfreeco/swift-case-paths.git", from: "1.6.0"),
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "swift-manual-d"
|
||||
name: "DatabaseClient",
|
||||
dependencies: [
|
||||
.target(name: "ManualDCore"),
|
||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||
.product(name: "DependenciesMacros", package: "swift-dependencies"),
|
||||
.product(name: "Fluent", package: "fluent"),
|
||||
.product(name: "Vapor", package: "vapor"),
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "ManualDCore"
|
||||
name: "ManualDCore",
|
||||
dependencies: [
|
||||
.product(name: "Dependencies", package: "swift-dependencies"),
|
||||
.product(name: "URLRouting", package: "swift-url-routing"),
|
||||
.product(name: "CasePaths", package: "swift-case-paths"),
|
||||
]
|
||||
),
|
||||
.target(
|
||||
name: "ManualDClient",
|
||||
@@ -35,8 +56,10 @@ let package = Package(
|
||||
]
|
||||
),
|
||||
.testTarget(
|
||||
name: "swift-manual-dTests",
|
||||
dependencies: ["swift-manual-d"]
|
||||
name: "ApiRouteTests",
|
||||
dependencies: [
|
||||
.target(name: "ManualDCore")
|
||||
]
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
11
Sources/DatabaseClient/Errors.swift
Normal file
11
Sources/DatabaseClient/Errors.swift
Normal file
@@ -0,0 +1,11 @@
|
||||
import Foundation
|
||||
|
||||
public struct ValidationError: Error {
|
||||
public let message: String
|
||||
|
||||
public init(_ message: String) {
|
||||
self.message = message
|
||||
}
|
||||
}
|
||||
|
||||
public struct NotFoundError: Error {}
|
||||
35
Sources/DatabaseClient/Interface.swift
Normal file
35
Sources/DatabaseClient/Interface.swift
Normal file
@@ -0,0 +1,35 @@
|
||||
import Dependencies
|
||||
import DependenciesMacros
|
||||
import FluentKit
|
||||
import ManualDCore
|
||||
|
||||
extension DependencyValues {
|
||||
public var database: DatabaseClient {
|
||||
get { self[DatabaseClient.self] }
|
||||
set { self[DatabaseClient.self] = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
@DependencyClient
|
||||
public struct DatabaseClient: Sendable {
|
||||
public var migrations: Migrations
|
||||
public var projects: Projects
|
||||
}
|
||||
|
||||
extension DatabaseClient {
|
||||
@DependencyClient
|
||||
public struct Migrations: Sendable {
|
||||
public var run: @Sendable () async throws -> [any AsyncMigration]
|
||||
}
|
||||
}
|
||||
|
||||
extension DatabaseClient: TestDependencyKey {
|
||||
public static let testValue: DatabaseClient = Self(
|
||||
migrations: .testValue,
|
||||
projects: .testValue
|
||||
)
|
||||
}
|
||||
|
||||
extension DatabaseClient.Migrations: TestDependencyKey {
|
||||
public static let testValue = Self()
|
||||
}
|
||||
161
Sources/DatabaseClient/Projects.swift
Normal file
161
Sources/DatabaseClient/Projects.swift
Normal file
@@ -0,0 +1,161 @@
|
||||
import Dependencies
|
||||
import DependenciesMacros
|
||||
import Fluent
|
||||
import Foundation
|
||||
import ManualDCore
|
||||
|
||||
extension DatabaseClient {
|
||||
@DependencyClient
|
||||
public struct Projects: Sendable {
|
||||
public var create: @Sendable (Project.Create) async throws -> Project
|
||||
public var delete: @Sendable (Project.ID) async throws -> Void
|
||||
public var get: @Sendable (Project.ID) async throws -> Project?
|
||||
}
|
||||
}
|
||||
|
||||
extension DatabaseClient.Projects: TestDependencyKey {
|
||||
public static let testValue = Self()
|
||||
}
|
||||
|
||||
extension DatabaseClient.Projects {
|
||||
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 = 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() }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension Project.Create {
|
||||
|
||||
func toModel() throws -> ProjectModel {
|
||||
try validate()
|
||||
return .init(
|
||||
name: name,
|
||||
streetAddress: streetAddress,
|
||||
city: city,
|
||||
state: state,
|
||||
zipCode: zipCode
|
||||
)
|
||||
}
|
||||
|
||||
func validate() throws(ValidationError) {
|
||||
guard !name.isEmpty else {
|
||||
throw ValidationError("Project name should not be empty.")
|
||||
}
|
||||
guard !streetAddress.isEmpty else {
|
||||
throw ValidationError("Project street address should not be empty.")
|
||||
}
|
||||
guard !city.isEmpty else {
|
||||
throw ValidationError("Project city should not be empty.")
|
||||
}
|
||||
guard !state.isEmpty else {
|
||||
throw ValidationError("Project state should not be empty.")
|
||||
}
|
||||
guard !zipCode.isEmpty else {
|
||||
throw ValidationError("Project zipCode should not be empty.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Project {
|
||||
struct Migrate: AsyncMigration {
|
||||
let name = "CreateProject"
|
||||
|
||||
func prepare(on database: any Database) async throws {
|
||||
try await database.schema(ProjectModel.schema)
|
||||
.id()
|
||||
.field("name", .string, .required)
|
||||
.field("streetAddress", .string, .required)
|
||||
.field("city", .string, .required)
|
||||
.field("state", .string, .required)
|
||||
.field("zipCode", .string, .required)
|
||||
.field("createdAt", .datetime)
|
||||
.field("updatedAt", .datetime)
|
||||
.unique(on: "name")
|
||||
.create()
|
||||
}
|
||||
|
||||
func revert(on database: any Database) async throws {
|
||||
try await database.schema(ProjectModel.schema).delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The Database model.
|
||||
final class ProjectModel: Model, @unchecked Sendable {
|
||||
|
||||
static let schema = "project"
|
||||
|
||||
@ID(key: .id)
|
||||
var id: UUID?
|
||||
|
||||
@Field(key: "name")
|
||||
var name: String
|
||||
|
||||
@Field(key: "streetAddress")
|
||||
var streetAddress: String
|
||||
|
||||
@Field(key: "city")
|
||||
var city: String
|
||||
|
||||
@Field(key: "state")
|
||||
var state: String
|
||||
|
||||
@Field(key: "zipCode")
|
||||
var zipCode: String
|
||||
|
||||
@Timestamp(key: "createdAt", on: .create, format: .iso8601)
|
||||
var createdAt: Date?
|
||||
|
||||
@Timestamp(key: "updatedAt", on: .update, format: .iso8601)
|
||||
var updatedAt: Date?
|
||||
|
||||
init() {}
|
||||
|
||||
init(
|
||||
id: UUID? = nil,
|
||||
name: String,
|
||||
streetAddress: String,
|
||||
city: String,
|
||||
state: String,
|
||||
zipCode: String,
|
||||
createdAt: Date? = nil,
|
||||
updatedAt: Date? = nil
|
||||
) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.streetAddress = streetAddress
|
||||
self.city = city
|
||||
self.city = city
|
||||
self.state = state
|
||||
self.zipCode = zipCode
|
||||
self.createdAt = createdAt
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
|
||||
func toDTO() throws -> Project {
|
||||
try .init(
|
||||
id: requireID(),
|
||||
name: name,
|
||||
streetAddress: streetAddress,
|
||||
city: city,
|
||||
state: state,
|
||||
zipCode: zipCode,
|
||||
createdAt: createdAt!,
|
||||
updatedAt: updatedAt!
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ extension ManualDClient: DependencyKey {
|
||||
},
|
||||
equivalentRectangularDuct: { request in
|
||||
let width = (Double.pi * (pow(Double(request.roundSize) / 2.0, 2.0))) / Double(request.height)
|
||||
// Round the width up or fail (really should never fail since we know the input is a number).
|
||||
guard let widthStr = numberFormatter.string(for: width),
|
||||
let widthInt = Int(widthStr)
|
||||
else {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
public struct EquipmentInfo: Codable, Equatable {
|
||||
public struct EquipmentInfo: Codable, Equatable, Sendable {
|
||||
public let staticPressure: Double
|
||||
public let heatingCFM: Int
|
||||
public let coolingCFM: Int
|
||||
|
||||
59
Sources/ManualDCore/Project.swift
Normal file
59
Sources/ManualDCore/Project.swift
Normal file
@@ -0,0 +1,59 @@
|
||||
import Foundation
|
||||
|
||||
public struct Project: Codable, Equatable, Identifiable, Sendable {
|
||||
|
||||
public let id: UUID
|
||||
public let name: String
|
||||
public let streetAddress: String
|
||||
public let city: String
|
||||
public let state: String
|
||||
public let zipCode: String
|
||||
public let createdAt: Date
|
||||
public let updatedAt: Date
|
||||
|
||||
public init(
|
||||
id: UUID,
|
||||
name: String,
|
||||
streetAddress: String,
|
||||
city: String,
|
||||
state: String,
|
||||
zipCode: String,
|
||||
createdAt: Date,
|
||||
updatedAt: Date
|
||||
) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.streetAddress = streetAddress
|
||||
self.city = city
|
||||
self.state = state
|
||||
self.zipCode = zipCode
|
||||
self.createdAt = createdAt
|
||||
self.updatedAt = updatedAt
|
||||
}
|
||||
}
|
||||
|
||||
extension Project {
|
||||
|
||||
public struct Create: Codable, Equatable, Sendable {
|
||||
|
||||
public let name: String
|
||||
public let streetAddress: String
|
||||
public let city: String
|
||||
public let state: String
|
||||
public let zipCode: String
|
||||
|
||||
public init(
|
||||
name: String,
|
||||
streetAddress: String,
|
||||
city: String,
|
||||
state: String,
|
||||
zipCode: String
|
||||
) {
|
||||
self.name = name
|
||||
self.streetAddress = streetAddress
|
||||
self.city = city
|
||||
self.state = state
|
||||
self.zipCode = zipCode
|
||||
}
|
||||
}
|
||||
}
|
||||
65
Sources/ManualDCore/Routes/ApiRoute.swift
Normal file
65
Sources/ManualDCore/Routes/ApiRoute.swift
Normal file
@@ -0,0 +1,65 @@
|
||||
import CasePathsCore
|
||||
import Foundation
|
||||
@preconcurrency import URLRouting
|
||||
|
||||
extension SiteRoute {
|
||||
/// Represents api routes.
|
||||
///
|
||||
/// The routes return json as opposed to view routes that return html.
|
||||
public enum Api: Sendable, Equatable {
|
||||
|
||||
case project(Self.ProjectRoute)
|
||||
|
||||
public static let rootPath = Path {
|
||||
"api"
|
||||
"v1"
|
||||
}
|
||||
|
||||
public static let router = OneOf {
|
||||
Route(.case(Self.project)) {
|
||||
rootPath
|
||||
ProjectRoute.router
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SiteRoute.Api {
|
||||
public enum ProjectRoute: Sendable, Equatable {
|
||||
case create(Project.Create)
|
||||
case delete(id: Project.ID)
|
||||
case get(id: Project.ID)
|
||||
case index
|
||||
|
||||
static let rootPath = "projects"
|
||||
|
||||
public static let router = OneOf {
|
||||
Route(.case(Self.create)) {
|
||||
Path { rootPath }
|
||||
Method.post
|
||||
Body(.json(Project.Create.self))
|
||||
}
|
||||
Route(.case(Self.delete(id:))) {
|
||||
Path {
|
||||
rootPath
|
||||
Project.ID.parser()
|
||||
}
|
||||
Method.delete
|
||||
}
|
||||
Route(.case(Self.get(id:))) {
|
||||
Path {
|
||||
rootPath
|
||||
Project.ID.parser()
|
||||
}
|
||||
Method.get
|
||||
}
|
||||
Route(.case(Self.index)) {
|
||||
Path { rootPath }
|
||||
Method.get
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
14
Sources/ManualDCore/Routes/SiteRoute.swift
Normal file
14
Sources/ManualDCore/Routes/SiteRoute.swift
Normal file
@@ -0,0 +1,14 @@
|
||||
import CasePathsCore
|
||||
import Foundation
|
||||
@preconcurrency import URLRouting
|
||||
|
||||
public enum SiteRoute: Equatable, Sendable {
|
||||
|
||||
case api(Self.Api)
|
||||
|
||||
public static let router = OneOf {
|
||||
Route(.case(Self.api)) {
|
||||
SiteRoute.Api.router
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Sources/ManualDCore/Routes/ViewRoute.swift
Normal file
12
Sources/ManualDCore/Routes/ViewRoute.swift
Normal file
@@ -0,0 +1,12 @@
|
||||
import CasePathsCore
|
||||
import Foundation
|
||||
@preconcurrency import URLRouting
|
||||
|
||||
extension SiteRoute {
|
||||
/// Represents view routes.
|
||||
///
|
||||
/// The routes return html.
|
||||
public enum View {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
// The Swift Programming Language
|
||||
// https://docs.swift.org/swift-book
|
||||
75
Tests/ApiRouteTests/ProjectRouteTests.swift
Normal file
75
Tests/ApiRouteTests/ProjectRouteTests.swift
Normal file
@@ -0,0 +1,75 @@
|
||||
import Dependencies
|
||||
import Foundation
|
||||
import ManualDCore
|
||||
import Testing
|
||||
import URLRouting
|
||||
|
||||
@Suite("ProjectRouteTests")
|
||||
struct ProjectRouteTests {
|
||||
let router = SiteRoute.Api.router
|
||||
|
||||
@Test
|
||||
func create() throws {
|
||||
let json = """
|
||||
{
|
||||
\"name\": \"Test\",
|
||||
\"streetAddress\": \"1234 Seasme Street\",
|
||||
\"city\": \"Nowhere\",
|
||||
\"state\": \"OH\",
|
||||
\"zipCode\": \"55555\"
|
||||
}
|
||||
"""
|
||||
var request = URLRequestData(
|
||||
method: "POST",
|
||||
path: "/api/v1/projects",
|
||||
body: .init(json.utf8)
|
||||
)
|
||||
let route = try router.parse(&request)
|
||||
#expect(
|
||||
route
|
||||
== .project(
|
||||
.create(
|
||||
.init(
|
||||
name: "Test",
|
||||
streetAddress: "1234 Seasme Street",
|
||||
city: "Nowhere",
|
||||
state: "OH",
|
||||
zipCode: "55555"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
func delete() throws {
|
||||
let id = UUID(0)
|
||||
var request = URLRequestData(
|
||||
method: "DELETE",
|
||||
path: "/api/v1/projects/\(id)"
|
||||
)
|
||||
let route = try router.parse(&request)
|
||||
#expect(route == .project(.delete(id: id)))
|
||||
}
|
||||
|
||||
@Test
|
||||
func get() throws {
|
||||
let id = UUID(0)
|
||||
var request = URLRequestData(
|
||||
method: "GET",
|
||||
path: "/api/v1/projects/\(id)"
|
||||
)
|
||||
let route = try router.parse(&request)
|
||||
#expect(route == .project(.get(id: id)))
|
||||
}
|
||||
|
||||
@Test
|
||||
func index() throws {
|
||||
var request = URLRequestData(
|
||||
method: "GET",
|
||||
path: "/api/v1/projects"
|
||||
)
|
||||
let route = try router.parse(&request)
|
||||
#expect(route == .project(.index))
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import Testing
|
||||
@testable import swift_manual_d
|
||||
|
||||
@Test func example() async throws {
|
||||
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
|
||||
}
|
||||
Reference in New Issue
Block a user