diff --git a/Package.resolved b/Package.resolved index a3a48a5..5c07f3d 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "62668360721c9ad13a7b961193242e083cbbf63a2bada2e8b1cc6bfeb4360fe6", + "originHash" : "dab5887e7f33b2dba9c9b86598c47d541464dda5c29e084c8d38dec82923c953", "pins" : [ { "identity" : "async-http-client", @@ -361,6 +361,15 @@ "version" : "0.2.1" } }, + { + "identity" : "vapor-routing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/vapor-routing.git", + "state" : { + "revision" : "ae1db2ec96fad88b00173a265313de2c447a9945", + "version" : "0.1.3" + } + }, { "identity" : "websocket-kit", "kind" : "remoteSourceControl", diff --git a/Package.swift b/Package.swift index c5cdd6a..10648e8 100644 --- a/Package.swift +++ b/Package.swift @@ -25,7 +25,8 @@ let package = Package( .package(url: "https://github.com/sliemeobn/elementary.git", from: "0.3.2"), .package(url: "https://github.com/sliemeobn/elementary-htmx.git", from: "0.4.0"), .package(url: "https://github.com/vapor-community/vapor-elementary.git", from: "0.1.0"), - .package(url: "https://github.com/pointfreeco/swift-url-routing.git", from: "0.6.2") + .package(url: "https://github.com/pointfreeco/swift-url-routing.git", from: "0.6.2"), + .package(url: "https://github.com/pointfreeco/vapor-routing.git", from: "0.1.3") ], targets: [ .executableTarget( @@ -41,7 +42,8 @@ let package = Package( .product(name: "DependenciesMacros", package: "swift-dependencies"), .product(name: "Elementary", package: "elementary"), .product(name: "VaporElementary", package: "vapor-elementary"), - .product(name: "ElementaryHTMX", package: "elementary-htmx") + .product(name: "ElementaryHTMX", package: "elementary-htmx"), + .product(name: "VaporRouting", package: "vapor-routing") ], swiftSettings: swiftSettings diff --git a/Sources/App/Controllers/ApiController.swift b/Sources/App/Controllers/ApiController.swift index bd49309..c27798c 100644 --- a/Sources/App/Controllers/ApiController.swift +++ b/Sources/App/Controllers/ApiController.swift @@ -1,4 +1,6 @@ +import Dependencies import Fluent +import SharedModels import Vapor struct ApiController: RouteCollection { diff --git a/Sources/App/Extensions/Request+extensions.swift b/Sources/App/Extensions/Request+extensions.swift index c196a7a..f6d2907 100644 --- a/Sources/App/Extensions/Request+extensions.swift +++ b/Sources/App/Extensions/Request+extensions.swift @@ -28,4 +28,16 @@ extension Request { let html = try await html() return HTMLResponse { html } } + + // Render the html if we're an htmx request, otherwise render the main page. + func render( + mainPage: (C) async throws -> D, + @HTMLBuilder html: () async throws -> C + ) async rethrows -> HTMLResponse where C: Sendable { + let html = try await html() + guard isHtmxRequest else { + return try await render { try await mainPage(html) } + } + return HTMLResponse { html } + } } diff --git a/Sources/App/routes.swift b/Sources/App/routes.swift index 2651cef..af16ff8 100644 --- a/Sources/App/routes.swift +++ b/Sources/App/routes.swift @@ -1,3 +1,4 @@ +import CasePathsCore import DatabaseClientLive import Dependencies import Elementary @@ -6,9 +7,12 @@ import Fluent import SharedModels import Vapor import VaporElementary +import VaporRouting func routes(_ app: Application) throws { - try app.register(collection: ApiController()) + // try app.register(collection: ApiController()) + app.mount(SiteRoute.router, use: siteHandler) + try app.register(collection: UserViewController()) try app.register(collection: VendorViewController()) try app.register(collection: EmployeeViewController()) @@ -35,10 +39,6 @@ func routes(_ app: Application) throws { } } - app.get("health") { _ in - HTTPStatus.ok - } - app.post("login") { req in @Dependency(\.database.users) var users let loginForm = try req.content.decode(User.Login.self) @@ -66,3 +66,362 @@ func routes(_ app: Application) throws { private struct LoginContext: Content { let next: String? } + +func siteHandler( + request: Request, + route: SiteRoute +) async throws -> any AsyncResponseEncodable { + switch route { + case let .api(route): + switch route { + case let .employee(route): + return try await route.handle(request: request) + case let .purchaseOrder(route): + return try await route.handle(request: request) + case let .user(route): + return try await route.handle(request: request) + case let .vendor(route): + return try await route.handle(request: request) + case let .vendorBranch(route): + return try await route.handle(request: request) + } + case .health: + return HTTPStatus.ok + case let .view(route): + switch route { + case let .employee(route): + return try await route.handle(request: request) + + case .login: + // TODO: Needs to have login context. + return await request.render { + MainPage(displayNav: false, route: .login) { + UserForm(context: .login(next: nil)) + } + } + + case .purchaseOrder: + fatalError() + + case .select: + fatalError() + + case let .user(route): + return try await route.handle(request: request) + + case let .vendor(route): + return try await route.handle(request: request) + } + } +} + +extension ApiRoute.EmployeeApiRoute { + func handle(request: Request) async throws -> any AsyncResponseEncodable { + @Dependency(\.database) var database + switch self { + case let .create(employee): + return try await database.employees.create(employee) + case let .delete(id: id): + try await database.employees.delete(id) + return HTTPStatus.ok + case .index: + return try await database.employees.fetchAll() + case let .get(id: id): + guard let employee = try await database.employees.get(id) else { + throw Abort(.badRequest, reason: "Employee not found") + } + return employee + case let .update(id: id, updates: updates): + return try await database.employees.update(id, updates) + } + } +} + +extension ApiRoute.PurchaseOrderApiRoute { + func handle(request: Request) async throws -> any AsyncResponseEncodable { + @Dependency(\.database.purchaseOrders) var purchaseOrders + switch self { + case .index: + return try await purchaseOrders.fetchAll() + case let .create(purchaseOrder): + return try await purchaseOrders.create(purchaseOrder) + case let .delete(id: id): + try await purchaseOrders.delete(id) + return HTTPStatus.ok + case let .get(id: id): + guard let output = try await purchaseOrders.get(id) else { + throw Abort(.badRequest, reason: "Purchase order not found.") + } + return output + case let .page(page: page, limit: limit): + return try await purchaseOrders.fetchPage(.init(page: page, per: limit)) + } + } +} + +extension ApiRoute.UserApiRoute { + func handle(request: Request) async throws -> any AsyncResponseEncodable { + @Dependency(\.database.users) var users + switch self { + case let .create(user): + return try await users.create(user) + case let .delete(id: id): + try await users.delete(id) + return HTTPStatus.ok + case .index: + return try await users.fetchAll() + case let .get(id: id): + guard let user = try await users.get(id) else { + throw Abort(.badRequest, reason: "Employee not found") + } + return user + case let .login(user): + return try await users.login(user) + case let .update(id: id, updates: updates): + return try await users.update(id, updates) + } + } +} + +extension ApiRoute.VendorApiRoute { + func handle(request: Request) async throws -> any AsyncResponseEncodable { + @Dependency(\.database.vendors) var vendors + switch self { + case let .create(vendor): + return try await vendors.create(vendor) + case let .delete(id: id): + try await vendors.delete(id) + return HTTPStatus.ok + case let .index(withBranches: withBranches): + guard withBranches == true else { + return try await vendors.fetchAll() + } + return try await vendors.fetchAll(.withBranches) + case let .get(id: id): + guard let vendor = try await vendors.get(id) else { + throw Abort(.badRequest, reason: "Employee not found") + } + return vendor + case let .update(id: id, updates: updates): + return try await vendors.update(id, with: updates) + } + } +} + +extension ApiRoute.VendorBranchApiRoute { + func handle(request: Request) async throws -> any AsyncResponseEncodable { + @Dependency(\.database.vendorBranches) var vendorBranches + switch self { + case let .create(branch): + return try await vendorBranches.create(branch) + case let .delete(id: id): + try await vendorBranches.delete(id) + return HTTPStatus.ok + case let .index(for: optionalVendorID): + guard let vendorID = optionalVendorID else { + return try await vendorBranches.fetchAll() + } + return try await vendorBranches.fetchAll(.for(vendorID: vendorID)) + case let .get(id: id): + guard let branch = try await vendorBranches.get(id) else { + throw Abort(.badRequest, reason: "Employee not found") + } + return branch + case let .update(id: id, updates: updates): + return try await vendorBranches.update(id, updates) + } + } +} + +extension SharedModels.ViewRoute.EmployeeRoute { + + private func mainPage( + _ html: C + ) async throws -> some SendableHTMLDocument where C: Sendable { + @Dependency(\.database) var database + let employees = try await database.employees.fetchAll() + return MainPage(displayNav: true, route: .employees) { + div(.class("container")) { + html + EmployeeTable(employees: employees) + } + } + } + + func handle(request: Request) async throws -> any AsyncResponseEncodable { + @Dependency(\.database.employees) var employees + + switch self { + case .form: + return try await request.render(mainPage: mainPage) { + EmployeeForm(shouldShow: true) + } + case let .shared(route): + switch route { + case .index: + return try await request.render { try await mainPage(EmployeeForm()) } + + case let .get(id: id): + guard let employee = try await employees.get(id) else { + throw Abort(.badRequest, reason: "Employee id not found.") + } + return try await request.render(mainPage: mainPage) { + EmployeeForm(employee: employee) + } + + case let .create(employee): + return try await request.render { + try await EmployeeTable.Row(employee: employees.create(employee)) + } + + case let .delete(id: id): + try await employees.delete(id) + return HTTPStatus.ok + + case let .update(id: id, updates: updates): + return try await request.render { + try await EmployeeTable.Row(employee: employees.update(id, updates)) + } + } + } + } + +} + +extension SharedModels.ViewRoute.UserRoute { + + private func mainPage(_ html: C) async throws -> some SendableHTMLDocument where C: Sendable { + @Dependency(\.database) var database + let users = try await database.users.fetchAll() + return MainPage(displayNav: true, route: .users) { + div(.class("container")) { + html + UserTable(users: users) + } + } + } + + func handle(request: Request) async throws -> any AsyncResponseEncodable { + @Dependency(\.database.users) var users + switch self { + case .form: + return try await request.render(mainPage: mainPage) { + UserForm(context: .create) + } + case let .shared(route): + switch route { + case let .create(user): + return try await request.render { + try await UserTable.Row(user: users.create(user)) + } + + case let .delete(id: id): + try await users.delete(id) + return HTTPStatus.ok + + case .index: + return try await request.render { + try await mainPage(UserDetail(user: nil)) + } + + case let .get(id: id): + guard let user = try await users.get(id) else { + throw Abort(.badRequest, reason: "User not found.") + } + return try await request.render(mainPage: mainPage) { + UserDetail(user: user) + } + + case let .login(login): + let token = try await users.login(login) + let user = try await users.get(token.userID)! + request.session.authenticate(user) + return await request.render { + MainPage(displayNav: true, route: .purchaseOrders) { + div( + .hx.get("/purchase-orders"), + .hx.pushURL(true), + .hx.target("body"), + .hx.trigger(.event(.revealed)), + .hx.indicator(".hx-indicator") + ) { + Img.spinner().attributes(.class("hx-indicator")) + } + } + } + + case let .update(id: id, updates: updates): + return try await request.render { + try await UserTable.Row(user: users.update(id, updates)) + } + } + } + } + +} + +extension SharedModels.ViewRoute.VendorRoute { + private func mainPage(_ html: C) async throws -> some SendableHTMLDocument where C: Sendable { + @Dependency(\.database) var database + let vendors = try await database.vendors.fetchAll(.withBranches) + return MainPage(displayNav: true, route: .vendors) { + div(.class("container"), .id("content")) { + html + VendorTable(vendors: vendors) + } + } + } + + func handle(request: Request) async throws -> any AsyncResponseEncodable { + @Dependency(\.database) var database + + switch self { + case .form: + return try await request.render(mainPage: mainPage) { + VendorForm(.float(shouldShow: true)) + } + + case let .createBranch(branch): + return try await request.render { + try await VendorDetail.BranchRow(branch: database.vendorBranches.create(branch)) + } + + case let .shared(route): + switch route { + case let .create(vendor): + let vendor = try await database.vendors.create(vendor) + return await request.render { + div(.class("container"), .id("content")) { + VendorDetail(vendor: vendor) + try await VendorTable(vendors: database.vendors.fetchAll(.withBranches)) + } + } + + case let .delete(id: id): + try await database.vendors.delete(id) + return HTTPStatus.ok + + case let .get(id: id): + guard let vendor = try await database.vendors.get(id, .withBranches) else { + throw Abort(.badRequest, reason: "Vendor not found.") + } + return try await request.render(mainPage: mainPage) { + VendorDetail(vendor: vendor) + } + + case .index: + return try await request.render { + try await mainPage(VendorForm()) + } + + case let .update(id: id, updates: updates): + return try await request.render { + try await VendorDetail( + vendor: database.vendors.update(id, with: updates, returnWithBranches: true) + ) + } + } + } + } + +} diff --git a/Sources/DatabaseClient/Interface.swift b/Sources/DatabaseClient/Interface.swift index ea25512..eec135d 100644 --- a/Sources/DatabaseClient/Interface.swift +++ b/Sources/DatabaseClient/Interface.swift @@ -1,6 +1,8 @@ import Dependencies import DependenciesMacros import FluentKit +import SharedModels +import Vapor public extension DependencyValues { var database: DatabaseClient { diff --git a/Sources/SharedModels/AppRoute.swift b/Sources/SharedModels/SiteRoute.swift similarity index 69% rename from Sources/SharedModels/AppRoute.swift rename to Sources/SharedModels/SiteRoute.swift index b5e43b7..d7acb11 100644 --- a/Sources/SharedModels/AppRoute.swift +++ b/Sources/SharedModels/SiteRoute.swift @@ -2,217 +2,18 @@ import CasePathsCore import Foundation @preconcurrency import URLRouting -// TODO: Share view and api routes. - -public enum ViewRoute: Sendable { - case employee(EmployeeRoute) - case purchaseOrder(PurchaseOrderRoute) - case select(SelectRoute) - case user(UserRoute) - case vendor(VendorRoute) +public enum SiteRoute: Sendable { + case api(ApiRoute) + case health + case view(ViewRoute) public static let router = OneOf { - Route(.case(Self.employee)) { EmployeeRoute.router } - Route(.case(Self.purchaseOrder)) { PurchaseOrderRoute.router } - Route(.case(Self.select)) { SelectRoute.router } - Route(.case(Self.user)) { UserRoute.router } - Route(.case(Self.vendor)) { VendorRoute.router } - } - - public enum EmployeeRoute: Sendable { - case create(Employee.Create) - case delete(id: Employee.ID) - case get(id: Employee.ID) - case form - case index - case update(id: Employee.ID, updates: Employee.Update) - - static let rootPath = "employees" - - public static let router = OneOf { - Route(.case(Self.create)) { - Path { rootPath } - Method.post - Body(.json(Employee.Create.self)) - } - Route(.case(Self.index)) { - Path { rootPath } - Method.get - } - Route(.case(Self.delete(id:))) { - Path { rootPath; UUID.parser() } - Method.delete - } - Route(.case(Self.get(id:))) { - Path { rootPath; UUID.parser() } - Method.get - } - Route(.case(Self.form)) { - Path { rootPath; "create" } - Method.get - } - Route(.case(Self.update(id:updates:))) { - Path { rootPath; UUID.parser() } - Method.put - Body(.json(Employee.Update.self)) - } - } - } - - // TODO: Add search. - public enum PurchaseOrderRoute: Sendable { - case create(PurchaseOrder.Create) - case form - case get(id: PurchaseOrder.ID) - case index - case next(page: Int, limit: Int) - - static let rootPath = "purchase-orders" - - public static let router = OneOf { - Route(.case(Self.create)) { - Path { rootPath } - Method.post - Body(.json(PurchaseOrder.Create.self)) - } - Route(.case(Self.form)) { - Path { rootPath; "create" } - Method.get - } - Route(.case(Self.get(id:))) { - Path { rootPath; Digits() } - Method.get - } - Route(.case(Self.index)) { - Path { rootPath } - Method.get - } - Route(.case(Self.next(page:limit:))) { - Path { rootPath; "next" } - Method.get - Query { - Field("page", default: 1) { Digits() } - Field("limit", default: 25) { Digits() } - } - } - } - } - - public enum SelectRoute: Sendable { - case employee(context: Context) - case vendorBranches(context: Context) - - public enum Context: String, Codable, Sendable, CaseIterable { - case purchaseOrderForm - case purchaseOrderSearch - } - - static let rootPath = "select" - - public static let router = OneOf { - Route(.case(Self.employee(context:))) { - Path { rootPath; "employee" } - Method.get - Query { - Field("context") { Context.parser() } - } - } - Route(.case(Self.vendorBranches(context:))) { - Path { rootPath; "vendor-branches" } - Method.get - Query { - Field("context") { Context.parser() } - } - } - } - } - - public enum UserRoute: Sendable { - case create(User.Create) - case delete(id: User.ID) - case form - case get(id: User.ID) - case index - case login(User.Login) - case update(id: User.ID, updates: User.Update) - - static let rootPath = "users" - - public static let router = OneOf { - Route(.case(Self.create)) { - Path { rootPath } - Method.post - Body(.json(User.Create.self)) - } - Route(.case(Self.delete(id:))) { - Path { rootPath; User.ID.parser() } - Method.delete - } - Route(.case(Self.form)) { - Path { rootPath; "create" } - Method.get - } - Route(.case(Self.get(id:))) { - Path { rootPath; User.ID.parser() } - Method.get - } - Route(.case(Self.index)) { - Path { rootPath } - Method.get - } - Route(.case(Self.login)) { - Path { rootPath } - Method.post - Body(.json(User.Login.self)) - } - Route(.case(Self.update(id:updates:))) { - Path { rootPath; User.ID.parser() } - // TODO: Use put or patch. - Method.post - Body(.json(User.Update.self)) - } - } - } - - public enum VendorRoute: Sendable { - case create(Vendor.Create) - case createBranch(VendorBranch.Create) - case form - case get(id: Vendor.ID) - case index - case update(id: Vendor.ID, updates: Vendor.Update) - - static let rootPath = "vendors" - - public static let router = OneOf { - Route(.case(Self.create)) { - Path { rootPath } - Method.post - Body(.json(Vendor.Create.self)) - } - Route(.case(Self.createBranch)) { - Path { rootPath; "branches" } - Method.post - Body(.json(VendorBranch.Create.self)) - } - Route(.case(Self.form)) { - Path { rootPath; "create" } - Method.get - } - Route(.case(Self.get(id:))) { - Path { rootPath; Vendor.ID.parser() } - Method.get - } - Route(.case(Self.index)) { - Path { rootPath } - Method.get - } - Route(.case(Self.update(id:updates:))) { - Path { rootPath; Vendor.ID.parser() } - Method.put - Body(.json(Vendor.Update.self)) - } + Route(.case(Self.view)) { ViewRoute.router } + Route(.case(Self.health)) { + Path { "health" } + Method.get } + Route(.case(Self.api)) { ApiRoute.router } } } @@ -224,12 +25,29 @@ public enum ApiRoute: Sendable { case vendor(VendorApiRoute) case vendorBranch(VendorBranchApiRoute) + static let rootPath = Path { "api"; "v1" } + public static let router = OneOf { - Route(.case(Self.employee)) { EmployeeApiRoute.router } - Route(.case(Self.purchaseOrder)) { PurchaseOrderApiRoute.router } - Route(.case(Self.user)) { UserApiRoute.router } - Route(.case(Self.vendor)) { VendorApiRoute.router } - Route(.case(Self.vendorBranch)) { VendorBranchApiRoute.router } + Route(.case(Self.employee)) { + rootPath + EmployeeApiRoute.router + } + Route(.case(Self.purchaseOrder)) { + rootPath + PurchaseOrderApiRoute.router + } + Route(.case(Self.user)) { + rootPath + UserApiRoute.router + } + Route(.case(Self.vendor)) { + rootPath + VendorApiRoute.router + } + Route(.case(Self.vendorBranch)) { + rootPath + VendorBranchApiRoute.router + } } public enum EmployeeApiRoute: Sendable { @@ -305,6 +123,7 @@ public enum ApiRoute: Sendable { } } + // TODO: Add logout. public enum UserApiRoute: Sendable { case create(User.Create) case delete(id: User.ID) @@ -349,8 +168,9 @@ public enum ApiRoute: Sendable { public enum VendorApiRoute: Sendable { case create(Vendor.Create) + case delete(id: Vendor.ID) case get(id: Vendor.ID) - case index + case index(withBranches: Bool?) case update(id: Vendor.ID, updates: Vendor.Update) static let rootPath = "vendors" @@ -361,13 +181,22 @@ public enum ApiRoute: Sendable { Method.post Body(.json(Vendor.Create.self)) } + Route(.case(Self.delete(id:))) { + Path { rootPath; Vendor.ID.parser() } + Method.delete + } Route(.case(Self.get(id:))) { Path { rootPath; Vendor.ID.parser() } Method.get } - Route(.case(Self.index)) { + Route(.case(Self.index(withBranches:))) { Path { rootPath } Method.get + Query { + Field("branches", default: nil) { + Optionally { Bool.parser() } + } + } } Route(.case(Self.update(id:updates:))) { Path { rootPath; Vendor.ID.parser() } @@ -381,6 +210,7 @@ public enum ApiRoute: Sendable { case create(VendorBranch.Create) case delete(id: VendorBranch.ID) case get(id: VendorBranch.ID) + case index(for: Vendor.ID? = nil) case update(id: VendorBranch.ID, updates: VendorBranch.Update) public static let router = OneOf { @@ -397,6 +227,13 @@ public enum ApiRoute: Sendable { Path { "vendors"; "branches"; VendorBranch.ID.parser() } Method.get } + Route(.case(Self.index(for:))) { + Path { "vendors"; "branches" } + Method.get + Query { + Field("vendorID", default: nil) { Optionally { VendorBranch.ID.parser() } } + } + } Route(.case(Self.update(id:updates:))) { Path { "vendors"; "branches"; VendorBranch.ID.parser() } Method.put @@ -405,3 +242,181 @@ public enum ApiRoute: Sendable { } } } + +public enum ViewRoute: Sendable { + + case employee(EmployeeRoute) + case login + case purchaseOrder(PurchaseOrderRoute) + case select(SelectRoute) + case user(UserRoute) + case vendor(VendorRoute) + + public static let router = OneOf { + Route(.case(Self.employee)) { EmployeeRoute.router } + Route(.case(Self.login)) { + Path { "login" } + Method.get + } + Route(.case(Self.purchaseOrder)) { PurchaseOrderRoute.router } + Route(.case(Self.select)) { SelectRoute.router } + Route(.case(Self.user)) { UserRoute.router } + Route(.case(Self.vendor)) { VendorRoute.router } + } + + public enum EmployeeRoute: Sendable { + case form + case shared(ApiRoute.EmployeeApiRoute) + + static let rootPath = "employees" + + public static let router = OneOf { + Route(.case(Self.form)) { + Path { rootPath; "create" } + Method.get + } + Route(.case(Self.shared)) { + ApiRoute.EmployeeApiRoute.router + } + } + } + + public enum PurchaseOrderRoute: Sendable { + case form + case search(Search) + case shared(ApiRoute.PurchaseOrderApiRoute) + + static let rootPath = "purchase-orders" + + public static let router = OneOf { + Route(.case(Self.form)) { + Path { rootPath; "create" } + Method.get + } + Route(.case(Self.search)) { + Search.router + } + Route(.case(Self.shared)) { + ApiRoute.PurchaseOrderApiRoute.router + } + } + + public enum Search: Sendable { + case index(context: Context, table: Bool?) + case search(Request) + + static let rootPath = Path { "purchase-orders"; "search" } + + static let router = OneOf { + Route(.case(Search.index(context:table:))) { + rootPath + Method.get + Query { + Field("context", default: .employee) { Search.Context.parser() } + Field("table", default: nil) { Optionally { Bool.parser() } } + } + } + Route(.case(Search.search)) { + rootPath + Method.post + Body(.json(Search.Request.self)) + } + } + + public enum Context: String, Codable, CaseIterable, Sendable { + case employee + case customer + case vendor + } + + public struct Request: Codable, Sendable { + public let context: Context + public let createdForID: Employee.ID? + public let customerSearch: String? + public let vendorBranchID: VendorBranch.ID? + + public init( + context: Context, + createdForID: Employee.ID? = nil, + customerSearch: String? = nil, + vendorBranchID: VendorBranch.ID? = nil + ) { + self.context = context + self.createdForID = createdForID + self.customerSearch = customerSearch + self.vendorBranchID = vendorBranchID + } + } + + } + } + + public enum SelectRoute: Sendable { + case employee(context: Context) + case vendorBranches(context: Context) + + public enum Context: String, Codable, Sendable, CaseIterable { + case purchaseOrderForm + case purchaseOrderSearch + } + + static let rootPath = "select" + + public static let router = OneOf { + Route(.case(Self.employee(context:))) { + Path { rootPath; "employee" } + Method.get + Query { + Field("context") { Context.parser() } + } + } + Route(.case(Self.vendorBranches(context:))) { + Path { rootPath; "vendor-branches" } + Method.get + Query { + Field("context") { Context.parser() } + } + } + } + } + + public enum UserRoute: Sendable { + case form + case shared(ApiRoute.UserApiRoute) + + static let rootPath = "users" + + public static let router = OneOf { + Route(.case(Self.form)) { + Path { rootPath; "create" } + Method.get + } + Route(.case(Self.shared)) { + ApiRoute.UserApiRoute.router + } + } + } + + public enum VendorRoute: Sendable { + case createBranch(VendorBranch.Create) + case form + case shared(ApiRoute.VendorApiRoute) + + static let rootPath = "vendors" + + public static let router = OneOf { + Route(.case(Self.createBranch)) { + Path { rootPath; "branches" } + Method.post + Body(.json(VendorBranch.Create.self)) + } + Route(.case(Self.form)) { + Path { rootPath; "create" } + Method.get + } + Route(.case(Self.shared)) { + ApiRoute.VendorApiRoute.router + } + } + } +} diff --git a/Tests/DatabaseClientTests/DatabaseClientTests.swift b/Tests/DatabaseClientTests/DatabaseClientTests.swift index 45b05dd..1733a15 100644 --- a/Tests/DatabaseClientTests/DatabaseClientTests.swift +++ b/Tests/DatabaseClientTests/DatabaseClientTests.swift @@ -19,8 +19,8 @@ struct DatabaseClientTests { @Test func testPath() { - let path = AppRoute.ViewRoute.router.path(for: .employee(.index)) - #expect(path == "/employees") + let path = ApiRoute.router.path(for: .employee(.index)) + #expect(path == "/api/v1/employees") } @Test