feat: Refactoring route declarations.

This commit is contained in:
2025-01-19 10:52:15 -05:00
parent d27a19863a
commit 1c8748211c
20 changed files with 954 additions and 644 deletions

View File

@@ -1,73 +1,73 @@
import Dependencies
import Elementary
import Fluent
import SharedModels
import Vapor
import VaporElementary
struct PurchaseOrderSearchViewController: RouteCollection {
@Dependency(\.database.employees) var employees
@Dependency(\.database.vendorBranches) var vendorBranches
@Dependency(\.database.purchaseOrders) var purchaseOrders
func boot(routes: any RoutesBuilder) throws {
let route = routes.protected.grouped("purchase-orders", "search")
route.get(use: index)
route.post(use: post)
}
@Sendable
func index(req: Request) async throws -> HTMLResponse {
let query = try? req.query.decode(FormQuery.self)
let html = PurchaseOrderSearch(context: query?.context)
if query?.table == true || !req.isHtmxRequest {
return await req.render { mainPage(search: html) }
}
return await req.render { html }
}
@Sendable
func post(req: Request) async throws -> HTMLResponse {
let context = try req.content.decode(PurchaseOrderSearchContent.self)
let results = try await purchaseOrders.search(context.toDatabaseQuery(), .init(page: 1, per: 25))
return await req.render { PurchaseOrderTable(page: results, context: .search, searchContext: nil) }
}
func mainPage(search: PurchaseOrderSearch = .init()) -> some SendableHTMLDocument {
MainPage(displayNav: true, route: .purchaseOrders) {
div(.class("container"), .id("purchase-order-content")) {
search
PurchaseOrderTable(page: .init(items: [], metadata: .init(page: 0, per: 50, total: 0)))
}
}
}
}
extension PurchaseOrderSearchContent {
func toDatabaseQuery() throws -> PurchaseOrder.SearchContext {
switch context {
case .employee:
guard let createdForID else {
throw Abort(.badRequest, reason: "Employee id not provided")
}
return .employee(createdForID)
case .customer:
guard let search, !search.isEmpty else {
throw Abort(.badRequest, reason: "Customer search string is empty.")
}
return .customer(search)
case .vendor:
guard let vendorBranchID else {
throw Abort(.badRequest, reason: "Vendor branch id not provided.")
}
return .vendor(vendorBranchID)
}
}
}
private struct FormQuery: Content {
let context: PurchaseOrderSearchContext
let table: Bool?
}
// import Dependencies
// import Elementary
// import Fluent
// import SharedModels
// import Vapor
// import VaporElementary
//
// struct PurchaseOrderSearchViewController: RouteCollection {
// @Dependency(\.database.employees) var employees
// @Dependency(\.database.vendorBranches) var vendorBranches
// @Dependency(\.database.purchaseOrders) var purchaseOrders
//
// func boot(routes: any RoutesBuilder) throws {
// let route = routes.protected.grouped("purchase-orders", "search")
// route.get(use: index)
// route.post(use: post)
// }
//
// @Sendable
// func index(req: Request) async throws -> HTMLResponse {
// let query = try? req.query.decode(FormQuery.self)
// let html = PurchaseOrderSearch(context: query?.context)
// if query?.table == true || !req.isHtmxRequest {
// return await req.render { mainPage(search: html) }
// }
// return await req.render { html }
// }
//
// @Sendable
// func post(req: Request) async throws -> HTMLResponse {
// let context = try req.content.decode(PurchaseOrderSearchContent.self)
// let results = try await purchaseOrders.search(context.toDatabaseQuery(), .init(page: 1, per: 25))
// return await req.render { PurchaseOrderTable(page: results, context: .search, searchContext: nil) }
// }
//
// func mainPage(search: PurchaseOrderSearch = .init()) -> some SendableHTMLDocument {
// MainPage(displayNav: true, route: .purchaseOrders) {
// div(.class("container"), .id("purchase-order-content")) {
// search
// PurchaseOrderTable(page: .init(items: [], metadata: .init(page: 0, per: 50, total: 0)))
// }
// }
// }
//
// }
//
// extension PurchaseOrderSearchContent {
//
// func toDatabaseQuery() throws -> PurchaseOrder.SearchContext {
// switch context {
// case .employee:
// guard let createdForID else {
// throw Abort(.badRequest, reason: "Employee id not provided")
// }
// return .employee(createdForID)
// case .customer:
// guard let search, !search.isEmpty else {
// throw Abort(.badRequest, reason: "Customer search string is empty.")
// }
// return .customer(search)
// case .vendor:
// guard let vendorBranchID else {
// throw Abort(.badRequest, reason: "Vendor branch id not provided.")
// }
// return .vendor(vendorBranchID)
// }
// }
// }
//
// private struct FormQuery: Content {
// let context: PurchaseOrderSearchContext
// let table: Bool?
// }

View File

@@ -15,8 +15,6 @@ struct PurchaseOrderViewController: RouteCollection {
route.get(use: index)
route.get("next", use: nextPage)
route.post(use: create(req:))
// route.post("search", use: postSearch)
// route.get("search", use: getSearch)
route.group("create") {
$0.get(use: form)
}
@@ -68,26 +66,26 @@ struct PurchaseOrderViewController: RouteCollection {
return await req.render { PurchaseOrderTable.Row(purchaseOrder: purchaseOrder) }
}
@Sendable
func postSearch(req: Request) async throws -> HTMLResponse {
let context = try req.content.decode(PurchaseOrderSearchContent.self)
let results = try await purchaseOrders.search(context.toDatabaseQuery(), .init(page: 1, per: 25))
return await req.render { PurchaseOrderTable(page: results, context: .search, searchContext: nil) }
}
// @Sendable
// func postSearch(req: Request) async throws -> HTMLResponse {
// let context = try req.content.decode(PurchaseOrderSearchContent.self)
// let results = try await purchaseOrders.search(context.toDatabaseQuery(), .init(page: 1, per: 25))
// return await req.render { PurchaseOrderTable(page: results, context: .search, searchContext: nil) }
// }
// Show the form to generate a search query.
@Sendable
func getSearch(req: Request) async throws -> HTMLResponse {
// TODO: Need to handle updating the form.
return await req.render {
MainPage(displayNav: true, route: .purchaseOrders) {
div(.class("container"), .id("purchase-order-content")) {
PurchaseOrderSearch()
PurchaseOrderTable(page: .init(items: [], metadata: .init(page: 0, per: 50, total: 0)))
}
}
}
}
// @Sendable
// func getSearch(req: Request) async throws -> HTMLResponse {
// // TODO: Need to handle updating the form.
// return await req.render {
// MainPage(displayNav: true, route: .purchaseOrders) {
// div(.class("container"), .id("purchase-order-content")) {
// PurchaseOrderSearch()
// PurchaseOrderTable(page: .init(items: [], metadata: .init(page: 0, per: 50, total: 0)))
// }
// }
// }
// }
private func mainPage<C: HTML>(
_ html: C,

View File

@@ -19,7 +19,7 @@ struct EmployeeForm: HTML {
var content: some HTML {
Float(shouldDisplay: shouldShow, resetURL: "/employees") {
form(
employee == nil ? .hx.post(targetURL) : .hx.put(targetURL),
employee == nil ? .hx.post(route: targetURL) : .hx.put(route: targetURL),
.hx.target(target),
employee == nil
? .hx.swap(.beforeEnd.transition(true).swap("0.5s"))
@@ -51,7 +51,7 @@ struct EmployeeForm: HTML {
"Delete"
}
.attributes(
.hx.delete("/api/v1/employees/\(employee.id)"),
.hx.delete(route: .employee(.shared(.delete(id: employee.id)))),
.hx.confirm("Are you sure you want to delete this employee?"),
.hx.target("#employee_\(employee.id)"),
.hx.swap(.outerHTML.transition(true).swap("1s"))
@@ -74,8 +74,8 @@ struct EmployeeForm: HTML {
return "Update"
}
private var targetURL: String {
guard let employee else { return "/employees" }
return "/employees/\(employee.id)"
private var targetURL: SharedModels.ViewRoute {
guard let employee else { return .employee(.shared(.index)) }
return .employee(.shared(.get(id: employee.id)))
}
}

View File

@@ -14,7 +14,7 @@ struct EmployeeTable: HTML {
Button.add()
.attributes(
.style("padding: 0px 10px;"),
.hx.get(route: .employees(.create)),
.hx.get(route: .employee(.shared(.index))),
.hx.target(.float),
.hx.swap(.outerHTML.transition(true).swap("0.5s"))
)
@@ -39,7 +39,7 @@ struct EmployeeTable: HTML {
Button.detail()
.attributes(
.style("padding-left: 15px;"),
.hx.get(route: .employees(.id(employee.id))),
.hx.get(route: .employee(.shared(.get(id: employee.id)))),
.hx.target(.float),
.hx.pushURL(true),
.hx.swap(.outerHTML.transition(true).swap("0.5s"))

View File

@@ -27,7 +27,7 @@ struct PurchaseOrderForm: HTML {
}
}
form(
.hx.post(route: .purchaseOrders()),
.hx.post(route: .purchaseOrder(.shared(.index))),
.hx.target(.purchaseOrders(.table)),
.hx.swap(.afterBegin),
.customToggleFloatAfterRequest

View File

@@ -5,35 +5,37 @@ import Vapor
struct PurchaseOrderSearch: HTML {
let context: PurchaseOrderSearchContext
typealias Context = SharedModels.ViewRoute.PurchaseOrderRoute.Search.Context
init(context: PurchaseOrderSearchContext? = nil) {
let context: Context
init(context: Context? = nil) {
self.context = context ?? .employee
}
var content: some HTML {
form(
.id(.search),
.hx.post(route: .purchaseOrders(.search())),
.hx.post(route: .purchaseOrder(.search(.index()))),
.hx.target(.purchaseOrders()),
.hx.swap(.outerHTML)
) {
div(.class("btn-row")) {
button(
.class("btn-secondary"), .style("position: absolute; top: 80px; right: 20px;"),
.hx.get(route: .purchaseOrders()), .hx.pushURL(true), .hx.target("body")
.hx.get(route: .purchaseOrder(.shared(.index))), .hx.pushURL(true), .hx.target("body")
)
{ "x" }
}
div(.class("row")) {
select(
.name("context"), .class("col-3"),
.hx.get(route: .purchaseOrders(.search())),
.hx.get(route: .purchaseOrder(.search(.index()))),
.hx.target(.search),
.hx.swap(.outerHTML.transition(true).swap("0.5s")),
.hx.pushURL(true)
) {
for context in PurchaseOrderSearchContext.allCases {
for context in Context.allCases {
option(.value(context.rawValue)) { context.rawValue.capitalized }
.attributes(.selected, when: self.context == context)
}
@@ -44,7 +46,7 @@ struct PurchaseOrderSearch: HTML {
} else if context == .customer {
input(
.type(.text), .class("col-6"), .style("margin-left: 60px; margin-top: 18px;"),
.name("search"), .placeholder("Search"), .required
.name("customerSearch"), .placeholder("Search"), .required
)
} else if context == .vendor {
VendorBranchSelect.purchaseOrderSearch()
@@ -60,15 +62,15 @@ struct PurchaseOrderSearch: HTML {
}
enum PurchaseOrderSearchContext: String, Codable, Content, CaseIterable {
case employee
case customer
case vendor
}
// enum PurchaseOrderSearchContext: String, Codable, Content, CaseIterable {
// case employee
// case customer
// case vendor
// }
struct PurchaseOrderSearchContent: Content {
let context: PurchaseOrderSearchContext
let createdForID: Employee.ID?
let search: String?
let vendorBranchID: VendorBranch.ID?
}
// struct PurchaseOrderSearchContent: Content {
// let context: PurchaseOrderSearchContext
// let createdForID: Employee.ID?
// let search: String?
// let vendorBranchID: VendorBranch.ID?
// }

View File

@@ -5,15 +5,16 @@ import SharedModels
import Vapor
struct PurchaseOrderTable: HTML {
typealias SearchContext = SharedModels.ViewRoute.PurchaseOrderRoute.Search.Context
let page: Page<PurchaseOrder>
let context: Context
let searchContext: PurchaseOrderSearchContext?
let searchContext: SearchContext?
init(
page: Page<PurchaseOrder>,
context: Context = .default,
searchContext: PurchaseOrderSearchContext? = nil
searchContext: SearchContext? = nil
) {
self.page = page
self.context = context
@@ -44,7 +45,7 @@ struct PurchaseOrderTable: HTML {
if context != .search {
Button.add()
.attributes(
.hx.get(route: .purchaseOrders(.create)), .hx.target(.float),
.hx.get(route: .purchaseOrder(.shared(.index))), .hx.target(.float),
.hx.swap(.outerHTML), .hx.pushURL(true)
)
}
@@ -59,7 +60,7 @@ struct PurchaseOrderTable: HTML {
button(
.id("btn-search"),
.class("btn-primary"), .style("position: absolute; top: 80px; right: 20px;"),
.hx.get(route: .purchaseOrders(.search(.context(.employee, table: true)))),
.hx.get(route: .purchaseOrder(.search(.index(context: .employee, table: true)))),
.hx.target(.body),
.hx.swap(.outerHTML.transition(true).swap("0.5s")),
.hx.pushURL(true)
@@ -81,7 +82,7 @@ struct PurchaseOrderTable: HTML {
if page.metadata.pageCount > page.metadata.page {
tr(
// .hx.get("/purchase-orders/next?page=\(page.metadata.page + 1)&limit=\(page.metadata.per)"),
.hx.get(route: .purchaseOrders(.nextPage(page.metadata))),
.hx.get(route: .purchaseOrder(.shared(.page(page: page.metadata.page + 1, limit: page.metadata.per)))),
.hx.trigger(.event(.revealed)),
.hx.swap(.outerHTML.transition(true).swap("1s")),
.hx.target(.this),
@@ -110,7 +111,7 @@ struct PurchaseOrderTable: HTML {
td {
Button.detail()
.attributes(
.hx.get("/purchase-orders/\(purchaseOrder.id)"),
.hx.get(route: .purchaseOrder(.shared(.get(id: purchaseOrder.id)))),
.hx.target("#float"),
.hx.swap(.outerHTML.transition(true).swap("0.5s")),
.hx.pushURL(true)

View File

@@ -12,7 +12,7 @@ struct UserDetail: HTML, Sendable {
Float(shouldDisplay: user != nil, resetURL: "/users") {
if let user {
form(
.hx.post(route: .users(.id(user.id))),
.hx.post(route: .user(.shared(.get(id: user.id)))),
.hx.swap(.outerHTML),
.hx.target(.user(.row(id: user.id))),
.custom(name: "hx-on::after-request", value: "toggleContent('float'); window.location.href='/users';")
@@ -36,7 +36,7 @@ struct UserDetail: HTML, Sendable {
) { "Update" }
Button.danger { "Delete" }
.attributes(
.hx.delete(route: .users(.id(user.id))),
.hx.delete(route: .user(.shared(.get(id: user.id)))),
.hx.trigger(.event(.click)),
.hx.swap(.outerHTML),
.hx.target(.user(.row(id: user.id))),

View File

@@ -99,7 +99,7 @@ struct UserForm: HTML, Sendable {
}
}
// TODO: Return a route container.
// TODO: Return a ViewRoute.
var targetURL: String {
switch self {
case .create:

View File

@@ -17,7 +17,7 @@ struct UserTable: HTML {
th(.style("width: 50px;")) {
Button.add()
.attributes(
.hx.get(route: .users(.create)),
.hx.get(route: .user(.form)),
.hx.target(.float),
.hx.swap(.outerHTML)
)
@@ -45,7 +45,7 @@ struct UserTable: HTML {
td { user.email }
td {
Button.detail().attributes(
.hx.get(route: .users(.id(user.id))),
.hx.get(route: .user(.shared(.get(id: user.id)))),
.hx.target(.float),
.hx.swap(.outerHTML),
.hx.pushURL(true)

View File

@@ -15,14 +15,15 @@ struct VendorDetail: HTML {
} closeButton: {
Button.close(id: "float")
.attributes(
.hx.get("/vendors"),
.hx.get(route: .vendor(.shared(.index(withBranches: true)))),
.hx.pushURL(true),
.hx.target("body"),
.hx.target(.body),
.hx.swap(.outerHTML)
)
}
}
// TODO: What route for here??
var branchForm: some HTML {
form(
.id("branch-form"),
@@ -33,7 +34,7 @@ struct VendorDetail: HTML {
) {
input(
.type(.text), .class("col-9"), .name("name"), .placeholder("Add branch..."), .required,
.hx.post("/vendors/\(vendor.id)/branches"),
.hx.post(route: .vendorBranch(.index(for: vendor.id))),
.hx.trigger(.event(.keyup).changed().delay("800ms")),
.hx.target("#branches"),
.hx.swap(.beforeEnd) // ,
@@ -65,7 +66,7 @@ struct VendorDetail: HTML {
span(.class("label")) { branch.name.capitalized }
button(
.class("btn"),
.hx.delete("/api/v1/vendors/branches/\(branch.id)"),
.hx.delete(route: .vendorBranch(.delete(id: branch.id))),
.hx.target("#branch_\(branch.id)"),
.hx.swap(.outerHTML.transition(true).swap("0.5s"))
) {

View File

@@ -41,7 +41,7 @@ struct VendorForm: HTML {
func makeForm(vendor: Vendor?) -> some HTML {
form(
.id("vendor-form"),
vendor != nil ? .hx.put(targetURL) : .hx.post(targetURL),
vendor != nil ? .hx.put(route: targetURL) : .hx.post(route: targetURL),
.hx.target("#content"),
.hx.swap(.outerHTML)
) {
@@ -53,7 +53,7 @@ struct VendorForm: HTML {
.name("name"),
.value(vendor?.name ?? ""),
.placeholder("Vendor Name"),
vendor != nil ? .hx.put(targetURL) : .hx.post(targetURL),
vendor != nil ? .hx.put(route: targetURL) : .hx.post(route: targetURL),
.hx.trigger(.event(.keyup).changed().delay("500ms")),
.required
)
@@ -61,7 +61,7 @@ struct VendorForm: HTML {
button(
.class("danger"),
.style("font-size: 1.25em; padding: 10px 20px; border-radius: 10px;"),
.hx.delete("/api/v1/vendors/\(vendor.id)"),
.hx.delete(route: .vendor(.delete(id: vendor.id))),
.hx.confirm("Are you sure you want to delete this vendor?"),
.hx.target("#vendor_\(vendor.id)"),
.hx.swap(.outerHTML.transition(true).swap("1s")),
@@ -85,8 +85,8 @@ struct VendorForm: HTML {
return "Update"
}
var targetURL: String {
guard let vendor else { return "/vendors" }
return "/vendors/\(vendor.id)"
var targetURL: SharedModels.ViewRoute {
guard let vendor else { return .vendor(.shared(.index(withBranches: true))) }
return .vendor(.shared(.get(id: vendor.id)))
}
}

View File

@@ -15,7 +15,7 @@ struct VendorTable: HTML {
Button.add()
.attributes(
.style("padding: 0px 10px;"),
.hx.get("/vendors/create"),
.hx.get(route: .vendor(.form)),
.hx.target("#float"),
.hx.swap(.outerHTML)
)
@@ -41,7 +41,7 @@ struct VendorTable: HTML {
Button.detail()
.attributes(
.style("padding-left: 15px;"),
.hx.get("/vendors/\(vendor.id)"),
.hx.get(route: .vendor(.shared(.get(id: vendor.id)))),
.hx.target("#float"),
.hx.pushURL(true),
.hx.swap(.outerHTML)

View File

@@ -4,21 +4,38 @@ import Fluent
import SharedModels
extension HTMLAttribute.hx {
static func get(route: RouteKey) -> HTMLAttribute {
get(route.url)
static func get(route: SharedModels.ViewRoute) -> HTMLAttribute {
get(SharedModels.ViewRoute.router.path(for: route))
}
static func post(route: RouteKey) -> HTMLAttribute {
post(route.url)
static func post(route: SharedModels.ViewRoute) -> HTMLAttribute {
post(SharedModels.ViewRoute.router.path(for: route))
}
static func put(route: RouteKey) -> HTMLAttribute {
put(route.url)
static func put(route: SharedModels.ViewRoute) -> HTMLAttribute {
put(SharedModels.ViewRoute.router.path(for: route))
}
static func delete(route: RouteKey) -> HTMLAttribute {
delete(route.url)
static func delete(route: SharedModels.ViewRoute) -> HTMLAttribute {
delete(SharedModels.ViewRoute.router.path(for: route))
}
// static func get(route: SharedModels.ApiRoute) -> HTMLAttribute {
// get(route: .shared(route))
// }
//
// static func post(route: SharedModels.ApiRoute) -> HTMLAttribute {
// post(SharedModels.ApiRoute.router.path(for: route))
// }
//
// static func put(route: SharedModels.ApiRoute) -> HTMLAttribute {
// put(SharedModels.ApiRoute.router.path(for: route))
// }
//
// static func delete(route: SharedModels.ApiRoute) -> HTMLAttribute {
// delete(SharedModels.ApiRoute.router.path(for: route))
// }
}
extension HTMLAttribute.hx {
@@ -34,6 +51,7 @@ extension HTMLAttribute where Tag: HTMLTrait.Attributes.Global {
}
}
// TODO: Remove.
enum RouteKey {
case employees(EmployeeRoute? = nil)
case purchaseOrders(PurchaseOrderRoute? = nil)
@@ -73,8 +91,8 @@ enum RouteKey {
enum PurchaseOrderRoute {
case create
case nextPage(PageMetadata)
case search(SearchQuery? = nil)
// case search(SearchQuery? = nil)
//
var path: String {
switch self {
case .create:
@@ -82,25 +100,24 @@ enum RouteKey {
case let .nextPage(currentPage):
return "next?page=\(currentPage.page + 1)&limit\(currentPage.per)"
case let .search(query):
guard let query else { return "search" }
return "search?\(query.query)"
// case let .search(query):
// guard let query else { return "search" }
// return "search?\(query.query)"
}
}
enum SearchQuery {
case context(PurchaseOrderSearchContext, table: Bool? = nil)
var query: String {
switch self {
case let .context(context, table):
let query = "context=\(context.rawValue)"
guard let table else { return query }
return "\(query)&table=\(table)"
}
}
}
// enum SearchQuery {
// case context(PurchaseOrderSearchContext, table: Bool? = nil)
//
// var query: String {
// switch self {
// case let .context(context, table):
// let query = "context=\(context.rawValue)"
// guard let table else { return query }
// return "\(query)&table=\(table)"
// }
// }
// }
}
enum UserRoute {

View File

@@ -13,13 +13,13 @@ func routes(_ app: Application) throws {
// 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())
try app.register(collection: PurchaseOrderViewController())
try app.register(collection: PurchaseOrderSearchViewController())
try app.register(collection: UtilsViewController())
// try app.register(collection: UserViewController())
// try app.register(collection: VendorViewController())
// try app.register(collection: EmployeeViewController())
// try app.register(collection: PurchaseOrderViewController())
// try app.register(collection: PurchaseOrderSearchViewController())
// try app.register(collection: UtilsViewController())
//
app.get { _ in
HTMLResponse {
MainPage(displayNav: false, route: .purchaseOrders) {
@@ -29,38 +29,38 @@ func routes(_ app: Application) throws {
}
}
}
app.get("login") { req in
let context = try req.query.decode(LoginContext.self)
return await req.render {
MainPage(displayNav: false, route: .login) {
UserForm(context: .login(next: context.next))
}
}
}
app.post("login") { req in
@Dependency(\.database.users) var users
let loginForm = try req.content.decode(User.Login.self)
let token = try await users.login(loginForm)
let user = try await users.get(token.userID)!
req.session.authenticate(user)
let context = try req.query.decode(LoginContext.self)
return await req.render {
MainPage(displayNav: true, route: .purchaseOrders) {
div(
.hx.get(context.next ?? "/purchase-orders"),
.hx.pushURL(true),
.hx.target("body"),
.hx.trigger(.event(.revealed)),
.hx.indicator(".hx-indicator")
) {
Img.spinner().attributes(.class("hx-indicator"))
}
}
}
}
//
// app.get("login") { req in
// let context = try req.query.decode(LoginContext.self)
// return await req.render {
// MainPage(displayNav: false, route: .login) {
// UserForm(context: .login(next: context.next))
// }
// }
// }
//
// app.post("login") { req in
// @Dependency(\.database.users) var users
// let loginForm = try req.content.decode(User.Login.self)
// let token = try await users.login(loginForm)
// let user = try await users.get(token.userID)!
// req.session.authenticate(user)
// let context = try req.query.decode(LoginContext.self)
//
// return await req.render {
// MainPage(displayNav: true, route: .purchaseOrders) {
// div(
// .hx.get(context.next ?? "/purchase-orders"),
// .hx.pushURL(true),
// .hx.target("body"),
// .hx.trigger(.event(.revealed)),
// .hx.indicator(".hx-indicator")
// ) {
// Img.spinner().attributes(.class("hx-indicator"))
// }
// }
// }
// }
}
private struct LoginContext: Content {
@@ -73,7 +73,17 @@ func siteHandler(
) async throws -> any AsyncResponseEncodable {
switch route {
case let .api(route):
switch route {
return try await route.handle(request: request)
case .health:
return HTTPStatus.ok
case let .view(route):
return try await route.handle(request: request)
}
}
extension ApiRoute {
func handle(request: Request) async throws -> any AsyncResponseEncodable {
switch self {
case let .employee(route):
return try await route.handle(request: request)
case let .purchaseOrder(route):
@@ -85,10 +95,12 @@ func siteHandler(
case let .vendorBranch(route):
return try await route.handle(request: request)
}
case .health:
return HTTPStatus.ok
case let .view(route):
switch route {
}
}
extension SharedModels.ViewRoute {
func handle(request: Request) async throws -> any AsyncResponseEncodable {
switch self {
case let .employee(route):
return try await route.handle(request: request)
@@ -100,11 +112,11 @@ func siteHandler(
}
}
case .purchaseOrder:
fatalError()
case let .purchaseOrder(route):
return try await route.handle(request: request)
case .select:
fatalError()
case let .select(route):
return try await route.handle(request: request)
case let .user(route):
return try await route.handle(request: request)
@@ -288,6 +300,105 @@ extension SharedModels.ViewRoute.EmployeeRoute {
}
extension SharedModels.ViewRoute.PurchaseOrderRoute {
private func mainPage<C: HTML>(
_ html: C,
page: Int,
limit: Int
) async throws -> some SendableHTMLDocument where C: Sendable {
@Dependency(\.database.purchaseOrders) var purchaseOrders
let page = try await purchaseOrders.fetchPage(.init(page: page, per: limit))
return MainPage(displayNav: true, route: .purchaseOrders) {
div(.class("container"), .id("purchase-order-content")) {
html
PurchaseOrderTable(page: page)
}
}
}
private func mainPage<C: HTML>(
_ html: C
) async throws -> some SendableHTMLDocument where C: Sendable {
try await mainPage(html, page: 1, limit: 25)
}
func handle(request: Vapor.Request) async throws -> any AsyncResponseEncodable {
@Dependency(\.database.purchaseOrders) var purchaseOrders
switch self {
case .form:
return try await request.render(mainPage: mainPage) {
PurchaseOrderForm(shouldShow: true)
}
case let .search(route):
return try await route.handle(request: request)
case let .shared(route):
switch route {
case let .create(purchaseOrder):
return try await request.render {
try await PurchaseOrderTable.Row(purchaseOrder: purchaseOrders.create(purchaseOrder))
}
case let .delete(id: id):
try await purchaseOrders.delete(id)
return HTTPStatus.ok
case .index:
return try await request.render {
try await mainPage(PurchaseOrderForm())
}
case let .get(id: id):
guard let purchaseOrder = try await purchaseOrders.get(id) else {
throw Abort(.badRequest, reason: "Purchase order not found.")
}
return try await request.render(mainPage: mainPage) {
PurchaseOrderForm(purchaseOrder: purchaseOrder, shouldShow: true)
}
case let .page(page: page, limit: limit):
return try await request.render {
try await PurchaseOrderTable.Rows(
page: purchaseOrders.fetchPage(.init(page: page, per: limit))
)
}
}
}
}
}
extension SharedModels.ViewRoute.PurchaseOrderRoute.Search {
func mainPage(search: PurchaseOrderSearch = .init()) -> some SendableHTMLDocument {
MainPage(displayNav: true, route: .purchaseOrders) {
div(.class("container"), .id("purchase-order-content")) {
search
PurchaseOrderTable(page: .init(items: [], metadata: .init(page: 0, per: 50, total: 0)))
}
}
}
func handle(request: Vapor.Request) async throws -> any AsyncResponseEncodable {
@Dependency(\.database) var database
switch self {
case let .index(context: context, table: table):
let html = PurchaseOrderSearch(context: context)
if table == true || !request.isHtmxRequest {
return await request.render { mainPage(search: html) }
}
return await request.render { html }
case let .search(context):
let results = try await database.purchaseOrders.search(context.toDatabaseQuery(), .init(page: 1, per: 25))
return await request.render {
PurchaseOrderTable(page: results, context: .search)
}
}
}
}
extension SharedModels.ViewRoute.UserRoute {
private func mainPage<C: HTML>(_ html: C) async throws -> some SendableHTMLDocument where C: Sendable {
@@ -381,11 +492,6 @@ extension SharedModels.ViewRoute.VendorRoute {
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):
@@ -423,5 +529,96 @@ extension SharedModels.ViewRoute.VendorRoute {
}
}
}
}
extension SharedModels.ViewRoute.VendorBranchRoute {
func handle(request: Request) async throws -> any AsyncResponseEncodable {
@Dependency(\.database) var database
switch self {
case let .shared(route):
switch route {
case let .create(branch):
return try await request.render {
try await VendorDetail.BranchRow(branch: database.vendorBranches.create(branch))
}
case let .delete(id: id):
try await database.vendorBranches.delete(id)
return HTTPStatus.ok
// FIX:
case let .get(id: id):
fatalError()
case let .index(for: vendorID):
fatalError()
case let .update(id: id, updates: updates):
fatalError()
}
}
}
}
extension SharedModels.ViewRoute.PurchaseOrderRoute.Search.Request {
func toDatabaseQuery() throws -> PurchaseOrder.SearchContext {
switch context {
case .employee:
guard let createdForID else {
throw Abort(.badRequest, reason: "Employee id not provided")
}
return .employee(createdForID)
case .customer:
guard let customerSearch, !customerSearch.isEmpty else {
throw Abort(.badRequest, reason: "Customer search string is empty.")
}
return .customer(customerSearch)
case .vendor:
guard let vendorBranchID else {
throw Abort(.badRequest, reason: "Vendor branch id not provided.")
}
return .vendor(vendorBranchID)
}
}
}
extension SharedModels.ViewRoute.SelectRoute {
func handle(request: Request) async throws -> any AsyncResponseEncodable {
@Dependency(\.database) var database
switch self {
case let .employee(context: context):
return try await request.render {
try await context.toHTML(employees: database.employees.fetchAll())
}
case let .vendorBranches(context: context):
return try await request.render {
try await context.toHTML(branches: database.vendorBranches.fetchAllWithDetail())
}
}
}
}
extension SharedModels.ViewRoute.SelectRoute.Context {
func toHTML(employees: [Employee]) -> EmployeeSelect {
switch self {
case .purchaseOrderForm:
return .purchaseOrderForm(employees: employees)
case .purchaseOrderSearch:
return .purchaseOrderSearch(employees: employees)
}
}
func toHTML(branches: [VendorBranch.Detail]) -> VendorBranchSelect {
switch self {
case .purchaseOrderForm:
return .purchaseOrderForm(branches: branches)
case .purchaseOrderSearch:
return .purchaseOrderSearch(branches: branches)
}
}
}

View File

@@ -0,0 +1,115 @@
import CasePathsCore
import Foundation
@preconcurrency import URLRouting
// TODO: Switch shared to be on API routes not view routes??
public enum ApiRoute: Sendable {
case employee(EmployeeApiRoute)
case purchaseOrder(PurchaseOrderApiRoute)
case user(UserApiRoute)
case vendor(VendorApiRoute)
case vendorBranch(VendorBranchApiRoute)
static let rootPath = Path { "api"; "v1" }
public static let router = OneOf {
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 {
case shared(SharedEmployeeRoute)
public static let router = Route(.case(Self.shared)) {
SharedEmployeeRoute.router
}
}
public enum PurchaseOrderApiRoute: Sendable {
case shared(SharedPurchaseOrderRoute)
public static let router = Route(.case(Self.shared)) {
SharedPurchaseOrderRoute.router
}
}
// TODO: Add logout.
public enum UserApiRoute: Sendable {
case shared(SharedUserRoute)
public static let router = Route(.case(Self.shared)) {
SharedUserRoute.router
}
}
public enum VendorApiRoute: Sendable {
case index(withBranches: Bool?)
case shared(SharedVendorRoute)
static let rootPath = "vendors"
public static let router = OneOf {
Route(.case(Self.index(withBranches:))) {
Path { rootPath }
Method.get
Query {
Field("branches", default: nil) {
Optionally { Bool.parser() }
}
}
}
Route(.case(Self.shared)) {
SharedVendorRoute.router
}
}
}
public enum VendorBranchApiRoute: Sendable {
case get(id: VendorBranch.ID)
case index(for: Vendor.ID? = nil)
case shared(SharedVendorBranchRoute)
case update(id: VendorBranch.ID, updates: VendorBranch.Update)
public static let router = OneOf {
Route(.case(Self.get(id:))) {
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.shared)) {
SharedVendorBranchRoute.router
}
Route(.case(Self.update(id:updates:))) {
Path { "vendors"; "branches"; VendorBranch.ID.parser() }
Method.put
Body(.json(VendorBranch.Update.self))
}
}
}
}

View File

@@ -0,0 +1,165 @@
import CasePathsCore
import Foundation
@preconcurrency import URLRouting
public enum SharedEmployeeRoute: Sendable {
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"
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.update(id:updates:))) {
Path { rootPath; UUID.parser() }
Method.put
Body(.json(Employee.Update.self))
}
}
}
public enum SharedPurchaseOrderRoute: Sendable {
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 = "purchase-orders"
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; Digits() }
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 enum SharedUserRoute: Sendable {
case create(User.Create)
case delete(id: User.ID)
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.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 SharedVendorRoute: Sendable {
case create(Vendor.Create)
case delete(id: Vendor.ID)
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.create)) {
Path { rootPath }
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.update(id:updates:))) {
Path { rootPath; Vendor.ID.parser() }
Method.put
Body(.json(Vendor.Update.self))
}
}
}
public enum SharedVendorBranchRoute: Sendable {
case create(VendorBranch.Create)
case delete(id: VendorBranch.ID)
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
}
}
}

View File

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

View File

@@ -0,0 +1,218 @@
import CasePathsCore
import Foundation
@preconcurrency import URLRouting
public enum ViewRoute: Sendable {
case employee(EmployeeRoute)
case login
case purchaseOrder(PurchaseOrderRoute)
case select(SelectRoute)
case user(UserRoute)
case vendor(VendorRoute)
case vendorBranch(VendorBranchRoute)
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 }
Route(.case(Self.vendorBranch)) { VendorBranchRoute.router }
}
public enum EmployeeRoute: Sendable {
case form
case shared(SharedEmployeeRoute)
public static func delete(id: Employee.ID) -> Self {
.shared(.delete(id: id))
}
public static func get(id: Employee.ID) -> Self {
.shared(.get(id: id))
}
public static var index: Self { .shared(.index) }
static let rootPath = "employees"
public static let router = OneOf {
Route(.case(Self.form)) {
Path { rootPath; "create" }
Method.get
}
Route(.case(Self.shared)) {
SharedEmployeeRoute.router
}
}
}
public enum PurchaseOrderRoute: Sendable {
case form
case search(Search)
case shared(SharedPurchaseOrderRoute)
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)) {
SharedPurchaseOrderRoute.router
}
}
public enum Search: Sendable {
case index(context: Context? = nil, table: Bool? = nil)
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) { Optionally { 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
}
}
}
}
// TODO: Move into respective view routes.
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(SharedUserRoute)
static let rootPath = "users"
public static func delete(id: User.ID) -> Self {
.shared(.delete(id: id))
}
public static var index: Self { .shared(.index) }
public static let router = OneOf {
Route(.case(Self.form)) {
Path { rootPath; "create" }
Method.get
}
Route(.case(Self.shared)) {
SharedUserRoute.router
}
}
}
public enum VendorRoute: Sendable {
case form
case shared(SharedVendorRoute)
static let rootPath = "vendors"
public static let router = OneOf {
Route(.case(Self.form)) {
Path { rootPath; "create" }
Method.get
}
Route(.case(Self.shared)) {
SharedVendorRoute.router
}
}
}
// TODO: Add Select
public enum VendorBranchRoute: Sendable {
case index(vendorID: Vendor.ID)
case shared(SharedVendorBranchRoute)
public static func delete(id: VendorBranch.ID) -> Self {
.shared(.delete(id: id))
}
public static let router = OneOf {
Route(.case(Self.index(vendorID:))) {
Path { "vendors"; "branches" }
Method.get
Query {
Field("vendorID") { Vendor.ID.parser() }
}
}
Route(.case(Self.shared)) {
SharedVendorBranchRoute.router
}
}
}
}

View File

@@ -1,422 +0,0 @@
import CasePathsCore
import Foundation
@preconcurrency import URLRouting
public enum SiteRoute: Sendable {
case api(ApiRoute)
case health
case view(ViewRoute)
public static let router = OneOf {
Route(.case(Self.view)) { ViewRoute.router }
Route(.case(Self.health)) {
Path { "health" }
Method.get
}
Route(.case(Self.api)) { ApiRoute.router }
}
}
public enum ApiRoute: Sendable {
case employee(EmployeeApiRoute)
case purchaseOrder(PurchaseOrderApiRoute)
case user(UserApiRoute)
case vendor(VendorApiRoute)
case vendorBranch(VendorBranchApiRoute)
static let rootPath = Path { "api"; "v1" }
public static let router = OneOf {
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 {
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"
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.update(id:updates:))) {
Path { rootPath; UUID.parser() }
Method.put
Body(.json(Employee.Update.self))
}
}
}
public enum PurchaseOrderApiRoute: Sendable {
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 = "purchase-orders"
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; Digits() }
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() }
}
}
}
}
// TODO: Add logout.
public enum UserApiRoute: Sendable {
case create(User.Create)
case delete(id: User.ID)
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.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 VendorApiRoute: Sendable {
case create(Vendor.Create)
case delete(id: Vendor.ID)
case get(id: Vendor.ID)
case index(withBranches: Bool?)
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.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(withBranches:))) {
Path { rootPath }
Method.get
Query {
Field("branches", default: nil) {
Optionally { Bool.parser() }
}
}
}
Route(.case(Self.update(id:updates:))) {
Path { rootPath; Vendor.ID.parser() }
Method.put
Body(.json(Vendor.Update.self))
}
}
}
public enum VendorBranchApiRoute: 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 {
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 {
Field("vendorID", default: nil) { Optionally { VendorBranch.ID.parser() } }
}
}
Route(.case(Self.update(id:updates:))) {
Path { "vendors"; "branches"; VendorBranch.ID.parser() }
Method.put
Body(.json(VendorBranch.Update.self))
}
}
}
}
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
}
}
}
}