feat: Fixes release build failures and get's release dockerfile working.

This commit is contained in:
2025-01-24 16:47:00 -05:00
parent 90c6058d56
commit 978ce8c74f
38 changed files with 233 additions and 217 deletions

View File

@@ -27,8 +27,8 @@ COPY . .
# N.B.: The static version of jemalloc is incompatible with the static Swift runtime. # N.B.: The static version of jemalloc is incompatible with the static Swift runtime.
RUN swift build -c release \ RUN swift build -c release \
--product App \ --product App \
--static-swift-stdlib \ --static-swift-stdlib \
-Xlinker -ljemalloc -Xlinker -ljemalloc
# Switch to the staging area # Switch to the staging area
WORKDIR /staging WORKDIR /staging

View File

@@ -164,5 +164,5 @@ var swiftSettings: [SwiftSetting] { [
.enableExperimentalFeature("StrictConcurrency=complete"), .enableExperimentalFeature("StrictConcurrency=complete"),
.enableUpcomingFeature("ExistentialAny"), .enableUpcomingFeature("ExistentialAny"),
.enableUpcomingFeature("ConciseMagicFile"), .enableUpcomingFeature("ConciseMagicFile"),
.enableUpcomingFeature("ForwardTrailinClosures") .enableUpcomingFeature("ForwardTrailingClosures")
] } ] }

View File

@@ -16,36 +16,13 @@ extension SharedModels.ViewRoute {
var middleware: [any Middleware]? { var middleware: [any Middleware]? {
switch self { switch self {
// case .index: return viewProtectedMiddleware case .employee,
case let .employee(route): return route.middleware .purchaseOrder,
.user,
.vendor,
.vendorBranch:
return viewProtectedMiddleware
case .login: return nil case .login: return nil
case let .purchaseOrder(route): return route.middleware
case let .user(route): return route.middleware
case let .vendor(route): return route.middleware
case let .vendorBranch(route): return route.middleware
} }
} }
} }
extension SharedModels.ViewRoute.EmployeeRoute {
var middleware: [any Middleware]? { viewProtectedMiddleware }
}
extension SharedModels.ViewRoute.PurchaseOrderRoute {
var middleware: [any Middleware]? { viewProtectedMiddleware }
}
extension SharedModels.ViewRoute.UserRoute {
var middleware: [any Middleware]? {
viewProtectedMiddleware
}
}
extension SharedModels.ViewRoute.VendorRoute {
var middleware: [any Middleware]? { viewProtectedMiddleware }
}
extension SharedModels.ViewRoute.VendorBranchRoute {
var middleware: [any Middleware]? { viewProtectedMiddleware }
}

View File

@@ -6,8 +6,8 @@ import Vapor
public extension DatabaseClient.Employees { public extension DatabaseClient.Employees {
static func live(database: any Database) -> DatabaseClient.Employees { static func live(database: any Database) -> Self {
.init { create in Self { create in
let model = try create.toModel() let model = try create.toModel()
try await model.save(on: database) try await model.save(on: database)
return try model.toDTO() return try model.toDTO()
@@ -18,13 +18,13 @@ public extension DatabaseClient.Employees {
try await model.delete(on: database) try await model.delete(on: database)
} fetchAll: { request in } fetchAll: { request in
var query = EmployeeModel.query(on: database) var query = EmployeeModel.query(on: database)
.sort(\.$lastName) .sort(\EmployeeModel.$lastName)
switch request { switch request {
case .active: case .active:
query = query.filter(\.$active == true) query = query.filter(\EmployeeModel.$active == true)
case .inactive: case .inactive:
query = query.filter(\.$active == false) query = query.filter(\EmployeeModel.$active == false)
case .all: case .all:
break break
} }

View File

@@ -9,7 +9,8 @@ public extension DatabaseClient.PurchaseOrders {
.init { create in .init { create in
let model = try create.toModel() let model = try create.toModel()
try await model.save(on: database) try await model.save(on: database)
let fetched = try await PurchaseOrderModel.allQuery(on: database).filter(\.$id == model.id!).first()! let fetched = try await PurchaseOrderModel.allQuery(on: database)
.filter(\PurchaseOrderModel.$id == model.id!).first()!
return try fetched.toDTO() return try fetched.toDTO()
} fetchAll: { } fetchAll: {
try await PurchaseOrderModel.allQuery(on: database) try await PurchaseOrderModel.allQuery(on: database)
@@ -21,7 +22,7 @@ public extension DatabaseClient.PurchaseOrders {
.map { try $0.toDTO() } .map { try $0.toDTO() }
} get: { id in } get: { id in
try await PurchaseOrderModel.allQuery(on: database) try await PurchaseOrderModel.allQuery(on: database)
.filter(\.$id == id) .filter(\PurchaseOrderModel.$id == id)
.first() .first()
.map { try $0.toDTO() } .map { try $0.toDTO() }
} delete: { id in } delete: { id in
@@ -34,17 +35,17 @@ public extension DatabaseClient.PurchaseOrders {
switch search { switch search {
case let .employee(id): case let .employee(id):
return try await query.filter(\.$createdFor.$id == id) return try await query.filter(\PurchaseOrderModel.$createdFor.$id == id)
.paginate(page) .paginate(page)
.map { try $0.toDTO() } .map { try $0.toDTO() }
case let .customer(search): case let .customer(search):
return try await query.filter(\.$customer ~~ search) return try await query.filter(\PurchaseOrderModel.$customer ~~ search)
.paginate(page) .paginate(page)
.map { try $0.toDTO() } .map { try $0.toDTO() }
case let .vendor(id): case let .vendor(id):
return try await query.filter(\.$vendorBranch.$id == id) return try await query.filter(\PurchaseOrderModel.$vendorBranch.$id == id)
.paginate(page) .paginate(page)
.map { try $0.toDTO() } .map { try $0.toDTO() }
} }
@@ -190,11 +191,11 @@ final class PurchaseOrderModel: Model, Codable, @unchecked Sendable {
static func allQuery(on db: any Database) -> QueryBuilder<PurchaseOrderModel> { static func allQuery(on db: any Database) -> QueryBuilder<PurchaseOrderModel> {
PurchaseOrderModel.query(on: db) PurchaseOrderModel.query(on: db)
.sort(\.$id, .descending) .sort(\PurchaseOrderModel.$id, .descending)
.with(\.$createdBy) .with(\PurchaseOrderModel.$createdBy)
.with(\.$createdFor) .with(\PurchaseOrderModel.$createdFor)
.with(\.$vendorBranch) { branch in .with(\PurchaseOrderModel.$vendorBranch) { branch in
branch.with(\.$vendor) branch.with(\VendorBranchModel.$vendor)
} }
} }
} }

View File

@@ -28,9 +28,9 @@ public extension DatabaseClient.Users {
var query = UserModel.query(on: database) var query = UserModel.query(on: database)
if let username = login.username { if let username = login.username {
query = query.filter(\.$username == username) query = query.filter(\UserModel.$username == username)
} else { } else {
query = query.filter(\.$email == login.email!) query = query.filter(\UserModel.$email == login.email!)
} }
guard let user = try await query.first() else { guard let user = try await query.first() else {
@@ -265,7 +265,7 @@ public struct UserPasswordAuthenticator: AsyncBasicAuthenticator {
public func authenticate(basic: BasicAuthorization, for request: Request) async throws { public func authenticate(basic: BasicAuthorization, for request: Request) async throws {
guard let user = try await UserModel.query(on: request.db) guard let user = try await UserModel.query(on: request.db)
.filter(\.$username == basic.username) .filter(\UserModel.$username == basic.username)
.first(), .first(),
try Bcrypt.verify(basic.password, created: user.passwordHash) try Bcrypt.verify(basic.password, created: user.passwordHash)
else { else {
@@ -282,8 +282,8 @@ public struct UserTokenAuthenticator: AsyncBearerAuthenticator {
public func authenticate(bearer: BearerAuthorization, for request: Request) async throws { public func authenticate(bearer: BearerAuthorization, for request: Request) async throws {
guard let token = try await UserTokenModel.query(on: request.db) guard let token = try await UserTokenModel.query(on: request.db)
.filter(\.$value == bearer.token) .filter(\UserTokenModel.$value == bearer.token)
.with(\.$user) .with(\UserTokenModel.$user)
.first() .first()
else { else {
throw Abort(.unauthorized) throw Abort(.unauthorized)
@@ -299,7 +299,7 @@ public struct UserSessionAuthenticator: AsyncSessionAuthenticator {
public func authenticate(sessionID: User.SessionID, for request: Request) async throws { public func authenticate(sessionID: User.SessionID, for request: Request) async throws {
guard let user = try await UserModel.query(on: request.db) guard let user = try await UserModel.query(on: request.db)
.filter(\.$username == sessionID) .filter(\UserModel.$username == sessionID)
.first() .first()
else { else {
throw Abort(.unauthorized) throw Abort(.unauthorized)

View File

@@ -23,8 +23,8 @@ public extension DatabaseClient.VendorBranches {
break break
case let .for(vendorID: vendorID): case let .for(vendorID: vendorID):
let branches = try await VendorModel.query(on: db) let branches = try await VendorModel.query(on: db)
.filter(\.$id == vendorID) .filter(\VendorModel.$id == vendorID)
.with(\.$branches) .with(\VendorModel.$branches)
.first()? .first()?
.branches .branches
.map { try $0.toDTO() } .map { try $0.toDTO() }
@@ -36,7 +36,7 @@ public extension DatabaseClient.VendorBranches {
return try await query.all().map { try $0.toDTO() } return try await query.all().map { try $0.toDTO() }
} fetchAllWithDetail: { } fetchAllWithDetail: {
try await VendorBranchModel.query(on: db) try await VendorBranchModel.query(on: db)
.with(\.$vendor) .with(\VendorBranchModel.$vendor)
.all() .all()
.map { try $0.toDetail() } .map { try $0.toDetail() }
} get: { id in } get: { id in

View File

@@ -17,13 +17,13 @@ public extension DatabaseClient.Vendors {
} }
try await model.delete(on: db) try await model.delete(on: db)
} fetchAll: { request in } fetchAll: { request in
var query = VendorModel.query(on: db).sort(\.$name, .ascending) var query = VendorModel.query(on: db).sort(\VendorModel.$name, .ascending)
let withBranches = request == .withBranches let withBranches = request == .withBranches
switch request { switch request {
case .withBranches: case .withBranches:
query = query.with(\.$branches) query = query.with(\VendorModel.$branches)
case .all: case .all:
break break
} }
@@ -34,7 +34,7 @@ public extension DatabaseClient.Vendors {
var query = VendorModel.query(on: db).filter(\.$id == id) var query = VendorModel.query(on: db).filter(\.$id == id)
let withBranches = request == .withBranches let withBranches = request == .withBranches
if withBranches { if withBranches {
query = query.with(\.$branches) query = query.with(\VendorModel.$branches)
} }
return try await query.first().map { try $0.toDTO(includeBranches: withBranches) } return try await query.first().map { try $0.toDTO(includeBranches: withBranches) }
} update: { id, updates, withBranches in } update: { id, updates, withBranches in
@@ -47,8 +47,8 @@ public extension DatabaseClient.Vendors {
return try model.toDTO() return try model.toDTO()
} }
return try await VendorModel.query(on: db) return try await VendorModel.query(on: db)
.filter(\.$id == id) .filter(\VendorModel.$id == id)
.with(\.$branches) .with(\VendorModel.$branches)
.first()! .first()!
.toDTO() .toDTO()
} }

View File

@@ -8,6 +8,7 @@ import ViewController
public extension SharedModels.ViewRoute { public extension SharedModels.ViewRoute {
@Sendable
func view( func view(
isHtmxRequest: Bool, isHtmxRequest: Bool,
logger: Logger, logger: Logger,
@@ -55,9 +56,10 @@ extension SharedModels.ViewRoute.EmployeeRoute {
private func mainPage<C: HTML>( private func mainPage<C: HTML>(
_ html: C _ html: C
) async throws -> some SendableHTMLDocument where C: Sendable { ) async throws -> AnySendableHTML where C: Sendable {
@Dependency(\.database) var database @Dependency(\.database) var database
let employees = try await database.employees.fetchAll() let employees = try await database.employees.fetchAll()
// return EmployeeMainPage(employees: employees, html: html)
return MainPage(displayNav: true, route: .employees) { return MainPage(displayNav: true, route: .employees) {
div(.class("container")) { div(.class("container")) {
html html
@@ -66,6 +68,7 @@ extension SharedModels.ViewRoute.EmployeeRoute {
} }
} }
@Sendable
func view(isHtmxRequest: Bool) async throws -> AnySendableHTML { func view(isHtmxRequest: Bool) async throws -> AnySendableHTML {
@Dependency(\.database.employees) var employees @Dependency(\.database.employees) var employees
@@ -98,7 +101,7 @@ extension SharedModels.ViewRoute.EmployeeRoute {
extension SharedModels.ViewRoute.PurchaseOrderRoute { extension SharedModels.ViewRoute.PurchaseOrderRoute {
private func mainPage<C: HTML>( private func mainPage<C: HTML>(
_ html: C _ html: C
) async throws -> some SendableHTMLDocument where C: Sendable { ) async throws -> AnySendableHTML where C: Sendable {
@Dependency(\.database.purchaseOrders) var purchaseOrders @Dependency(\.database.purchaseOrders) var purchaseOrders
let page = try await purchaseOrders.fetchPage(.init(page: 1, per: 25)) let page = try await purchaseOrders.fetchPage(.init(page: 1, per: 25))
return MainPage(displayNav: true, route: .purchaseOrders) { return MainPage(displayNav: true, route: .purchaseOrders) {
@@ -109,6 +112,7 @@ extension SharedModels.ViewRoute.PurchaseOrderRoute {
} }
} }
@Sendable
func view(isHtmxRequest: Bool) async throws -> AnySendableHTML { func view(isHtmxRequest: Bool) async throws -> AnySendableHTML {
@Dependency(\.database.purchaseOrders) var purchaseOrders @Dependency(\.database.purchaseOrders) var purchaseOrders
@@ -146,7 +150,7 @@ extension SharedModels.ViewRoute.PurchaseOrderRoute {
extension SharedModels.ViewRoute.PurchaseOrderRoute.Search { extension SharedModels.ViewRoute.PurchaseOrderRoute.Search {
func mainPage(search: PurchaseOrderSearch = .init()) -> some SendableHTMLDocument { func mainPage(search: PurchaseOrderSearch = .init()) -> AnySendableHTML {
MainPage(displayNav: true, route: .purchaseOrders) { MainPage(displayNav: true, route: .purchaseOrders) {
div(.class("container"), .id("purchase-order-content")) { div(.class("container"), .id("purchase-order-content")) {
search search
@@ -158,6 +162,7 @@ extension SharedModels.ViewRoute.PurchaseOrderRoute.Search {
} }
} }
@Sendable
func view(isHtmxRequest: Bool) async throws -> AnySendableHTML { func view(isHtmxRequest: Bool) async throws -> AnySendableHTML {
@Dependency(\.database) var database @Dependency(\.database) var database
switch self { switch self {
@@ -177,7 +182,7 @@ extension SharedModels.ViewRoute.PurchaseOrderRoute.Search {
extension SharedModels.ViewRoute.UserRoute { extension SharedModels.ViewRoute.UserRoute {
private func mainPage<C: HTML>(_ html: C) async throws -> some SendableHTMLDocument where C: Sendable { private func mainPage<C: HTML>(_ html: C) async throws -> AnySendableHTML where C: Sendable {
@Dependency(\.database) var database @Dependency(\.database) var database
let users = try await database.users.fetchAll() let users = try await database.users.fetchAll()
return MainPage(displayNav: true, route: .users) { return MainPage(displayNav: true, route: .users) {
@@ -188,6 +193,7 @@ extension SharedModels.ViewRoute.UserRoute {
} }
} }
@Sendable
func view(isHtmxRequest: Bool) async throws -> AnySendableHTML { func view(isHtmxRequest: Bool) async throws -> AnySendableHTML {
@Dependency(\.database.users) var users @Dependency(\.database.users) var users
@@ -215,7 +221,7 @@ extension SharedModels.ViewRoute.UserRoute {
} }
extension SharedModels.ViewRoute.VendorRoute { extension SharedModels.ViewRoute.VendorRoute {
private func mainPage<C: HTML>(_ html: C) async throws -> some SendableHTMLDocument where C: Sendable { private func mainPage<C: HTML>(_ html: C) async throws -> AnySendableHTML where C: Sendable {
@Dependency(\.database) var database @Dependency(\.database) var database
let vendors = try await database.vendors.fetchAll(.withBranches) let vendors = try await database.vendors.fetchAll(.withBranches)
return MainPage(displayNav: true, route: .vendors) { return MainPage(displayNav: true, route: .vendors) {
@@ -226,6 +232,7 @@ extension SharedModels.ViewRoute.VendorRoute {
} }
} }
@Sendable
func view(isHtmxRequest: Bool) async throws -> AnySendableHTML { func view(isHtmxRequest: Bool) async throws -> AnySendableHTML {
@Dependency(\.database) var database @Dependency(\.database) var database
@@ -262,6 +269,7 @@ extension SharedModels.ViewRoute.VendorRoute {
extension SharedModels.ViewRoute.VendorBranchRoute { extension SharedModels.ViewRoute.VendorBranchRoute {
@Sendable
func view(isHtmxRequest: Bool) async throws -> AnySendableHTML { func view(isHtmxRequest: Bool) async throws -> AnySendableHTML {
@Dependency(\.database) var database @Dependency(\.database) var database
@@ -286,6 +294,7 @@ extension SharedModels.ViewRoute.VendorBranchRoute {
extension SharedModels.ViewRoute.PurchaseOrderRoute.Search.Request { extension SharedModels.ViewRoute.PurchaseOrderRoute.Search.Request {
@Sendable
func toDatabaseQuery() throws -> PurchaseOrder.SearchContext { func toDatabaseQuery() throws -> PurchaseOrder.SearchContext {
switch context { switch context {
case .employee: case .employee:
@@ -308,6 +317,8 @@ extension SharedModels.ViewRoute.PurchaseOrderRoute.Search.Request {
} }
extension SharedModels.ViewRoute.SelectContext { extension SharedModels.ViewRoute.SelectContext {
@Sendable
func toHTML(employees: [Employee]) -> EmployeeSelect { func toHTML(employees: [Employee]) -> EmployeeSelect {
switch self { switch self {
case .purchaseOrderForm: case .purchaseOrderForm:
@@ -327,8 +338,9 @@ extension SharedModels.ViewRoute.SelectContext {
} }
} }
@Sendable
private func render<C: HTML>( private func render<C: HTML>(
_ mainPage: (C) async throws -> any SendableHTMLDocument, _ mainPage: (C) async throws -> AnySendableHTML,
_ isHtmxRequest: Bool, _ isHtmxRequest: Bool,
@HTMLBuilder html: () -> C @HTMLBuilder html: () -> C
) async rethrows -> AnySendableHTML where C: Sendable { ) async rethrows -> AnySendableHTML where C: Sendable {
@@ -338,8 +350,9 @@ private func render<C: HTML>(
return html() return html()
} }
@Sendable
private func render<C: HTML>( private func render<C: HTML>(
_ mainPage: (C) async throws -> any SendableHTMLDocument, _ mainPage: (C) async throws -> AnySendableHTML,
_ isHtmxRequest: Bool, _ isHtmxRequest: Bool,
_ html: @autoclosure @escaping () -> C _ html: @autoclosure @escaping () -> C
) async rethrows -> AnySendableHTML where C: Sendable { ) async rethrows -> AnySendableHTML where C: Sendable {

View File

@@ -2,7 +2,7 @@ import Elementary
import ElementaryHTMX import ElementaryHTMX
import SharedModels import SharedModels
struct EmployeeTable: HTML { struct EmployeeTable: HTML, Sendable {
let employees: [Employee] let employees: [Employee]
var content: some HTML { var content: some HTML {
@@ -29,7 +29,7 @@ struct EmployeeTable: HTML {
} }
} }
struct Row: HTML { struct Row: HTML, Sendable {
let employee: Employee let employee: Employee
var content: some HTML { var content: some HTML {

View File

@@ -3,24 +3,29 @@ import ElementaryHTMX
import SharedModels import SharedModels
extension HTMLAttribute.hx { extension HTMLAttribute.hx {
@Sendable
static func get(route: SharedModels.ViewRoute) -> HTMLAttribute { static func get(route: SharedModels.ViewRoute) -> HTMLAttribute {
get(SharedModels.ViewRoute.router.path(for: route)) get(SharedModels.ViewRoute.router.path(for: route))
} }
@Sendable
static func post(route: SharedModels.ViewRoute) -> HTMLAttribute { static func post(route: SharedModels.ViewRoute) -> HTMLAttribute {
post(SharedModels.ViewRoute.router.path(for: route)) post(SharedModels.ViewRoute.router.path(for: route))
} }
@Sendable
static func put(route: SharedModels.ViewRoute) -> HTMLAttribute { static func put(route: SharedModels.ViewRoute) -> HTMLAttribute {
put(SharedModels.ViewRoute.router.path(for: route)) put(SharedModels.ViewRoute.router.path(for: route))
} }
@Sendable
static func delete(route: SharedModels.ApiRoute) -> HTMLAttribute { static func delete(route: SharedModels.ApiRoute) -> HTMLAttribute {
delete(SharedModels.ApiRoute.router.path(for: route)) delete(SharedModels.ApiRoute.router.path(for: route))
} }
} }
extension HTMLAttribute.hx { extension HTMLAttribute.hx {
@Sendable
static func target(_ target: HXTarget) -> HTMLAttribute { static func target(_ target: HXTarget) -> HTMLAttribute {
Self.target(target.selector) Self.target(target.selector)
} }
@@ -28,37 +33,43 @@ extension HTMLAttribute.hx {
extension HTMLAttribute.hx where Tag: HTMLTrait.Attributes.Global { extension HTMLAttribute.hx where Tag: HTMLTrait.Attributes.Global {
@Sendable
static func on(_ event: HXOnEvent, value: String) -> HTMLAttribute { static func on(_ event: HXOnEvent, value: String) -> HTMLAttribute {
HTMLAttribute.custom(name: "hx-on::\(event.rawValue)", value: value) HTMLAttribute.custom(name: "hx-on::\(event.rawValue)", value: value)
} }
@Sendable
static func on(_ event: HXOnEvent, _ value: HXOnValue) -> HTMLAttribute { static func on(_ event: HXOnEvent, _ value: HXOnValue) -> HTMLAttribute {
on(event, value: value.value) on(event, value: value.value)
} }
@Sendable
static func on(_ event: HXOnEvent, _ values: HXOnValue...) -> HTMLAttribute { static func on(_ event: HXOnEvent, _ values: HXOnValue...) -> HTMLAttribute {
on(event, value: values.value) on(event, value: values.value)
} }
} }
enum HXOnEvent: String { enum HXOnEvent: String, Sendable {
case afterRequest = "after-request" case afterRequest = "after-request"
} }
indirect enum HXOnValue { indirect enum HXOnValue: Sendable {
case ifSuccessful([Self]) case ifSuccessful([Self])
case resetForm case resetForm
case setWindowLocation(String) case setWindowLocation(String)
case toggleContent(id: String) case toggleContent(id: String)
@Sendable
static func toggleContent(_ toggle: Toggle) -> Self { static func toggleContent(_ toggle: Toggle) -> Self {
toggleContent(id: toggle.rawValue) toggleContent(id: toggle.rawValue)
} }
@Sendable
static func setWindowLocation(to route: ViewRoute) -> Self { static func setWindowLocation(to route: ViewRoute) -> Self {
setWindowLocation(ViewRoute.router.path(for: route)) setWindowLocation(ViewRoute.router.path(for: route))
} }
@Sendable
static func ifSuccessful(_ values: Self...) -> Self { static func ifSuccessful(_ values: Self...) -> Self {
.ifSuccessful(values) .ifSuccessful(values)
} }
@@ -76,7 +87,7 @@ indirect enum HXOnValue {
} }
} }
enum Toggle: String { enum Toggle: String, Sendable {
case float case float
} }
} }
@@ -89,12 +100,14 @@ extension Array where Element == HXOnValue {
} }
extension HTMLAttribute where Tag: HTMLTrait.Attributes.Global { extension HTMLAttribute where Tag: HTMLTrait.Attributes.Global {
@Sendable
static func id(_ key: IDKey) -> Self { static func id(_ key: IDKey) -> Self {
id(key.description) id(key.description)
} }
} }
enum IDKey: CustomStringConvertible { enum IDKey: CustomStringConvertible, Sendable {
case branch(Branch) case branch(Branch)
case employee(Employee) case employee(Employee)
case float case float
@@ -115,7 +128,7 @@ enum IDKey: CustomStringConvertible {
} }
} }
enum Branch: CustomStringConvertible { enum Branch: CustomStringConvertible, Sendable {
case list case list
case form case form
case row(id: VendorBranch.ID) case row(id: VendorBranch.ID)
@@ -129,7 +142,7 @@ enum IDKey: CustomStringConvertible {
} }
} }
enum Employee: CustomStringConvertible { enum Employee: CustomStringConvertible, Sendable {
case table case table
case row(id: SharedModels.Employee.ID) case row(id: SharedModels.Employee.ID)
@@ -141,7 +154,7 @@ enum IDKey: CustomStringConvertible {
} }
} }
enum PurchaseOrder: CustomStringConvertible { enum PurchaseOrder: CustomStringConvertible, Sendable {
case content case content
case row(id: SharedModels.PurchaseOrder.ID) case row(id: SharedModels.PurchaseOrder.ID)
case search case search
@@ -157,7 +170,7 @@ enum IDKey: CustomStringConvertible {
} }
} }
enum User: CustomStringConvertible { enum User: CustomStringConvertible, Sendable {
case form case form
case row(id: SharedModels.User.ID) case row(id: SharedModels.User.ID)
case table case table
@@ -171,7 +184,7 @@ enum IDKey: CustomStringConvertible {
} }
} }
enum Vendor: CustomStringConvertible { enum Vendor: CustomStringConvertible, Sendable {
case form case form
case row(id: SharedModels.Vendor.ID) case row(id: SharedModels.Vendor.ID)
@@ -185,7 +198,7 @@ enum IDKey: CustomStringConvertible {
} }
} }
enum HXTarget: CustomStringConvertible { enum HXTarget: CustomStringConvertible, Sendable {
case body case body
case id(IDKey) case id(IDKey)
case this case this

View File

@@ -49,7 +49,7 @@ extension MainPage where Inner == LoggedIn {
} }
} }
struct LoggedIn: HTML { struct LoggedIn: HTML, Sendable {
let next: String? let next: String?
var content: some HTML { var content: some HTML {
div( div(
@@ -72,7 +72,7 @@ struct LoggedIn: HTML {
} }
} }
struct RouteHeaderView: HTML { struct RouteHeaderView: HTML, Sendable {
let title: String let title: String
let description: String let description: String
@@ -95,7 +95,7 @@ struct RouteHeaderView: HTML {
} }
} }
enum ViewRoute: String { enum ViewRoute: String, Sendable {
case employees case employees
case login case login

View File

@@ -3,7 +3,7 @@ import Elementary
import ElementaryHTMX import ElementaryHTMX
import SharedModels import SharedModels
struct PurchaseOrderForm: HTML { struct PurchaseOrderForm: HTML, Sendable {
@Dependency(\.dateFormatter) var dateFormatter @Dependency(\.dateFormatter) var dateFormatter

View File

@@ -3,7 +3,7 @@ import ElementaryHTMX
import SharedModels import SharedModels
import Vapor import Vapor
struct PurchaseOrderSearch: HTML { struct PurchaseOrderSearch: HTML, Sendable {
typealias Context = SharedModels.ViewRoute.PurchaseOrderRoute.Search.Context typealias Context = SharedModels.ViewRoute.PurchaseOrderRoute.Search.Context

View File

@@ -4,7 +4,7 @@ import Fluent
import SharedModels import SharedModels
import Vapor import Vapor
struct PurchaseOrderTable: HTML { struct PurchaseOrderTable: HTML, Sendable {
typealias SearchContext = SharedModels.ViewRoute.PurchaseOrderRoute.Search.Context typealias SearchContext = SharedModels.ViewRoute.PurchaseOrderRoute.Search.Context
let page: Page<PurchaseOrder> let page: Page<PurchaseOrder>
@@ -72,7 +72,7 @@ struct PurchaseOrderTable: HTML {
} }
// Produces only the rows for the given page // Produces only the rows for the given page
struct Rows: HTML { struct Rows: HTML, Sendable {
let page: Page<PurchaseOrder> let page: Page<PurchaseOrder>
var content: some HTML { var content: some HTML {
@@ -98,7 +98,7 @@ struct PurchaseOrderTable: HTML {
} }
// A single row. // A single row.
struct Row: HTML { struct Row: HTML, Sendable {
let purchaseOrder: PurchaseOrder let purchaseOrder: PurchaseOrder
var content: some HTML<HTMLTag.tr> { var content: some HTML<HTMLTag.tr> {
@@ -124,7 +124,7 @@ struct PurchaseOrderTable: HTML {
} }
} }
enum Context: String { enum Context: String, Sendable {
case `default` case `default`
case search case search
} }

View File

@@ -58,7 +58,7 @@ struct UserForm: HTML, Sendable {
} }
} }
enum Context: Equatable { enum Context: Equatable, Sendable {
case create case create
case login(next: String?) case login(next: String?)

View File

@@ -4,7 +4,7 @@ import Elementary
import ElementaryHTMX import ElementaryHTMX
import SharedModels import SharedModels
struct UserTable: HTML { struct UserTable: HTML, Sendable {
let users: [User] let users: [User]
@@ -32,7 +32,7 @@ struct UserTable: HTML {
} }
} }
struct Row: HTML { struct Row: HTML, Sendable {
let user: User let user: User
init(user: User) { init(user: User) {

View File

@@ -2,7 +2,8 @@ import Elementary
import SharedModels import SharedModels
import URLRouting import URLRouting
struct ToggleFormButton: HTML { // TODO: Remove.
struct ToggleFormButton: HTML, Sendable {
var content: some HTML<HTMLTag.a> { var content: some HTML<HTMLTag.a> {
a(.href("javascript:void(0)"), .on(.click, "toggleContent('form')"), .class("btn-add")) { a(.href("javascript:void(0)"), .on(.click, "toggleContent('form')"), .class("btn-add")) {
"+" "+"
@@ -12,20 +13,24 @@ struct ToggleFormButton: HTML {
enum Button { enum Button {
@Sendable
static func add() -> some HTML<HTMLTag.button> { static func add() -> some HTML<HTMLTag.button> {
button(.class("btn btn-add")) { "+" } button(.class("btn btn-add")) { "+" }
} }
@Sendable
static func danger<C: HTML>(@HTMLBuilder body: () -> C) -> some HTML<HTMLTag.button> { static func danger<C: HTML>(@HTMLBuilder body: () -> C) -> some HTML<HTMLTag.button> {
button(.class("danger")) { body() } button(.class("danger")) { body() }
} }
@Sendable
static func close(id: String, resetURL: String? = nil) -> some HTML<HTMLTag.button> { static func close(id: String, resetURL: String? = nil) -> some HTML<HTMLTag.button> {
button(.class("btn-close"), .on(.click, makeOnClick(id, resetURL))) { button(.class("btn-close"), .on(.click, makeOnClick(id, resetURL))) {
"x" "x"
} }
} }
@Sendable
static func close(id: IDKey, resetURL route: ViewRoute? = nil) -> some HTML<HTMLTag.button> { static func close(id: IDKey, resetURL route: ViewRoute? = nil) -> some HTML<HTMLTag.button> {
close( close(
id: id.description, id: id.description,
@@ -33,16 +38,19 @@ enum Button {
) )
} }
@Sendable
static func update() -> some HTML<HTMLTag.button> { static func update() -> some HTML<HTMLTag.button> {
button(.class("btn-update")) { "Update" } button(.class("btn-update")) { "Update" }
} }
@Sendable
static func detail() -> some HTML<HTMLTag.button> { static func detail() -> some HTML<HTMLTag.button> {
button(.class("btn-detail")) { button(.class("btn-detail")) {
"" ""
} }
} }
@Sendable
private static func makeOnClick(_ id: String, _ resetURL: String?) -> String { private static func makeOnClick(_ id: String, _ resetURL: String?) -> String {
let output = "toggleContent('\(id)');" let output = "toggleContent('\(id)');"
if let resetURL { if let resetURL {

View File

@@ -49,7 +49,7 @@ struct Float<C: HTML, B: HTML>: HTML {
} }
} }
struct DefaultCloseButton: HTML { struct DefaultCloseButton: HTML, Sendable {
let id: String let id: String
let resetURL: String? let resetURL: String?

View File

@@ -3,7 +3,7 @@ import ElementaryHTMX
import SharedModels import SharedModels
import Vapor import Vapor
struct EmployeeSelect: HTML { struct EmployeeSelect: HTML, Sendable {
let employees: [Employee]? let employees: [Employee]?
let context: ViewRoute.SelectContext let context: ViewRoute.SelectContext
@@ -40,7 +40,7 @@ struct EmployeeSelect: HTML {
} }
struct VendorBranchSelect: HTML { struct VendorBranchSelect: HTML, Sendable {
let branches: [VendorBranch.Detail]? let branches: [VendorBranch.Detail]?
let context: ViewRoute.SelectContext let context: ViewRoute.SelectContext

View File

@@ -2,7 +2,7 @@ import Elementary
import ElementaryHTMX import ElementaryHTMX
import SharedModels import SharedModels
struct VendorBranchForm: HTML { struct VendorBranchForm: HTML, Sendable {
let vendorID: Vendor.ID let vendorID: Vendor.ID
var content: some HTML { var content: some HTML {

View File

@@ -2,7 +2,7 @@ import Elementary
import ElementaryHTMX import ElementaryHTMX
import SharedModels import SharedModels
struct VendorBranchList: HTML { struct VendorBranchList: HTML, Sendable {
let vendorID: Vendor.ID let vendorID: Vendor.ID
let branches: [VendorBranch]? let branches: [VendorBranch]?
@@ -25,7 +25,7 @@ struct VendorBranchList: HTML {
} }
} }
struct Row: HTML { struct Row: HTML, Sendable {
let branch: VendorBranch let branch: VendorBranch
var content: some HTML<HTMLTag.li> { var content: some HTML<HTMLTag.li> {

View File

@@ -2,7 +2,7 @@ import Elementary
import ElementaryHTMX import ElementaryHTMX
import SharedModels import SharedModels
struct VendorDetail: HTML { struct VendorDetail: HTML, Sendable {
let vendor: Vendor let vendor: Vendor

View File

@@ -2,7 +2,7 @@ import Elementary
import ElementaryHTMX import ElementaryHTMX
import SharedModels import SharedModels
struct VendorForm: HTML { struct VendorForm: HTML, Sendable {
let context: Context let context: Context
var vendor: Vendor? { context.vendor } var vendor: Vendor? { context.vendor }
@@ -15,7 +15,7 @@ struct VendorForm: HTML {
init() { self.init(.float(nil)) } init() { self.init(.float(nil)) }
enum Context { enum Context: Sendable {
case float(Vendor? = nil, shouldShow: Bool = false) case float(Vendor? = nil, shouldShow: Bool = false)
case formOnly(Vendor) case formOnly(Vendor)

View File

@@ -2,7 +2,7 @@ import Elementary
import ElementaryHTMX import ElementaryHTMX
import SharedModels import SharedModels
struct VendorTable: HTML { struct VendorTable: HTML, Sendable {
let vendors: [Vendor] let vendors: [Vendor]
var content: some HTML { var content: some HTML {
@@ -30,7 +30,7 @@ struct VendorTable: HTML {
} }
} }
struct Row: HTML { struct Row: HTML, Sendable {
let vendor: Vendor let vendor: Vendor
var content: some HTML<HTMLTag.tr> { var content: some HTML<HTMLTag.tr> {

View File

@@ -101,80 +101,86 @@ struct ViewControllerTests {
@Test @Test
func userViews() async throws { func userViews() async throws {
try await withDependencies { try await withSnapshotTesting(record: record) {
$0.dateFormatter = .liveValue try await withDependencies {
$0.database.users = .mock $0.dateFormatter = .liveValue
$0.viewController = .liveValue $0.database.users = .mock
} operation: { $0.viewController = .liveValue
@Dependency(\.database) var database } operation: {
@Dependency(\.viewController) var viewController @Dependency(\.database) var database
@Dependency(\.viewController) var viewController
var htmlString = try await viewController.render(.user(.index)) var htmlString = try await viewController.render(.user(.index))
assertSnapshot(of: htmlString, as: .html) assertSnapshot(of: htmlString, as: .html)
htmlString = try await viewController.render(.user(.form)) htmlString = try await viewController.render(.user(.form))
assertSnapshot(of: htmlString, as: .html) assertSnapshot(of: htmlString, as: .html)
htmlString = try await viewController.render(.user(.create(.mock))) htmlString = try await viewController.render(.user(.create(.mock)))
assertSnapshot(of: htmlString, as: .html) assertSnapshot(of: htmlString, as: .html)
htmlString = try await viewController.render(.user(.get(id: UUID(0)))) htmlString = try await viewController.render(.user(.get(id: UUID(0))))
assertSnapshot(of: htmlString, as: .html) assertSnapshot(of: htmlString, as: .html)
htmlString = try await viewController.render(.user(.update(id: UUID(0), updates: .mock))) htmlString = try await viewController.render(.user(.update(id: UUID(0), updates: .mock)))
assertSnapshot(of: htmlString, as: .html) assertSnapshot(of: htmlString, as: .html)
}
} }
} }
@Test @Test
func vendorViews() async throws { func vendorViews() async throws {
try await withDependencies { try await withSnapshotTesting(record: record) {
$0.dateFormatter = .liveValue try await withDependencies {
$0.database.vendors = .mock $0.dateFormatter = .liveValue
$0.viewController = .liveValue $0.database.vendors = .mock
} operation: { $0.viewController = .liveValue
@Dependency(\.database) var database } operation: {
@Dependency(\.viewController) var viewController @Dependency(\.database) var database
@Dependency(\.viewController) var viewController
var htmlString = try await viewController.render(.vendor(.index)) var htmlString = try await viewController.render(.vendor(.index))
assertSnapshot(of: htmlString, as: .html) assertSnapshot(of: htmlString, as: .html)
htmlString = try await viewController.render(.vendor(.form)) htmlString = try await viewController.render(.vendor(.form))
assertSnapshot(of: htmlString, as: .html) assertSnapshot(of: htmlString, as: .html)
htmlString = try await viewController.render(.vendor(.create(.mock))) htmlString = try await viewController.render(.vendor(.create(.mock)))
assertSnapshot(of: htmlString, as: .html) assertSnapshot(of: htmlString, as: .html)
htmlString = try await viewController.render(.vendor(.get(id: UUID(0)))) htmlString = try await viewController.render(.vendor(.get(id: UUID(0))))
assertSnapshot(of: htmlString, as: .html) assertSnapshot(of: htmlString, as: .html)
htmlString = try await viewController.render(.vendor(.update(id: UUID(0), updates: .mock))) htmlString = try await viewController.render(.vendor(.update(id: UUID(0), updates: .mock)))
assertSnapshot(of: htmlString, as: .html) assertSnapshot(of: htmlString, as: .html)
}
} }
} }
@Test @Test
func vendorBranchViews() async throws { func vendorBranchViews() async throws {
try await withDependencies { try await withSnapshotTesting(record: record) {
$0.dateFormatter = .liveValue try await withDependencies {
$0.database.vendors = .mock $0.dateFormatter = .liveValue
$0.database.vendorBranches = .mock $0.database.vendors = .mock
$0.viewController = .liveValue $0.database.vendorBranches = .mock
} operation: { $0.viewController = .liveValue
@Dependency(\.database) var database } operation: {
@Dependency(\.viewController) var viewController @Dependency(\.database) var database
@Dependency(\.viewController) var viewController
var htmlString = try await viewController.render(.vendorBranch(.index(for: UUID(0)))) var htmlString = try await viewController.render(.vendorBranch(.index(for: UUID(0))))
assertSnapshot(of: htmlString, as: .html) assertSnapshot(of: htmlString, as: .html)
htmlString = try await viewController.render(.vendorBranch(.select(context: .purchaseOrderSearch))) htmlString = try await viewController.render(.vendorBranch(.select(context: .purchaseOrderSearch)))
assertSnapshot(of: htmlString, as: .html) assertSnapshot(of: htmlString, as: .html)
htmlString = try await viewController.render(.vendorBranch(.select(context: .purchaseOrderForm))) htmlString = try await viewController.render(.vendorBranch(.select(context: .purchaseOrderForm)))
assertSnapshot(of: htmlString, as: .html) assertSnapshot(of: htmlString, as: .html)
htmlString = try await viewController.render(.vendorBranch(.create(.mock))) htmlString = try await viewController.render(.vendorBranch(.create(.mock)))
assertSnapshot(of: htmlString, as: .html) assertSnapshot(of: htmlString, as: .html)
}
} }
} }
} }
@@ -182,14 +188,12 @@ struct ViewControllerTests {
extension ViewController { extension ViewController {
func render(_ route: ViewRoute) async throws -> String { func render(_ route: ViewRoute) async throws -> String {
guard let html = try await view( let html = try await view(
for: route, for: route,
isHtmxRequest: true, isHtmxRequest: true,
logger: .init(label: "tests"), logger: .init(label: "tests"),
authenticate: { _ in } authenticate: { _ in }
) else { )
throw TestError()
}
return html.renderFormatted() return html.renderFormatted()
} }

View File

@@ -10,7 +10,7 @@
</div> </div>
<div class="btn-row"> <div class="btn-row">
<button type="submit" class="btn-primary">Update</button> <button type="submit" class="btn-primary">Update</button>
<button class="danger" hx-confirm="Are you sure you want to delete this employee?" hx-delete="/employees/00000000-0000-0000-0000-000000000000" hx-target="#employee-00000000-0000-0000-0000-000000000000" hx-swap="outerHTML transition:true swap:1s">Delete</button> <button class="danger" hx-confirm="Are you sure you want to delete this employee?" hx-delete="/api/v1/employees/00000000-0000-0000-0000-000000000000" hx-target="#employee-00000000-0000-0000-0000-000000000000" hx-swap="outerHTML transition:true swap:1s">Delete</button>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -12,7 +12,7 @@
<div class="row"><span class="label col-2">Created:</span><span class="date col-4"></span><span class="label col-2">Updated:</span><span class="date col-4"></span></div> <div class="row"><span class="label col-2">Created:</span><span class="date col-4"></span><span class="label col-2">Updated:</span><span class="date col-4"></span></div>
<div class="btn-row user-buttons"> <div class="btn-row user-buttons">
<button type="submit" class="btn-secondary">Update</button> <button type="submit" class="btn-secondary">Update</button>
<button class="danger" hx-delete="/users/00000000-0000-0000-0000-000000000000" hx-trigger="click" hx-swap="outerHTML" hx-target="#user-00000000-0000-0000-0000-000000000000" hx-confirm="Are you sure you want to delete this user?" hx-on::after-request="toggleContent('float'); window.location.href='/users';">Delete</button> <button class="danger" hx-delete="/api/v1/users/00000000-0000-0000-0000-000000000000" hx-trigger="click" hx-swap="outerHTML" hx-target="#user-00000000-0000-0000-0000-000000000000" hx-confirm="Are you sure you want to delete this user?" hx-on::after-request="toggleContent('float'); window.location.href='/users';">Delete</button>
</div> </div>
</form> </form>
</div> </div>

View File

@@ -1,7 +1,7 @@
<ul id="branch-list"> <ul id="branch-list">
<li id="branch-00000000-0000-0000-0000-000000000001" class="branch-row"> <li id="branch-00000000-0000-0000-0000-000000000001" class="branch-row">
<span class="label">Mock</span> <span class="label">Mock</span>
<button class="btn" hx-delete="/vendors/branches/00000000-0000-0000-0000-000000000001" hx-target="#branch-00000000-0000-0000-0000-000000000001" hx-swap="outerHTML transition:true swap:0.5s"> <button class="btn" hx-delete="/api/v1/vendors/branches/00000000-0000-0000-0000-000000000001" hx-target="#branch-00000000-0000-0000-0000-000000000001" hx-swap="outerHTML transition:true swap:0.5s">
<img src="/images/trash-can.svg" width="30" height="30" style="margin-top: 5px;"> <img src="/images/trash-can.svg" width="30" height="30" style="margin-top: 5px;">
</button> </button>
</li> </li>

View File

@@ -1,6 +1,6 @@
<li id="branch-00000000-0000-0000-0000-000000000001" class="branch-row"> <li id="branch-00000000-0000-0000-0000-000000000001" class="branch-row">
<span class="label">Mock</span> <span class="label">Mock</span>
<button class="btn" hx-delete="/vendors/branches/00000000-0000-0000-0000-000000000001" hx-target="#branch-00000000-0000-0000-0000-000000000001" hx-swap="outerHTML transition:true swap:0.5s"> <button class="btn" hx-delete="/api/v1/vendors/branches/00000000-0000-0000-0000-000000000001" hx-target="#branch-00000000-0000-0000-0000-000000000001" hx-swap="outerHTML transition:true swap:0.5s">
<img src="/images/trash-can.svg" width="30" height="30" style="margin-top: 5px;"> <img src="/images/trash-can.svg" width="30" height="30" style="margin-top: 5px;">
</button> </button>
</li> </li>

View File

@@ -6,7 +6,7 @@
<form id="vendor-form" hx-put="/vendors/00000000-0000-0000-0000-000000000000" hx-target="#content" hx-swap="outerHTML"> <form id="vendor-form" hx-put="/vendors/00000000-0000-0000-0000-000000000000" hx-target="#content" hx-swap="outerHTML">
<div class="row"> <div class="row">
<input type="text" class="col-9" id="vendor-name" name="name" value="Test" placeholder="Vendor Name" hx-put="/vendors/00000000-0000-0000-0000-000000000000" hx-trigger="keyup changed delay:500ms" required> <input type="text" class="col-9" id="vendor-name" name="name" value="Test" placeholder="Vendor Name" hx-put="/vendors/00000000-0000-0000-0000-000000000000" hx-trigger="keyup changed delay:500ms" required>
<button class="danger" style="font-size: 1.25em; padding: 10px 20px; border-radius: 10px;" hx-delete="/vendors/00000000-0000-0000-0000-000000000000" hx-confirm="Are you sure you want to delete this vendor?" hx-target="#vendor-00000000-0000-0000-0000-000000000000" hx-swap="outerHTML transition:true swap:1s" hx-on::after-request="if(event.detail.successful) toggleContent('float'); window.location.href='/vendors';">Delete</button> <button class="danger" style="font-size: 1.25em; padding: 10px 20px; border-radius: 10px;" hx-delete="/api/v1/vendors/00000000-0000-0000-0000-000000000000" hx-confirm="Are you sure you want to delete this vendor?" hx-target="#vendor-00000000-0000-0000-0000-000000000000" hx-swap="outerHTML transition:true swap:1s" hx-on::after-request="if(event.detail.successful) toggleContent('float'); window.location.href='/vendors';">Delete</button>
<button type="submit" class="btn-primary" style="float: right">Update</button> <button type="submit" class="btn-primary" style="float: right">Update</button>
</div> </div>
</form> </form>

View File

@@ -5,7 +5,7 @@
<form id="vendor-form" hx-put="/vendors/00000000-0000-0000-0000-000000000000" hx-target="#content" hx-swap="outerHTML"> <form id="vendor-form" hx-put="/vendors/00000000-0000-0000-0000-000000000000" hx-target="#content" hx-swap="outerHTML">
<div class="row"> <div class="row">
<input type="text" class="col-9" id="vendor-name" name="name" value="Test" placeholder="Vendor Name" hx-put="/vendors/00000000-0000-0000-0000-000000000000" hx-trigger="keyup changed delay:500ms" required> <input type="text" class="col-9" id="vendor-name" name="name" value="Test" placeholder="Vendor Name" hx-put="/vendors/00000000-0000-0000-0000-000000000000" hx-trigger="keyup changed delay:500ms" required>
<button class="danger" style="font-size: 1.25em; padding: 10px 20px; border-radius: 10px;" hx-delete="/vendors/00000000-0000-0000-0000-000000000000" hx-confirm="Are you sure you want to delete this vendor?" hx-target="#vendor-00000000-0000-0000-0000-000000000000" hx-swap="outerHTML transition:true swap:1s" hx-on::after-request="if(event.detail.successful) toggleContent('float'); window.location.href='/vendors';">Delete</button> <button class="danger" style="font-size: 1.25em; padding: 10px 20px; border-radius: 10px;" hx-delete="/api/v1/vendors/00000000-0000-0000-0000-000000000000" hx-confirm="Are you sure you want to delete this vendor?" hx-target="#vendor-00000000-0000-0000-0000-000000000000" hx-swap="outerHTML transition:true swap:1s" hx-on::after-request="if(event.detail.successful) toggleContent('float'); window.location.href='/vendors';">Delete</button>
<button type="submit" class="btn-primary" style="float: right">Update</button> <button type="submit" class="btn-primary" style="float: right">Update</button>
</div> </div>
</form> </form>

View File

@@ -5,7 +5,7 @@
<form id="vendor-form" hx-put="/vendors/00000000-0000-0000-0000-000000000000" hx-target="#content" hx-swap="outerHTML"> <form id="vendor-form" hx-put="/vendors/00000000-0000-0000-0000-000000000000" hx-target="#content" hx-swap="outerHTML">
<div class="row"> <div class="row">
<input type="text" class="col-9" id="vendor-name" name="name" value="Test" placeholder="Vendor Name" hx-put="/vendors/00000000-0000-0000-0000-000000000000" hx-trigger="keyup changed delay:500ms" required> <input type="text" class="col-9" id="vendor-name" name="name" value="Test" placeholder="Vendor Name" hx-put="/vendors/00000000-0000-0000-0000-000000000000" hx-trigger="keyup changed delay:500ms" required>
<button class="danger" style="font-size: 1.25em; padding: 10px 20px; border-radius: 10px;" hx-delete="/vendors/00000000-0000-0000-0000-000000000000" hx-confirm="Are you sure you want to delete this vendor?" hx-target="#vendor-00000000-0000-0000-0000-000000000000" hx-swap="outerHTML transition:true swap:1s" hx-on::after-request="if(event.detail.successful) toggleContent('float'); window.location.href='/vendors';">Delete</button> <button class="danger" style="font-size: 1.25em; padding: 10px 20px; border-radius: 10px;" hx-delete="/api/v1/vendors/00000000-0000-0000-0000-000000000000" hx-confirm="Are you sure you want to delete this vendor?" hx-target="#vendor-00000000-0000-0000-0000-000000000000" hx-swap="outerHTML transition:true swap:1s" hx-on::after-request="if(event.detail.successful) toggleContent('float'); window.location.href='/vendors';">Delete</button>
<button type="submit" class="btn-primary" style="float: right">Update</button> <button type="submit" class="btn-primary" style="float: right">Update</button>
</div> </div>
</form> </form>

View File

@@ -21,18 +21,18 @@ struct EmployeeViewRouteTests {
) )
} }
@Test // @Test
func employeeDelete() throws { // func employeeDelete() throws {
let id = UUID(0) // let id = UUID(0)
var request = URLRequestData( // var request = URLRequestData(
method: "DELETE", // method: "DELETE",
path: "/employees/\(id)" // path: "/employees/\(id)"
) // )
let route = try router.parse(&request) // let route = try router.parse(&request)
#expect( // #expect(
route == .employee(.delete(id: id)) // route == .employee(.delete(id: id))
) // )
} // }
@Test @Test
func employeeForm() throws { func employeeForm() throws {

View File

@@ -29,16 +29,16 @@ struct PurchaseOrderViewRouteTests {
)))) ))))
} }
@Test // @Test
func delete() throws { // func delete() throws {
let id = 1 // let id = 1
var request = URLRequestData( // var request = URLRequestData(
method: "DELETE", // method: "DELETE",
path: "/purchase-orders/\(id)" // path: "/purchase-orders/\(id)"
) // )
let route = try router.parse(&request) // let route = try router.parse(&request)
#expect(route == .purchaseOrder(.delete(id: id))) // #expect(route == .purchaseOrder(.delete(id: id)))
} // }
@Test @Test
func form() throws { func form() throws {

View File

@@ -25,16 +25,16 @@ struct UserViewRouteTests {
)))) ))))
} }
@Test // @Test
func delete() throws { // func delete() throws {
let id = UUID(0) // let id = UUID(0)
var request = URLRequestData( // var request = URLRequestData(
method: "DELETE", // method: "DELETE",
path: "/users/\(id)" // path: "/users/\(id)"
) // )
let route = try router.parse(&request) // let route = try router.parse(&request)
#expect(route == .user(.delete(id: id))) // #expect(route == .user(.delete(id: id)))
} // }
@Test @Test
func form() throws { func form() throws {

View File

@@ -20,16 +20,16 @@ struct VendorBranchViewRouteTests {
#expect(route == .vendorBranch(.create(.init(name: "Test", vendorID: id)))) #expect(route == .vendorBranch(.create(.init(name: "Test", vendorID: id))))
} }
@Test // @Test
func delete() throws { // func delete() throws {
let id = UUID(0) // let id = UUID(0)
var request = URLRequestData( // var request = URLRequestData(
method: "DELETE", // method: "DELETE",
path: "/vendors/branches/\(id)" // path: "/vendors/branches/\(id)"
) // )
let route = try router.parse(&request) // let route = try router.parse(&request)
#expect(route == .vendorBranch(.delete(id: id))) // #expect(route == .vendorBranch(.delete(id: id)))
} // }
@Test @Test
func index() throws { func index() throws {

View File

@@ -19,16 +19,16 @@ struct VendorViewRouteTests {
#expect(route == .vendor(.create(.init(name: "Test")))) #expect(route == .vendor(.create(.init(name: "Test"))))
} }
@Test // @Test
func delete() throws { // func delete() throws {
let id = UUID(0) // let id = UUID(0)
var request = URLRequestData( // var request = URLRequestData(
method: "DELETE", // method: "DELETE",
path: "/vendors/\(id)" // path: "/vendors/\(id)"
) // )
let route = try router.parse(&request) // let route = try router.parse(&request)
#expect(route == .vendor(.delete(id: id))) // #expect(route == .vendor(.delete(id: id)))
} // }
@Test @Test
func get() throws { func get() throws {