feat: Refactoring route declarations.

This commit is contained in:
2025-01-26 01:16:59 -05:00
parent 0fad024350
commit 4dc928e1f4
34 changed files with 592 additions and 606 deletions

View File

@@ -14,15 +14,15 @@ public extension DependencyValues {
public struct ApiController: Sendable {
public var json: @Sendable (Request) async throws -> (any Encodable)?
public func json(_ route: ApiRoute, logger: Logger) async throws -> (any Encodable)? {
public func json(_ route: SiteRoute.Api, logger: Logger) async throws -> (any Encodable)? {
try await json(.init(route, logger: logger))
}
public struct Request: Sendable {
public let route: ApiRoute
public let route: SiteRoute.Api
public let logger: Logger
public init(_ route: ApiRoute, logger: Logger) {
public init(_ route: SiteRoute.Api, logger: Logger) {
self.route = route
self.logger = logger
}

View File

@@ -41,7 +41,7 @@ private struct TokenResponse: Encodable {
}
private extension ApiRoute.EmployeeRoute {
private extension SiteRoute.Api.EmployeeRoute {
func handleApiRequest(logger: Logger) async throws -> (any Encodable)? {
@Dependency(\.database) var database
@@ -66,7 +66,7 @@ private extension ApiRoute.EmployeeRoute {
}
}
private extension ApiRoute.PurchaseOrderRoute {
private extension SiteRoute.Api.PurchaseOrderRoute {
func handleApiRequest(logger: Logger) async throws -> (any Encodable)? {
@Dependency(\.database.purchaseOrders) var purchaseOrders
@@ -90,8 +90,7 @@ private extension ApiRoute.PurchaseOrderRoute {
}
}
// TODO: Add Login.
private extension ApiRoute.UserRoute {
private extension SiteRoute.Api.UserRoute {
func handleApiRequest(logger: Logger) async throws -> (any Encodable)? {
@Dependency(\.database.users) var users
@@ -117,7 +116,7 @@ private extension ApiRoute.UserRoute {
}
}
private extension ApiRoute.VendorRoute {
private extension SiteRoute.Api.VendorRoute {
func handleApiRequest(logger: Logger) async throws -> (any Encodable)? {
@Dependency(\.database.vendors) var vendors
switch self {
@@ -143,7 +142,7 @@ private extension ApiRoute.VendorRoute {
}
}
private extension ApiRoute.VendorBranchRoute {
private extension SiteRoute.Api.VendorBranchRoute {
func handleApiRequest(logger: Logger) async throws -> (any Encodable)? {
@Dependency(\.database.vendorBranches) var vendorBranches
switch self {

View File

@@ -4,7 +4,7 @@ import Vapor
extension ApiController {
func respond(_ route: ApiRoute, request: Vapor.Request) async throws -> any AsyncResponseEncodable {
func respond(_ route: SiteRoute.Api, request: Vapor.Request) async throws -> any AsyncResponseEncodable {
guard let encodable = try await json(route, logger: request.logger) else {
return HTTPStatus.ok
}

View File

@@ -5,7 +5,7 @@ import VaporElementary
import ViewController
extension ViewController {
func respond(route: ViewRoute, request: Vapor.Request) async throws -> any AsyncResponseEncodable {
func respond(route: SiteRoute.View, request: Vapor.Request) async throws -> any AsyncResponseEncodable {
let html = try await view(
for: route,
isHtmxRequest: request.isHtmxRequest,

View File

@@ -9,7 +9,7 @@ private let apiMiddleware: [any Middleware] = [
User.guardMiddleware()
]
extension ApiRoute {
extension SiteRoute.Api {
var middleware: [any Middleware]? {
switch self {
case .login: return nil

View File

@@ -12,7 +12,7 @@ private let viewProtectedMiddleware: [any Middleware] = [
}
]
extension SharedModels.ViewRoute {
extension SiteRoute.View {
var middleware: [any Middleware]? {
switch self {

View File

@@ -73,7 +73,7 @@ private func setupDatabase(
private func addRoutes(to app: Application) {
// Redirect the index path to purchase order route.
app.get { req in
req.redirect(to: ViewRoute.router.path(for: .purchaseOrder(.index)))
req.redirect(to: SiteRoute.View.router.path(for: .purchaseOrder(.index)))
}
app.mount(

View File

@@ -2,227 +2,230 @@ import CasePaths
import Foundation
@preconcurrency import URLRouting
public enum ApiRoute: Sendable, Equatable {
public extension SiteRoute {
case employee(EmployeeRoute)
case login(User.Login)
case purchaseOrder(PurchaseOrderRoute)
case user(UserRoute)
case vendor(VendorRoute)
case vendorBranch(VendorBranchRoute)
enum Api: Sendable, Equatable {
static let rootPath = Path { "api"; "v1" }
case employee(EmployeeRoute)
case login(User.Login)
case purchaseOrder(PurchaseOrderRoute)
case user(UserRoute)
case vendor(VendorRoute)
case vendorBranch(VendorBranchRoute)
public static let router = OneOf {
Route(.case(Self.employee)) {
rootPath
EmployeeRoute.router
}
Route(.case(Self.login)) {
Path { "api"; "v1"; "login" }
Method.post
Body(.json(User.Login.self))
}
Route(.case(Self.purchaseOrder)) {
rootPath
PurchaseOrderRoute.router
}
Route(.case(Self.user)) {
rootPath
UserRoute.router
}
Route(.case(Self.vendor)) {
rootPath
VendorRoute.router
}
Route(.case(Self.vendorBranch)) {
rootPath
VendorBranchRoute.router
}
}
public enum EmployeeRoute: Sendable, Equatable {
case create(Employee.Create)
case delete(id: Employee.ID)
case get(id: Employee.ID)
case index
case update(id: Employee.ID, updates: Employee.Update)
static let rootPath = "employees"
static let rootPath = Path { "api"; "v1" }
public static let router = OneOf {
Route(.case(Self.create)) {
Path { rootPath }
Route(.case(Self.employee)) {
rootPath
EmployeeRoute.router
}
Route(.case(Self.login)) {
Path { "api"; "v1"; "login" }
Method.post
Body(.json(Employee.Create.self))
Body(.json(User.Login.self))
}
Route(.case(Self.delete(id:))) {
Path { rootPath; Employee.ID.parser() }
Method.delete
Route(.case(Self.purchaseOrder)) {
rootPath
PurchaseOrderRoute.router
}
Route(.case(Self.index)) {
Path { rootPath }
Method.get
Route(.case(Self.user)) {
rootPath
UserRoute.router
}
Route(.case(Self.get(id:))) {
Path { rootPath; Employee.ID.parser() }
Method.get
Route(.case(Self.vendor)) {
rootPath
VendorRoute.router
}
Route(.case(Self.update(id:updates:))) {
Path { rootPath; Employee.ID.parser() }
Method.put
Body(.json(Employee.Update.self))
Route(.case(Self.vendorBranch)) {
rootPath
VendorBranchRoute.router
}
}
}
public enum PurchaseOrderRoute: Sendable, Equatable {
case create(PurchaseOrder.Create)
case delete(id: PurchaseOrder.ID)
case get(id: PurchaseOrder.ID)
case index
case page(page: Int, limit: Int)
public enum EmployeeRoute: Sendable, Equatable {
case create(Employee.Create)
case delete(id: Employee.ID)
case get(id: Employee.ID)
case index
case update(id: Employee.ID, updates: Employee.Update)
static let rootPath = "purchase-orders"
static let rootPath = "employees"
public static let router = OneOf {
Route(.case(Self.create)) {
Path { rootPath }
Method.post
Body(.json(PurchaseOrder.Create.self))
}
Route(.case(Self.delete(id:))) {
Path { rootPath; PurchaseOrder.ID.parser() }
Method.delete
}
Route(.case(Self.get(id:))) {
Path { rootPath; Digits() }
Method.get
}
Route(.case(Self.index)) {
Path { rootPath }
Method.get
}
Route(.case(Self.page(page:limit:))) {
Path { rootPath; "next" }
Method.get
Query {
Field("page", default: 1) { Digits() }
Field("limit", default: 25) { Digits() }
public static let router = OneOf {
Route(.case(Self.create)) {
Path { rootPath }
Method.post
Body(.json(Employee.Create.self))
}
Route(.case(Self.delete(id:))) {
Path { rootPath; Employee.ID.parser() }
Method.delete
}
Route(.case(Self.index)) {
Path { rootPath }
Method.get
}
Route(.case(Self.get(id:))) {
Path { rootPath; Employee.ID.parser() }
Method.get
}
Route(.case(Self.update(id:updates:))) {
Path { rootPath; Employee.ID.parser() }
Method.put
Body(.json(Employee.Update.self))
}
}
}
}
public enum UserRoute: Sendable, Equatable {
case delete(id: User.ID)
case create(User.Create)
case get(id: User.ID)
case index
case update(id: User.ID, updates: User.Update)
public enum PurchaseOrderRoute: Sendable, Equatable {
case create(PurchaseOrder.Create)
case delete(id: PurchaseOrder.ID)
case get(id: PurchaseOrder.ID)
case index
case page(page: Int, limit: Int)
static let rootPath = "users"
static let rootPath = "purchase-orders"
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.get(id:))) {
Path { rootPath; User.ID.parser() }
Method.get
}
Route(.case(Self.index)) {
Path { rootPath }
Method.get
}
Route(.case(Self.update(id:updates:))) {
Path { rootPath; User.ID.parser() }
Method.patch
Body(.json(User.Update.self))
}
}
}
public enum VendorRoute: Sendable, Equatable {
case delete(id: Vendor.ID)
case index(withBranches: Bool? = nil)
case create(Vendor.Create)
case get(id: Vendor.ID)
case update(id: Vendor.ID, updates: Vendor.Update)
static let rootPath = "vendors"
public static let router = OneOf {
Route(.case(Self.delete(id:))) {
Path { rootPath; Vendor.ID.parser() }
Method.delete
}
Route(.case(Self.create)) {
Path { rootPath }
Method.post
Body(.json(Vendor.Create.self))
}
Route(.case(Self.get(id:))) {
Path { rootPath; Vendor.ID.parser() }
Method.get
}
Route(.case(Self.index(withBranches:))) {
Path { rootPath }
Method.get
Query {
Optionally {
Field("branches", default: nil) {
Bool.parser()
}
public static let router = OneOf {
Route(.case(Self.create)) {
Path { rootPath }
Method.post
Body(.json(PurchaseOrder.Create.self))
}
Route(.case(Self.delete(id:))) {
Path { rootPath; PurchaseOrder.ID.parser() }
Method.delete
}
Route(.case(Self.get(id:))) {
Path { rootPath; Digits() }
Method.get
}
Route(.case(Self.index)) {
Path { rootPath }
Method.get
}
Route(.case(Self.page(page:limit:))) {
Path { rootPath; "next" }
Method.get
Query {
Field("page", default: 1) { Digits() }
Field("limit", default: 25) { Digits() }
}
}
}
Route(.case(Self.update(id:updates:))) {
Path { rootPath; Vendor.ID.parser() }
Method.put
Body(.json(Vendor.Update.self))
}
}
}
public enum VendorBranchRoute: Sendable, Equatable {
case delete(id: VendorBranch.ID)
case create(VendorBranch.Create)
case get(id: VendorBranch.ID)
case index(for: Vendor.ID? = nil)
case update(id: VendorBranch.ID, updates: VendorBranch.Update)
public enum UserRoute: Sendable, Equatable {
case delete(id: User.ID)
case create(User.Create)
case get(id: User.ID)
case index
case update(id: User.ID, updates: User.Update)
public static let router = OneOf {
Route(.case(Self.create)) {
Path { "vendors"; "branches" }
Method.post
Body(.json(VendorBranch.Create.self))
}
Route(.case(Self.delete(id:))) {
Path { "vendors"; "branches"; VendorBranch.ID.parser() }
Method.delete
}
Route(.case(Self.get(id:))) {
Path { "vendors"; "branches"; VendorBranch.ID.parser() }
Method.get
}
Route(.case(Self.index(for:))) {
Path { "vendors"; "branches" }
Method.get
Query {
Optionally { Field("vendorID", default: nil) { VendorBranch.ID.parser() } }
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.get(id:))) {
Path { rootPath; User.ID.parser() }
Method.get
}
Route(.case(Self.index)) {
Path { rootPath }
Method.get
}
Route(.case(Self.update(id:updates:))) {
Path { rootPath; User.ID.parser() }
Method.patch
Body(.json(User.Update.self))
}
}
Route(.case(Self.update(id:updates:))) {
Path { "vendors"; "branches"; VendorBranch.ID.parser() }
Method.put
Body(.json(VendorBranch.Update.self))
}
public enum VendorRoute: Sendable, Equatable {
case delete(id: Vendor.ID)
case index(withBranches: Bool? = nil)
case create(Vendor.Create)
case get(id: Vendor.ID)
case update(id: Vendor.ID, updates: Vendor.Update)
static let rootPath = "vendors"
public static let router = OneOf {
Route(.case(Self.delete(id:))) {
Path { rootPath; Vendor.ID.parser() }
Method.delete
}
Route(.case(Self.create)) {
Path { rootPath }
Method.post
Body(.json(Vendor.Create.self))
}
Route(.case(Self.get(id:))) {
Path { rootPath; Vendor.ID.parser() }
Method.get
}
Route(.case(Self.index(withBranches:))) {
Path { rootPath }
Method.get
Query {
Optionally {
Field("branches", default: nil) {
Bool.parser()
}
}
}
}
Route(.case(Self.update(id:updates:))) {
Path { rootPath; Vendor.ID.parser() }
Method.put
Body(.json(Vendor.Update.self))
}
}
}
public enum VendorBranchRoute: Sendable, Equatable {
case delete(id: VendorBranch.ID)
case create(VendorBranch.Create)
case get(id: VendorBranch.ID)
case index(for: Vendor.ID? = nil)
case update(id: VendorBranch.ID, updates: VendorBranch.Update)
public static let router = OneOf {
Route(.case(Self.create)) {
Path { "vendors"; "branches" }
Method.post
Body(.json(VendorBranch.Create.self))
}
Route(.case(Self.delete(id:))) {
Path { "vendors"; "branches"; VendorBranch.ID.parser() }
Method.delete
}
Route(.case(Self.get(id:))) {
Path { "vendors"; "branches"; VendorBranch.ID.parser() }
Method.get
}
Route(.case(Self.index(for:))) {
Path { "vendors"; "branches" }
Method.get
Query {
Optionally { Field("vendorID", default: nil) { VendorBranch.ID.parser() } }
}
}
Route(.case(Self.update(id:updates:))) {
Path { "vendors"; "branches"; VendorBranch.ID.parser() }
Method.put
Body(.json(VendorBranch.Update.self))
}
}
}
}

View File

@@ -3,16 +3,16 @@ import Foundation
@preconcurrency import URLRouting
public enum SiteRoute: Sendable {
case api(ApiRoute)
case api(SiteRoute.Api)
case health
case view(ViewRoute)
case view(SiteRoute.View)
public static let router = OneOf {
Route(.case(Self.view)) { ViewRoute.router }
Route(.case(Self.view)) { SiteRoute.View.router }
Route(.case(Self.health)) {
Path { "health" }
Method.get
}
Route(.case(Self.api)) { ApiRoute.router }
Route(.case(Self.api)) { SiteRoute.Api.router }
}
}

View File

@@ -2,393 +2,375 @@ import CasePathsCore
import Foundation
@preconcurrency import URLRouting
public enum ViewRoute: Sendable, Equatable {
public extension SiteRoute {
enum View: Sendable, Equatable {
case employee(EmployeeRoute)
case login(LoginRoute)
case purchaseOrder(PurchaseOrderRoute)
case user(UserRoute)
case vendor(VendorRoute)
case vendorBranch(VendorBranchRoute)
public static let router = OneOf {
Route(.case(Self.employee)) { EmployeeRoute.router }
Route(.case(Self.login)) { LoginRoute.router }
Route(.case(Self.purchaseOrder)) { PurchaseOrderRoute.router }
Route(.case(Self.user)) { UserRoute.router }
Route(.case(Self.vendor)) { VendorRoute.router }
Route(.case(Self.vendorBranch)) { VendorBranchRoute.router }
}
}
public extension ViewRoute {
enum EmployeeRoute: Sendable, Equatable {
case create(Employee.Create)
case form
case get(id: Employee.ID)
case index
case select(context: SelectContext)
case update(id: Employee.ID, updates: Employee.Update)
static let rootPath = "employees"
case employee(SiteRoute.View.EmployeeRoute)
case login(SiteRoute.View.LoginRoute)
case purchaseOrder(SiteRoute.View.PurchaseOrderRoute)
case user(SiteRoute.View.UserRoute)
case vendor(SiteRoute.View.VendorRoute)
case vendorBranch(SiteRoute.View.VendorBranchRoute)
public static let router = OneOf {
Route(.case(Self.create)) {
Path { rootPath }
Method.post
Body {
FormData {
Field("firstName", .string)
Field("lastName", .string)
Optionally { Field("active") { Bool.parser() } }
Route(.case(Self.employee)) { SiteRoute.View.EmployeeRoute.router }
Route(.case(Self.login)) { SiteRoute.View.LoginRoute.router }
Route(.case(Self.purchaseOrder)) { SiteRoute.View.PurchaseOrderRoute.router }
Route(.case(Self.user)) { SiteRoute.View.UserRoute.router }
Route(.case(Self.vendor)) { SiteRoute.View.VendorRoute.router }
Route(.case(Self.vendorBranch)) { SiteRoute.View.VendorBranchRoute.router }
}
public enum EmployeeRoute: Sendable, Equatable {
case create(Employee.Create)
case form
case get(id: Employee.ID)
case index
case select(context: SiteRoute.View.SelectContext)
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 {
FormData {
Field("firstName", .string)
Field("lastName", .string)
Optionally { Field("active") { Bool.parser() } }
}
.map(.memberwise(Employee.Create.init))
}
.map(.memberwise(Employee.Create.init))
}
}
Route(.case(Self.get(id:))) {
Path { rootPath; Employee.ID.parser() }
Method.get
}
Route(.case(Self.form)) {
Path { rootPath; "create" }
Method.get
}
Route(.case(Self.index)) {
Path { rootPath }
Method.get
}
Route(.case(Self.update(id:updates:))) {
Path { rootPath; Employee.ID.parser() }
Method.put
Body {
FormData {
Optionally { Field("firstName") { CharacterSet.alphanumerics.map(.string) } }
Optionally { Field("lastName") { CharacterSet.alphanumerics.map(.string) } }
Optionally { Field("active") { Bool.parser() } }
Route(.case(Self.get(id:))) {
Path { rootPath; Employee.ID.parser() }
Method.get
}
Route(.case(Self.form)) {
Path { rootPath; "create" }
Method.get
}
Route(.case(Self.index)) {
Path { rootPath }
Method.get
}
Route(.case(Self.update(id:updates:))) {
Path { rootPath; Employee.ID.parser() }
Method.put
Body {
FormData {
Optionally { Field("firstName") { CharacterSet.alphanumerics.map(.string) } }
Optionally { Field("lastName") { CharacterSet.alphanumerics.map(.string) } }
Optionally { Field("active") { Bool.parser() } }
}
.map(.memberwise(Employee.Update.init))
}
.map(.memberwise(Employee.Update.init))
}
}
Route(.case(Self.select(context:))) {
Path { rootPath; "select" }
Method.get
Query {
Field("context") { SelectContext.parser() }
Route(.case(Self.select(context:))) {
Path { rootPath; "select" }
Method.get
Query {
Field("context") { SelectContext.parser() }
}
}
}
}
}
}
public extension ViewRoute {
public enum LoginRoute: Sendable, Equatable {
case index(next: String? = nil)
case post(Request)
enum LoginRoute: Sendable, Equatable {
case index(next: String? = nil)
case post(Request)
static let rootPath = "login"
static let rootPath = "login"
public static let router = OneOf {
Route(.case(Self.index)) {
Method.get
Path { rootPath }
Query {
Optionally {
Field("next", default: nil) {
CharacterSet.urlPathAllowed.map(.string)
}
}
}
}
Route(.case(Self.post)) {
Path { rootPath }
Method.post
Body {
FormData {
Field("username", .string)
Field("password", .string)
public static let router = OneOf {
Route(.case(Self.index)) {
Method.get
Path { rootPath }
Query {
Optionally {
Field("next", default: nil) {
CharacterSet.urlPathAllowed.map(.string)
}
}
}
.map(.memberwise(Request.init))
}
}
}
public struct Request: Codable, Equatable, Sendable {
public let username: String
public let password: String
public let next: String?
public init(username: String, password: String, next: String? = nil) {
self.username = username
self.password = password
self.next = next
}
}
}
}
public extension ViewRoute {
enum PurchaseOrderRoute: Sendable, Equatable {
case create(PurchaseOrder.Create)
case form
case get(id: PurchaseOrder.ID)
case index
case page(page: Int, limit: Int)
case search(Search)
static let rootPath = "purchase-orders"
public static let router = OneOf {
Route(.case(Self.create)) {
Path { rootPath }
Method.post
Body {
FormData {
Optionally { Field("id") { PurchaseOrder.ID.parser() } }
Optionally { Field("workOrder") { Int.parser() } }
Field("materials", .string)
Field("customer", .string)
Optionally { Field("truckStock") { Bool.parser() } }
Field("createdByID") { User.ID.parser() }
Field("createdForID") { Employee.ID.parser() }
Field("vendorBranchID") { VendorBranch.ID.parser() }
}
.map(.memberwise(PurchaseOrder.Create.init))
}
}
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.page(page:limit:))) {
Path { rootPath; "next" }
Method.get
Query {
Field("page", default: 1) { Digits() }
Field("limit", default: 25) { Digits() }
}
}
Route(.case(Self.search)) {
Search.router
}
}
public enum Search: Sendable, Equatable {
case index(context: Context? = nil, table: Bool? = nil)
case request(Request)
static let rootPath = Path { "purchase-orders"; "search" }
static let router = OneOf {
Route(.case(Search.index(context:table:))) {
rootPath
Method.get
Query {
Optionally { Field("context", default: .employee) { Search.Context.parser() } }
Optionally { Field("table", default: nil) { Bool.parser() } }
}
}
Route(.case(Search.request)) {
rootPath
Route(.case(Self.post)) {
Path { rootPath }
Method.post
Body {
FormData {
Field("context") { Context.parser() }
Optionally { Field("createdForID") { Employee.ID.parser() } }
Optionally { Field("customerSearch", .string) }
Optionally { Field("vendorBranchID") { VendorBranch.ID.parser() } }
Field("username", .string)
Field("password", .string)
Optionally {
Field("next", default: nil) {
CharacterSet.urlPathAllowed.map(.string)
}
}
}
.map(.memberwise(Request.init))
}
}
}
public enum Context: String, Codable, CaseIterable, Equatable, Sendable {
case employee
case customer
case vendor
}
// TODO: Create a validation or potentially turn this into an enum with the valid states.
public struct Request: Codable, Equatable, Sendable {
public let context: Context
public let createdForID: Employee.ID?
public let customerSearch: String?
public let vendorBranchID: VendorBranch.ID?
public let username: String
public let password: String
public let next: String?
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 init(username: String, password: String, next: String? = nil) {
self.username = username
self.password = password
self.next = next
}
}
}
}
}
public extension ViewRoute {
public enum PurchaseOrderRoute: Sendable, Equatable {
case create(PurchaseOrder.Create)
case form
case get(id: PurchaseOrder.ID)
case index
case page(page: Int, limit: Int)
case search(Search)
enum SelectContext: String, Codable, Equatable, Sendable, CaseIterable {
case purchaseOrderForm
case purchaseOrderSearch
}
static let rootPath = "purchase-orders"
}
public extension ViewRoute {
enum UserRoute: Sendable, Equatable {
case create(User.Create)
case form
case get(id: User.ID)
case index
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 {
FormData {
Field("username", .string)
Field("email", .string)
Field("password", .string)
Field("confirmPassword", .string)
}
.map(.memberwise(User.Create.init))
}
}
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.update(id:updates:))) {
Path { rootPath; User.ID.parser() }
Method.patch
Body {
FormData {
Optionally { Field("username") {
CharacterSet.alphanumerics.map(.string)
public static let router = OneOf {
Route(.case(Self.create)) {
Path { rootPath }
Method.post
Body {
FormData {
Optionally { Field("id") { PurchaseOrder.ID.parser() } }
Optionally { Field("workOrder") { Int.parser() } }
Field("materials", .string)
Field("customer", .string)
Optionally { Field("truckStock") { Bool.parser() } }
Field("createdByID") { User.ID.parser() }
Field("createdForID") { Employee.ID.parser() }
Field("vendorBranchID") { VendorBranch.ID.parser() }
}
.map(.memberwise(PurchaseOrder.Create.init))
}
}
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.page(page:limit:))) {
Path { rootPath; "next" }
Method.get
Query {
Field("page", default: 1) { Digits() }
Field("limit", default: 25) { Digits() }
}
}
Route(.case(Self.search)) {
Search.router
}
}
public enum Search: Sendable, Equatable {
case index(context: Context? = nil, table: Bool? = nil)
case request(Request)
static let rootPath = Path { "purchase-orders"; "search" }
static let router = OneOf {
Route(.case(Search.index(context:table:))) {
rootPath
Method.get
Query {
Optionally { Field("context", default: .employee) { Search.Context.parser() } }
Optionally { Field("table", default: nil) { Bool.parser() } }
}
Optionally { Field("email", .string) }
}
.map(.memberwise(User.Update.init))
}
}
}
}
}
public extension ViewRoute {
enum VendorRoute: Sendable, Equatable {
case create(Vendor.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 {
FormData {
Field("name", .string)
}
.map(.memberwise(Vendor.Create.init))
}
}
Route(.case(Self.get(id:))) {
Path { rootPath; Vendor.ID.parser() }
Method.get
}
Route(.case(Self.form)) {
Path { rootPath; "create" }
Method.get
}
Route(.case(Self.index)) {
Path { rootPath }
Method.get
}
Route(.case(Self.update(id:updates:))) {
Path { rootPath; Vendor.ID.parser() }
Method.put
Body {
FormData {
Field("name", .string)
}
.map(.memberwise(Vendor.Update.init))
}
}
}
}
}
public extension ViewRoute {
enum VendorBranchRoute: Sendable, Equatable {
case create(VendorBranch.Create)
case index(for: Vendor.ID? = nil)
case select(context: ViewRoute.SelectContext)
public static let router = OneOf {
Route(.case(Self.create)) {
Path { "vendors"; "branches" }
Method.post
Body {
FormData {
Field("name", .string)
Field("vendorID") { Vendor.ID.parser() }
}
.map(.memberwise(VendorBranch.Create.init))
}
}
Route(.case(Self.index(for:))) {
Path { "vendors"; "branches" }
Method.get
Query {
Optionally { Field("vendorID") { Vendor.ID.parser() } }
}
}
Route(.case(Self.select(context:))) {
Path { "vendors"; "branches"; "select" }
Method.get
Query {
Field("context") { SelectContext.parser() }
Route(.case(Search.request)) {
rootPath
Method.post
Body {
FormData {
Field("context") { Context.parser() }
Optionally { Field("createdForID") { Employee.ID.parser() } }
Optionally { Field("customerSearch", .string) }
Optionally { Field("vendorBranchID") { VendorBranch.ID.parser() } }
}
.map(.memberwise(Request.init))
}
}
}
public enum Context: String, Codable, CaseIterable, Equatable, Sendable {
case employee
case customer
case vendor
}
// TODO: Create a validation or potentially turn this into an enum with the valid states.
public struct Request: Codable, Equatable, 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 SelectContext: String, Codable, Equatable, Sendable, CaseIterable {
case purchaseOrderForm
case purchaseOrderSearch
}
public enum UserRoute: Sendable, Equatable {
case create(User.Create)
case form
case get(id: User.ID)
case index
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 {
FormData {
Field("username", .string)
Field("email", .string)
Field("password", .string)
Field("confirmPassword", .string)
}
.map(.memberwise(User.Create.init))
}
}
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.update(id:updates:))) {
Path { rootPath; User.ID.parser() }
Method.patch
Body {
FormData {
Optionally { Field("username") {
CharacterSet.alphanumerics.map(.string)
}
}
Optionally { Field("email", .string) }
}
.map(.memberwise(User.Update.init))
}
}
}
}
public enum VendorRoute: Sendable, Equatable {
case create(Vendor.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 {
FormData {
Field("name", .string)
}
.map(.memberwise(Vendor.Create.init))
}
}
Route(.case(Self.get(id:))) {
Path { rootPath; Vendor.ID.parser() }
Method.get
}
Route(.case(Self.form)) {
Path { rootPath; "create" }
Method.get
}
Route(.case(Self.index)) {
Path { rootPath }
Method.get
}
Route(.case(Self.update(id:updates:))) {
Path { rootPath; Vendor.ID.parser() }
Method.put
Body {
FormData {
Field("name", .string)
}
.map(.memberwise(Vendor.Update.init))
}
}
}
}
public enum VendorBranchRoute: Sendable, Equatable {
case create(VendorBranch.Create)
case index(for: Vendor.ID? = nil)
case select(context: SiteRoute.View.SelectContext)
public static let router = OneOf {
Route(.case(Self.create)) {
Path { "vendors"; "branches" }
Method.post
Body {
FormData {
Field("name", .string)
Field("vendorID") { Vendor.ID.parser() }
}
.map(.memberwise(VendorBranch.Create.init))
}
}
Route(.case(Self.index(for:))) {
Path { "vendors"; "branches" }
Method.get
Query {
Optionally { Field("vendorID") { Vendor.ID.parser() } }
}
}
Route(.case(Self.select(context:))) {
Path { "vendors"; "branches"; "select" }
Method.get
Query {
Field("context") { SiteRoute.View.SelectContext.parser() }
}
}
}
}

View File

@@ -21,7 +21,7 @@ public struct ViewController: Sendable {
@Sendable
public func view(
for route: ViewRoute,
for route: SiteRoute.View,
isHtmxRequest: Bool,
logger: Logger,
authenticate: @escaping AuthenticateHandler
@@ -30,13 +30,13 @@ public struct ViewController: Sendable {
}
public struct Request: Sendable {
public let route: ViewRoute
public let route: SiteRoute.View
public let isHtmxRequest: Bool
public let authenticate: AuthenticateHandler
public let logger: Logger
public init(
_ route: ViewRoute,
_ route: SiteRoute.View,
isHtmxRequest: Bool,
authenticate: @escaping AuthenticateHandler,
logger: Logger

View File

@@ -6,7 +6,7 @@ import SharedModels
import Vapor
import ViewController
public extension SharedModels.ViewRoute {
public extension SiteRoute.View {
@Sendable
func view(
@@ -52,7 +52,7 @@ public extension SharedModels.ViewRoute {
}
}
extension SharedModels.ViewRoute.EmployeeRoute {
extension SiteRoute.View.EmployeeRoute {
private func mainPage<C: HTML>(
_ html: C
@@ -98,7 +98,7 @@ extension SharedModels.ViewRoute.EmployeeRoute {
}
extension SharedModels.ViewRoute.PurchaseOrderRoute {
extension SiteRoute.View.PurchaseOrderRoute {
private func mainPage<C: HTML>(
_ html: C
) async throws -> AnySendableHTML where C: Sendable {
@@ -148,7 +148,7 @@ extension SharedModels.ViewRoute.PurchaseOrderRoute {
}
extension SharedModels.ViewRoute.PurchaseOrderRoute.Search {
extension SiteRoute.View.PurchaseOrderRoute.Search {
func mainPage(search: PurchaseOrderSearch = .init()) -> AnySendableHTML {
MainPage(displayNav: true, route: .purchaseOrders) {
@@ -180,7 +180,7 @@ extension SharedModels.ViewRoute.PurchaseOrderRoute.Search {
}
}
extension SharedModels.ViewRoute.UserRoute {
extension SiteRoute.View.UserRoute {
private func mainPage<C: HTML>(_ html: C) async throws -> AnySendableHTML where C: Sendable {
@Dependency(\.database) var database
@@ -220,7 +220,7 @@ extension SharedModels.ViewRoute.UserRoute {
}
extension SharedModels.ViewRoute.VendorRoute {
extension SiteRoute.View.VendorRoute {
private func mainPage<C: HTML>(_ html: C) async throws -> AnySendableHTML where C: Sendable {
@Dependency(\.database) var database
let vendors = try await database.vendors.fetchAll(.withBranches)
@@ -267,7 +267,7 @@ extension SharedModels.ViewRoute.VendorRoute {
}
}
extension SharedModels.ViewRoute.VendorBranchRoute {
extension SiteRoute.View.VendorBranchRoute {
@Sendable
func view(isHtmxRequest: Bool) async throws -> AnySendableHTML {
@@ -292,7 +292,7 @@ extension SharedModels.ViewRoute.VendorBranchRoute {
}
}
extension SharedModels.ViewRoute.PurchaseOrderRoute.Search.Request {
extension SiteRoute.View.PurchaseOrderRoute.Search.Request {
@Sendable
func toDatabaseQuery() throws -> PurchaseOrder.SearchContext {
@@ -316,7 +316,7 @@ extension SharedModels.ViewRoute.PurchaseOrderRoute.Search.Request {
}
}
extension SharedModels.ViewRoute.SelectContext {
extension SiteRoute.View.SelectContext {
@Sendable
func toHTML(employees: [Employee]) -> EmployeeSelect {

View File

@@ -74,7 +74,7 @@ struct EmployeeForm: HTML {
return "Update"
}
private var targetURL: SharedModels.ViewRoute {
private var targetURL: SiteRoute.View {
guard let employee else { return .employee(.index) }
return .employee(.get(id: employee.id))
}

View File

@@ -4,23 +4,23 @@ import SharedModels
extension HTMLAttribute.hx {
@Sendable
static func get(route: SharedModels.ViewRoute) -> HTMLAttribute {
get(SharedModels.ViewRoute.router.path(for: route))
static func get(route: SiteRoute.View) -> HTMLAttribute {
get(SiteRoute.View.router.path(for: route))
}
@Sendable
static func post(route: SharedModels.ViewRoute) -> HTMLAttribute {
post(SharedModels.ViewRoute.router.path(for: route))
static func post(route: SiteRoute.View) -> HTMLAttribute {
post(SiteRoute.View.router.path(for: route))
}
@Sendable
static func put(route: SharedModels.ViewRoute) -> HTMLAttribute {
put(SharedModels.ViewRoute.router.path(for: route))
static func put(route: SiteRoute.View) -> HTMLAttribute {
put(SiteRoute.View.router.path(for: route))
}
@Sendable
static func delete(route: SharedModels.ApiRoute) -> HTMLAttribute {
delete(SharedModels.ApiRoute.router.path(for: route))
static func delete(route: SiteRoute.Api) -> HTMLAttribute {
delete(SiteRoute.Api.router.path(for: route))
}
}
@@ -65,8 +65,8 @@ indirect enum HXOnValue: Sendable {
}
@Sendable
static func setWindowLocation(to route: ViewRoute) -> Self {
setWindowLocation(ViewRoute.router.path(for: route))
static func setWindowLocation(to route: SiteRoute.View) -> Self {
setWindowLocation(SiteRoute.View.router.path(for: route))
}
@Sendable

View File

@@ -53,7 +53,7 @@ struct LoggedIn: HTML, Sendable {
let next: String?
var content: some HTML {
div(
.hx.get(nextRoute ?? ViewRoute.router.path(for: .purchaseOrder(.index))),
.hx.get(nextRoute ?? SiteRoute.View.router.path(for: .purchaseOrder(.index))),
.hx.pushURL(true),
.hx.target(.body),
.hx.trigger(.event(.revealed)),
@@ -66,7 +66,9 @@ struct LoggedIn: HTML, Sendable {
// HACK: to get search route to work after login.
var nextRoute: String? {
if let next, next.contains("search") {
return ViewRoute.router.path(for: .purchaseOrder(.search(.index(context: .employee, table: true))))
return SiteRoute.View.router.path(
for: .purchaseOrder(.search(.index(context: .employee, table: true)))
)
}
return next
}

View File

@@ -5,7 +5,7 @@ import Vapor
struct PurchaseOrderSearch: HTML, Sendable {
typealias Context = SharedModels.ViewRoute.PurchaseOrderRoute.Search.Context
typealias Context = SiteRoute.View.PurchaseOrderRoute.Search.Context
let context: Context

View File

@@ -5,7 +5,7 @@ import SharedModels
import Vapor
struct PurchaseOrderTable: HTML, Sendable {
typealias SearchContext = SharedModels.ViewRoute.PurchaseOrderRoute.Search.Context
typealias SearchContext = SiteRoute.View.PurchaseOrderRoute.Search.Context
let page: Page<PurchaseOrder>
let context: Context

View File

@@ -101,7 +101,7 @@ struct UserForm: HTML, Sendable {
}
}
var targetURL: ViewRoute {
var targetURL: SiteRoute.View {
switch self {
case .create:
return .user(.index)

View File

@@ -22,10 +22,10 @@ enum Button {
}
@Sendable
static func close(id: IDKey, resetURL route: ViewRoute? = nil) -> some HTML<HTMLTag.button> {
static func close(id: IDKey, resetURL route: SiteRoute.View? = nil) -> some HTML<HTMLTag.button> {
close(
id: id.description,
resetURL: route != nil ? ViewRoute.router.path(for: route!) : nil
resetURL: route != nil ? SiteRoute.View.router.path(for: route!) : nil
)
}

View File

@@ -62,7 +62,7 @@ extension Float where B == DefaultCloseButton {
init(
id: String = "float",
shouldDisplay: Bool,
resetURL route: ViewRoute? = nil,
resetURL route: SiteRoute.View? = nil,
@HTMLBuilder body: () -> C
) {
self.init(
@@ -71,7 +71,7 @@ extension Float where B == DefaultCloseButton {
body: body,
closeButton: { DefaultCloseButton(
id: id,
resetURL: route != nil ? ViewRoute.router.path(for: route!) : nil
resetURL: route != nil ? SiteRoute.View.router.path(for: route!) : nil
) }
)
}

View File

@@ -6,7 +6,7 @@ import Vapor
struct EmployeeSelect: HTML, Sendable {
let employees: [Employee]?
let context: ViewRoute.SelectContext
let context: SiteRoute.View.SelectContext
var content: some HTML {
if let employees {
@@ -42,7 +42,7 @@ struct EmployeeSelect: HTML, Sendable {
struct VendorBranchSelect: HTML, Sendable {
let branches: [VendorBranch.Detail]?
let context: ViewRoute.SelectContext
let context: SiteRoute.View.SelectContext
var content: some HTML {
if let branches {
@@ -79,7 +79,7 @@ struct VendorBranchSelect: HTML, Sendable {
// case purchaseOrderForm
// case purchaseOrderSearch
extension ViewRoute.SelectContext {
extension SiteRoute.View.SelectContext {
var classString: String {
switch self {
case .purchaseOrderForm: return "col-3"

View File

@@ -85,7 +85,7 @@ struct VendorForm: HTML, Sendable {
return "Update"
}
var targetURL: SharedModels.ViewRoute {
var targetURL: SiteRoute.View {
guard let vendor else { return .vendor(.index) }
return .vendor(.get(id: vendor.id))
}

View File

@@ -6,7 +6,7 @@ import URLRouting
@Suite("EmployeeApiRouteTests")
struct EmployeeApiRouteTests {
let router = ApiRoute.router
let router = SiteRoute.Api.router
@Test
func employeeCreate() throws {

View File

@@ -6,7 +6,7 @@ import URLRouting
@Suite("PurchaseOrderApiRouteTests")
struct PurchaseOrderApiRouteTests {
let router = ApiRoute.router
let router = SiteRoute.Api.router
@Test
func create() throws {

View File

@@ -6,7 +6,7 @@ import URLRouting
@Suite("UserApiRouteTests")
struct UserApiRouteTests {
let router = ApiRoute.router
let router = SiteRoute.Api.router
@Test
func create() throws {

View File

@@ -6,7 +6,7 @@ import URLRouting
@Suite("VendorApiRouteTests")
struct VendorApiRouteTests {
let router = ApiRoute.router
let router = SiteRoute.Api.router
@Test
func create() throws {

View File

@@ -6,7 +6,7 @@ import URLRouting
@Suite("VendorBranchApiRouteTests")
struct VendorBranchApiRouteTests {
let router = ApiRoute.router
let router = SiteRoute.Api.router
@Test
func create() throws {

View File

@@ -90,7 +90,7 @@ struct ViewControllerTests {
htmlString = try await viewController.render(.purchaseOrder(.page(page: 1, limit: 25)))
assertSnapshot(of: htmlString, as: .html)
for context in ViewRoute.PurchaseOrderRoute.Search.Context.allCases {
for context in SiteRoute.View.PurchaseOrderRoute.Search.Context.allCases {
htmlString = try await viewController.render(.purchaseOrder(.search(.index(
context: context,
table: true
@@ -189,7 +189,7 @@ struct ViewControllerTests {
extension ViewController {
func render(_ route: ViewRoute) async throws -> String {
func render(_ route: SiteRoute.View) async throws -> String {
let html = try await view(
for: route,
isHtmxRequest: true,

View File

@@ -6,7 +6,7 @@ import URLRouting
@Suite("EmployeeViewRouteTests")
struct EmployeeViewRouteTests {
let router = ViewRoute.router
let router = SiteRoute.View.router
@Test
func employeeCreate() throws {

View File

@@ -6,7 +6,7 @@ import URLRouting
@Suite("LoginViewRouteTests")
struct LoginViewRouteTests {
let router = ViewRoute.router
let router = SiteRoute.View.router
@Test
func get() throws {

View File

@@ -6,7 +6,7 @@ import URLRouting
@Suite("PurchaseOrderViewRouteTests")
struct PurchaseOrderViewRouteTests {
let router = ViewRoute.router
let router = SiteRoute.View.router
@Test
func create() throws {

View File

@@ -6,7 +6,7 @@ import URLRouting
@Suite("UserViewRouteTests")
struct UserViewRouteTests {
let router = ViewRoute.router
let router = SiteRoute.View.router
@Test
func create() throws {

View File

@@ -6,7 +6,7 @@ import URLRouting
@Suite("VendorBranchViewRouteTests")
struct VendorBranchViewRouteTests {
let router = ViewRoute.router
let router = SiteRoute.View.router
@Test
func create() throws {

View File

@@ -6,7 +6,7 @@ import URLRouting
@Suite("VendorViewRouteTests")
struct VendorViewRouteTests {
let router = ViewRoute.router
let router = SiteRoute.View.router
@Test
func create() throws {