feat: Begins integrating database client into vapor app.

This commit is contained in:
2025-01-14 11:50:06 -05:00
parent c8bcffa0b5
commit ccf80f05a7
42 changed files with 2378 additions and 2540 deletions

View File

@@ -1,158 +1,158 @@
import Dependencies
import Fluent
import Leaf
import Vapor
struct EmployeeViewController: RouteCollection {
@Dependency(\.employees) var employees
func boot(routes: any RoutesBuilder) throws {
let protected = routes.protected.grouped("employees")
protected.get(use: index(req:))
protected.get("form", use: employeeForm(req:))
protected.post(use: create(req:))
protected.group(":employeeID") {
$0.get(use: get(req:))
$0.get("edit", use: edit(req:))
$0.delete(use: delete(req:))
$0.put(use: update(req:))
$0.patch("toggle-active", use: toggleActive(req:))
}
}
@Sendable
func index(req: Request) async throws -> View {
return try await renderIndex(req)
}
@Sendable
private func renderIndex(
_ req: Request,
_ employee: Employee.DTO? = nil,
_ form: EmployeeFormCTX? = nil
) async throws -> View {
return try await req.view.render(
"employees/index",
EmployeesCTX(employee: employee, employees: employees.fetchAll(), form: form ?? .init())
)
}
@Sendable
func create(req: Request) async throws -> View {
try Employee.Create.validate(content: req)
let employee = try await employees.create(req.content.decode(Employee.Create.self))
return try await req.view.render("employees/table-row", employee)
}
@Sendable
func get(req: Request) async throws -> View {
let employee = try await employees.get(req.ensureIDPathComponent(key: "employeeID"))
// Check if we've rendered the page yet.
guard req.isHtmxRequest else {
return try await renderIndex(req, employee)
}
return try await req.view.render("employees/detail", ["employee": employee])
}
@Sendable
func toggleActive(req: Request) async throws -> View {
guard let id = req.parameters.get("employeeID", as: Employee.IDValue.self) else {
throw Abort(.badRequest, reason: "Employee id not supplied.")
}
let employee = try await employees.toggleActive(id)
return try await req.view.render("employees/table-row", employee)
}
// TODO: I think we can just return a response and remove the table-row, here.
@Sendable
func delete(req: Request) async throws -> View {
let id = try req.requireEmployeeID()
_ = try await employees.delete(id)
let employees = try await employees.fetchAll()
return try await req.view.render("employees/table", ["employees": employees])
}
@Sendable
func edit(req: Request) async throws -> View {
guard let employee = try await employees.get(req.parameters.get("employeeID")) else {
throw Abort(.notFound)
}
return try await req.view.render("employees/detail", EmployeeDetailCTX(editing: true, employee: employee))
}
@Sendable
func update(req: Request) async throws -> View {
let id = try req.requireEmployeeID()
try Employee.Update.validate(content: req)
let updates = try req.content.decode(Employee.Update.self)
req.logger.info("Employee updates: \(updates)")
let employee = try await employees.update(id, updates)
req.logger.info("Done updating employee: \(employee)")
return try await req.view.render("employees/table-row", employee)
}
@Sendable
func employeeForm(req: Request) async throws -> View {
try await req.view.render("employees/form", EmployeeFormCTX())
}
}
private extension Request {
func requireEmployeeID() throws -> Employee.IDValue {
guard let id = parameters.get("employeeID", as: Employee.IDValue.self) else {
throw Abort(.badRequest, reason: "Employee id not supplied")
}
return id
}
}
private struct EmployeeDetailCTX: Content {
let editing: Bool
let employee: Employee.DTO?
init(editing: Bool = false, employee: Employee.DTO? = nil) {
self.editing = editing
self.employee = employee
}
}
private struct EmployeesCTX: Content {
let employee: Employee.DTO?
let employees: [Employee.DTO]
let form: EmployeeFormCTX
init(
employee: Employee.DTO? = nil,
employees: [Employee.DTO],
form: EmployeeFormCTX? = nil
) {
self.employee = employee
self.employees = employees
self.form = form ?? .init()
}
}
private struct EmployeeFormCTX: Content {
let htmxForm: HtmxFormCTX<Context>
init(employee: Employee.DTO? = nil) {
self.htmxForm = .init(
formClass: "employee-form",
formId: "employee-form",
htmxTargetUrl: employee?.id == nil ? .post("/employees") : .put("/employees/\(employee!.id!)"),
htmxTarget: "#employee-table",
htmxPushUrl: false,
htmxResetAfterRequest: true,
htmxSwapOob: nil,
htmxSwap: employee == nil ? .outerHTML : nil,
context: .init(employee: employee)
)
}
struct Context: Content {
let employee: Employee.DTO?
}
}
// import Dependencies
// import Fluent
// import Leaf
// import Vapor
//
// struct EmployeeViewController: RouteCollection {
//
// @Dependency(\.employees) var employees
//
// func boot(routes: any RoutesBuilder) throws {
// let protected = routes.protected.grouped("employees")
// protected.get(use: index(req:))
// protected.get("form", use: employeeForm(req:))
// protected.post(use: create(req:))
// protected.group(":employeeID") {
// $0.get(use: get(req:))
// $0.get("edit", use: edit(req:))
// $0.delete(use: delete(req:))
// $0.put(use: update(req:))
// $0.patch("toggle-active", use: toggleActive(req:))
// }
// }
//
// @Sendable
// func index(req: Request) async throws -> View {
// return try await renderIndex(req)
// }
//
// @Sendable
// private func renderIndex(
// _ req: Request,
// _ employee: Employee.DTO? = nil,
// _ form: EmployeeFormCTX? = nil
// ) async throws -> View {
// return try await req.view.render(
// "employees/index",
// EmployeesCTX(employee: employee, employees: employees.fetchAll(), form: form ?? .init())
// )
// }
//
// @Sendable
// func create(req: Request) async throws -> View {
// try Employee.Create.validate(content: req)
// let employee = try await employees.create(req.content.decode(Employee.Create.self))
// return try await req.view.render("employees/table-row", employee)
// }
//
// @Sendable
// func get(req: Request) async throws -> View {
// let employee = try await employees.get(req.ensureIDPathComponent(key: "employeeID"))
// // Check if we've rendered the page yet.
// guard req.isHtmxRequest else {
// return try await renderIndex(req, employee)
// }
// return try await req.view.render("employees/detail", ["employee": employee])
// }
//
// @Sendable
// func toggleActive(req: Request) async throws -> View {
// guard let id = req.parameters.get("employeeID", as: Employee.IDValue.self) else {
// throw Abort(.badRequest, reason: "Employee id not supplied.")
// }
// let employee = try await employees.toggleActive(id)
// return try await req.view.render("employees/table-row", employee)
// }
//
// // TODO: I think we can just return a response and remove the table-row, here.
// @Sendable
// func delete(req: Request) async throws -> View {
// let id = try req.requireEmployeeID()
// _ = try await employees.delete(id)
// let employees = try await employees.fetchAll()
// return try await req.view.render("employees/table", ["employees": employees])
// }
//
// @Sendable
// func edit(req: Request) async throws -> View {
// guard let employee = try await employees.get(req.parameters.get("employeeID")) else {
// throw Abort(.notFound)
// }
// return try await req.view.render("employees/detail", EmployeeDetailCTX(editing: true, employee: employee))
// }
//
// @Sendable
// func update(req: Request) async throws -> View {
// let id = try req.requireEmployeeID()
// try Employee.Update.validate(content: req)
// let updates = try req.content.decode(Employee.Update.self)
// req.logger.info("Employee updates: \(updates)")
// let employee = try await employees.update(id, updates)
// req.logger.info("Done updating employee: \(employee)")
// return try await req.view.render("employees/table-row", employee)
// }
//
// @Sendable
// func employeeForm(req: Request) async throws -> View {
// try await req.view.render("employees/form", EmployeeFormCTX())
// }
//
// }
//
// private extension Request {
// func requireEmployeeID() throws -> Employee.IDValue {
// guard let id = parameters.get("employeeID", as: Employee.IDValue.self) else {
// throw Abort(.badRequest, reason: "Employee id not supplied")
// }
// return id
// }
// }
//
// private struct EmployeeDetailCTX: Content {
// let editing: Bool
// let employee: Employee.DTO?
//
// init(editing: Bool = false, employee: Employee.DTO? = nil) {
// self.editing = editing
// self.employee = employee
// }
// }
//
// private struct EmployeesCTX: Content {
// let employee: Employee.DTO?
// let employees: [Employee.DTO]
// let form: EmployeeFormCTX
//
// init(
// employee: Employee.DTO? = nil,
// employees: [Employee.DTO],
// form: EmployeeFormCTX? = nil
// ) {
// self.employee = employee
// self.employees = employees
// self.form = form ?? .init()
// }
// }
//
// private struct EmployeeFormCTX: Content {
//
// let htmxForm: HtmxFormCTX<Context>
//
// init(employee: Employee.DTO? = nil) {
// self.htmxForm = .init(
// formClass: "employee-form",
// formId: "employee-form",
// htmxTargetUrl: employee?.id == nil ? .post("/employees") : .put("/employees/\(employee!.id!)"),
// htmxTarget: "#employee-table",
// htmxPushUrl: false,
// htmxResetAfterRequest: true,
// htmxSwapOob: nil,
// htmxSwap: employee == nil ? .outerHTML : nil,
// context: .init(employee: employee)
// )
// }
//
// struct Context: Content {
// let employee: Employee.DTO?
// }
// }

View File

@@ -1,196 +1,196 @@
import Dependencies
import Fluent
import Vapor
struct PurchaseOrderViewController: RouteCollection {
@Dependency(\.employees) var employees
@Dependency(\.purchaseOrders) var purchaseOrders
@Dependency(\.vendorBranches) var vendorBranches
func boot(routes: any RoutesBuilder) throws {
let pos = routes.protected.grouped("purchase-orders")
pos.get(use: index(req:))
pos.group("details", "close") {
$0.get(use: detailClose(req:))
}
pos.post(use: create(req:))
pos.group(":id") {
$0.get(use: detail(req:))
}
}
@Sendable
func index(req: Request) async throws -> View {
let params = try? req.query.decode(PurchaseOrderIndex.self)
let purchaseOrdersPage = try await purchaseOrders.fetchPage(
.init(page: params?.page ?? 1, per: params?.limit ?? 50)
)
let branches = try await vendorBranches.getBranches(req: req)
let employees = try await employees.fetchAll()
req.logger.debug("Branches: \(branches)")
return try await req.view.render(
"purchaseOrders/index",
PurchaseOrderCTX(
page: purchaseOrdersPage,
form: .create(branches: branches, employees: employees)
)
)
}
@Sendable
func detail(req: Request) async throws -> View {
guard let id = req.parameters.get("id", as: PurchaseOrder.IDValue.self) else {
throw Abort(.badRequest, reason: "Id not supplied.")
}
let purchaseOrder = try await purchaseOrders.get(id)
return try await req.view.render("purchaseOrders/detail", ["purchaseOrderDetail": purchaseOrder])
}
@Sendable
func detailClose(req: Request) async throws -> View {
return try await req.view.render("purchaseOrders/detail")
}
@Sendable
func create(req: Request) async throws -> View {
try PurchaseOrder.FormCreate.validate(content: req)
let createdById = try req.auth.require(User.self).requireID()
let create = try req.content.decode(PurchaseOrder.FormCreate.self).toCreate()
let purchaseOrder = try await purchaseOrders.create(create, createdById)
return try await req.view.render("purchaseOrders/table-row", purchaseOrder)
}
}
private struct PurchaseOrderIndex: Content {
let page: Int?
let limit: Int?
}
private struct PurchaseOrderCTX: Content {
let purchaseOrderDetail: PurchaseOrder.DTO?
let purchaseOrders: [PurchaseOrder.DTO]
let page: Int
let limit: Int
let hasNext: Bool
let hasPrevious: Bool
let form: PurchaseOrderFormCTX?
init(
detail: PurchaseOrder.DTO? = nil,
page: Page<PurchaseOrder.DTO>,
form: PurchaseOrderFormCTX?
) {
self.purchaseOrderDetail = detail
self.purchaseOrders = page.items
self.page = page.metadata.page
self.limit = page.metadata.per
self.hasNext = page.metadata.hasNext
self.hasPrevious = page.metadata.page > 1
self.form = form
}
}
private extension PageMetadata {
var hasNext: Bool {
total > (page * per)
}
}
private struct PurchaseOrderFormCTX: Content {
let htmxForm: HtmxFormCTX<Context>
struct Context: Content {
let branches: [VendorBranch.FormDTO]
let employees: [Employee.DTO]
}
static func create(branches: [VendorBranch.FormDTO], employees: [Employee.DTO]) -> Self {
.init(htmxForm: .init(
formClass: "po-form",
formId: "po-form",
htmxTargetUrl: .post("/purchase-orders"),
htmxTarget: "#po-table-body",
htmxPushUrl: false,
htmxResetAfterRequest: true,
htmxSwapOob: nil,
htmxSwap: .afterbegin,
context: .init(branches: branches, employees: employees)
))
}
}
extension VendorBranch {
struct FormDTO: Content {
let id: UUID
let name: String
let vendor: Vendor.DTO
}
func toFormDTO() throws -> VendorBranch.FormDTO {
try .init(
id: requireID(),
name: name,
vendor: vendor.toDTO()
)
}
}
private extension PurchaseOrder {
struct FormCreate: Content {
let id: Int?
let workOrder: String?
let materials: String
let customer: String
let truckStock: Bool?
let createdForID: Employee.IDValue
let vendorBranchID: VendorBranch.IDValue
// TODO: Remove.
func toModel(createdByID: User.IDValue) -> PurchaseOrder {
.init(
id: id,
workOrder: workOrder != nil ? (workOrder == "" ? nil : Int(workOrder!)) : nil,
materials: materials,
customer: customer,
truckStock: truckStock ?? false,
createdByID: createdByID,
createdForID: createdForID,
vendorBranchID: vendorBranchID,
createdAt: nil,
updatedAt: nil
)
}
func toCreate() -> PurchaseOrder.Create {
.init(
id: id,
workOrder: workOrder != nil ? (workOrder == "" ? nil : Int(workOrder!)) : nil,
materials: materials,
customer: customer,
truckStock: truckStock,
createdForID: createdForID,
vendorBranchID: vendorBranchID
)
}
}
}
private extension VendorBranchDB {
func getBranches(req: Request) async throws -> [VendorBranch.FormDTO] {
try await VendorBranch.query(on: req.db)
.with(\.$vendor)
.all()
.map { try $0.toFormDTO() }
}
}
extension PurchaseOrder.FormCreate: Validatable {
static func validations(_ validations: inout Validations) {
validations.add("materials", as: String.self, is: !.empty)
validations.add("customer", as: String.self, is: !.empty)
}
}
// import Dependencies
// import Fluent
// import Vapor
//
// struct PurchaseOrderViewController: RouteCollection {
// @Dependency(\.employees) var employees
// @Dependency(\.purchaseOrders) var purchaseOrders
// @Dependency(\.vendorBranches) var vendorBranches
//
// func boot(routes: any RoutesBuilder) throws {
// let pos = routes.protected.grouped("purchase-orders")
//
// pos.get(use: index(req:))
// pos.group("details", "close") {
// $0.get(use: detailClose(req:))
// }
// pos.post(use: create(req:))
// pos.group(":id") {
// $0.get(use: detail(req:))
// }
// }
//
// @Sendable
// func index(req: Request) async throws -> View {
// let params = try? req.query.decode(PurchaseOrderIndex.self)
// let purchaseOrdersPage = try await purchaseOrders.fetchPage(
// .init(page: params?.page ?? 1, per: params?.limit ?? 50)
// )
// let branches = try await vendorBranches.getBranches(req: req)
// let employees = try await employees.fetchAll()
// req.logger.debug("Branches: \(branches)")
// return try await req.view.render(
// "purchaseOrders/index",
// PurchaseOrderCTX(
// page: purchaseOrdersPage,
// form: .create(branches: branches, employees: employees)
// )
// )
// }
//
// @Sendable
// func detail(req: Request) async throws -> View {
// guard let id = req.parameters.get("id", as: PurchaseOrder.IDValue.self) else {
// throw Abort(.badRequest, reason: "Id not supplied.")
// }
// let purchaseOrder = try await purchaseOrders.get(id)
// return try await req.view.render("purchaseOrders/detail", ["purchaseOrderDetail": purchaseOrder])
// }
//
// @Sendable
// func detailClose(req: Request) async throws -> View {
// return try await req.view.render("purchaseOrders/detail")
// }
//
// @Sendable
// func create(req: Request) async throws -> View {
// try PurchaseOrder.FormCreate.validate(content: req)
// let createdById = try req.auth.require(User.self).requireID()
// let create = try req.content.decode(PurchaseOrder.FormCreate.self).toCreate()
// let purchaseOrder = try await purchaseOrders.create(create, createdById)
// return try await req.view.render("purchaseOrders/table-row", purchaseOrder)
// }
// }
//
// private struct PurchaseOrderIndex: Content {
// let page: Int?
// let limit: Int?
// }
//
// private struct PurchaseOrderCTX: Content {
// let purchaseOrderDetail: PurchaseOrder.DTO?
// let purchaseOrders: [PurchaseOrder.DTO]
// let page: Int
// let limit: Int
// let hasNext: Bool
// let hasPrevious: Bool
// let form: PurchaseOrderFormCTX?
//
// init(
// detail: PurchaseOrder.DTO? = nil,
// page: Page<PurchaseOrder.DTO>,
// form: PurchaseOrderFormCTX?
// ) {
// self.purchaseOrderDetail = detail
// self.purchaseOrders = page.items
// self.page = page.metadata.page
// self.limit = page.metadata.per
// self.hasNext = page.metadata.hasNext
// self.hasPrevious = page.metadata.page > 1
// self.form = form
// }
// }
//
// private extension PageMetadata {
// var hasNext: Bool {
// total > (page * per)
// }
// }
//
// private struct PurchaseOrderFormCTX: Content {
//
// let htmxForm: HtmxFormCTX<Context>
//
// struct Context: Content {
// let branches: [VendorBranch.FormDTO]
// let employees: [Employee.DTO]
// }
//
// static func create(branches: [VendorBranch.FormDTO], employees: [Employee.DTO]) -> Self {
// .init(htmxForm: .init(
// formClass: "po-form",
// formId: "po-form",
// htmxTargetUrl: .post("/purchase-orders"),
// htmxTarget: "#po-table-body",
// htmxPushUrl: false,
// htmxResetAfterRequest: true,
// htmxSwapOob: nil,
// htmxSwap: .afterbegin,
// context: .init(branches: branches, employees: employees)
// ))
// }
// }
//
// extension VendorBranch {
// struct FormDTO: Content {
// let id: UUID
// let name: String
// let vendor: Vendor.DTO
// }
//
// func toFormDTO() throws -> VendorBranch.FormDTO {
// try .init(
// id: requireID(),
// name: name,
// vendor: vendor.toDTO()
// )
// }
// }
//
// private extension PurchaseOrder {
// struct FormCreate: Content {
// let id: Int?
// let workOrder: String?
// let materials: String
// let customer: String
// let truckStock: Bool?
// let createdForID: Employee.IDValue
// let vendorBranchID: VendorBranch.IDValue
//
// // TODO: Remove.
// func toModel(createdByID: User.IDValue) -> PurchaseOrder {
// .init(
// id: id,
// workOrder: workOrder != nil ? (workOrder == "" ? nil : Int(workOrder!)) : nil,
// materials: materials,
// customer: customer,
// truckStock: truckStock ?? false,
// createdByID: createdByID,
// createdForID: createdForID,
// vendorBranchID: vendorBranchID,
// createdAt: nil,
// updatedAt: nil
// )
// }
//
// func toCreate() -> PurchaseOrder.Create {
// .init(
// id: id,
// workOrder: workOrder != nil ? (workOrder == "" ? nil : Int(workOrder!)) : nil,
// materials: materials,
// customer: customer,
// truckStock: truckStock,
// createdForID: createdForID,
// vendorBranchID: vendorBranchID
// )
// }
// }
// }
//
// private extension VendorBranchDB {
//
// func getBranches(req: Request) async throws -> [VendorBranch.FormDTO] {
// try await VendorBranch.query(on: req.db)
// .with(\.$vendor)
// .all()
// .map { try $0.toFormDTO() }
// }
// }
//
// extension PurchaseOrder.FormCreate: Validatable {
//
// static func validations(_ validations: inout Validations) {
// validations.add("materials", as: String.self, is: !.empty)
// validations.add("customer", as: String.self, is: !.empty)
// }
// }

View File

@@ -1,121 +1,121 @@
import Dependencies
import Fluent
import Vapor
struct UserViewController: RouteCollection {
@Dependency(\.users) var users
private let api = UserApiController()
func boot(routes: any RoutesBuilder) throws {
let users = routes.protected.grouped("users")
users.get(use: index(req:))
users.post(use: create(req:))
users.group(":id") {
$0.get(use: details(req:))
$0.delete(use: delete(req:))
}
}
@Sendable
func index(req: Request) async throws -> View {
try await renderIndex(req)
}
@Sendable
private func renderIndex(_ req: Request, _ user: User.DTO? = nil) async throws -> View {
let users = try await api.getSortedUsers(req: req)
return try await req.view.render("users/index", UsersCTX(user: user, users: users))
}
@Sendable
func create(req: Request) async throws -> View {
let user = try await api.create(req: req)
return try await req.view.render("users/table-row", user)
}
@Sendable
func details(req: Request) async throws -> View {
let user = try await users.get(req.ensureIDPathComponent())
// Check if the page has been rendered before.
guard req.isHtmxRequest else {
// Not an htmx-request, so render the whole page with the details.
return try await renderIndex(req, user)
}
// An htmx-request header was present, so just return the details,
return try await req.view.render("users/detail", ["user": user])
}
@Sendable
func delete(req: Request) async throws -> View {
_ = try await api.delete(req: req)
return try await req.view.render("users/table", ["users": api.getSortedUsers(req: req)])
}
}
struct UserFormCTX: Content {
let htmxForm: HtmxFormCTX<Context>
struct Context: Content {
let showConfirmPassword: Bool
let showEmailInput: Bool
let buttonLabel: String
}
static func signIn(next: String?) -> Self {
.init(
htmxForm: .init(
formClass: "user-form",
formId: "user-form",
htmxTargetUrl: .post("/login\((next != nil && next != "/") ? "?next=\(next!)" : "")"),
htmxTarget: "user-table",
htmxPushUrl: true,
htmxResetAfterRequest: true,
htmxSwapOob: nil,
htmxSwap: .afterbegin,
context: .init(showConfirmPassword: false, showEmailInput: false, buttonLabel: "Sign In")
)
)
}
static func create() -> Self {
.init(
htmxForm: .init(
formClass: "user-form",
formId: "user-form",
htmxTargetUrl: .post("/users"),
htmxTarget: "#user-table",
htmxPushUrl: false,
htmxResetAfterRequest: true,
htmxSwapOob: nil,
htmxSwap: nil,
context: .init(showConfirmPassword: true, showEmailInput: true, buttonLabel: "Create")
)
)
}
}
private struct UsersCTX: Content {
let user: User.DTO?
let users: [User.DTO]
let form: UserFormCTX
init(
user: User.DTO? = nil,
users: [User.DTO],
form: UserFormCTX? = nil
) {
self.user = user
self.users = users
self.form = form ?? .create()
}
}
private extension UserApiController {
func getSortedUsers(req: Request) async throws -> [User.DTO] {
try await index(req: req)
.sorted { ($0.username ?? "") < ($1.username ?? "") }
}
}
// import Dependencies
// import Fluent
// import Vapor
//
// struct UserViewController: RouteCollection {
//
// @Dependency(\.users) var users
//
// private let api = UserApiController()
//
// func boot(routes: any RoutesBuilder) throws {
// let users = routes.protected.grouped("users")
// users.get(use: index(req:))
// users.post(use: create(req:))
// users.group(":id") {
// $0.get(use: details(req:))
// $0.delete(use: delete(req:))
// }
// }
//
// @Sendable
// func index(req: Request) async throws -> View {
// try await renderIndex(req)
// }
//
// @Sendable
// private func renderIndex(_ req: Request, _ user: User.DTO? = nil) async throws -> View {
// let users = try await api.getSortedUsers(req: req)
// return try await req.view.render("users/index", UsersCTX(user: user, users: users))
// }
//
// @Sendable
// func create(req: Request) async throws -> View {
// let user = try await api.create(req: req)
// return try await req.view.render("users/table-row", user)
// }
//
// @Sendable
// func details(req: Request) async throws -> View {
// let user = try await users.get(req.ensureIDPathComponent())
// // Check if the page has been rendered before.
// guard req.isHtmxRequest else {
// // Not an htmx-request, so render the whole page with the details.
// return try await renderIndex(req, user)
// }
// // An htmx-request header was present, so just return the details,
// return try await req.view.render("users/detail", ["user": user])
// }
//
// @Sendable
// func delete(req: Request) async throws -> View {
// _ = try await api.delete(req: req)
// return try await req.view.render("users/table", ["users": api.getSortedUsers(req: req)])
// }
// }
//
// struct UserFormCTX: Content {
// let htmxForm: HtmxFormCTX<Context>
//
// struct Context: Content {
// let showConfirmPassword: Bool
// let showEmailInput: Bool
// let buttonLabel: String
// }
//
// static func signIn(next: String?) -> Self {
// .init(
// htmxForm: .init(
// formClass: "user-form",
// formId: "user-form",
// htmxTargetUrl: .post("/login\((next != nil && next != "/") ? "?next=\(next!)" : "")"),
// htmxTarget: "user-table",
// htmxPushUrl: true,
// htmxResetAfterRequest: true,
// htmxSwapOob: nil,
// htmxSwap: .afterbegin,
// context: .init(showConfirmPassword: false, showEmailInput: false, buttonLabel: "Sign In")
// )
// )
// }
//
// static func create() -> Self {
// .init(
// htmxForm: .init(
// formClass: "user-form",
// formId: "user-form",
// htmxTargetUrl: .post("/users"),
// htmxTarget: "#user-table",
// htmxPushUrl: false,
// htmxResetAfterRequest: true,
// htmxSwapOob: nil,
// htmxSwap: nil,
// context: .init(showConfirmPassword: true, showEmailInput: true, buttonLabel: "Create")
// )
// )
// }
// }
//
// private struct UsersCTX: Content {
// let user: User.DTO?
// let users: [User.DTO]
// let form: UserFormCTX
//
// init(
// user: User.DTO? = nil,
// users: [User.DTO],
// form: UserFormCTX? = nil
// ) {
// self.user = user
// self.users = users
// self.form = form ?? .create()
// }
// }
//
// private extension UserApiController {
//
// func getSortedUsers(req: Request) async throws -> [User.DTO] {
// try await index(req: req)
// .sorted { ($0.username ?? "") < ($1.username ?? "") }
// }
// }

View File

@@ -1,108 +1,108 @@
import Fluent
import Vapor
struct VendorViewController: RouteCollection {
private let api = VendorApiController()
func boot(routes: any RoutesBuilder) throws {
let vendors = routes.protected.grouped("vendors")
vendors.get(use: index(req:))
vendors.post(use: create(req:))
vendors.group(":vendorID") {
$0.delete(use: delete(req:))
$0.put(use: update(req:))
}
}
@Sendable
func index(req: Request) async throws -> View {
return try await req.view.render("vendors/index", makeCtx(req: req))
}
@Sendable
func create(req: Request) async throws -> View {
let ctx = try req.content.decode(CreateVendorCTX.self)
req.logger.debug("CTX: \(ctx)")
let vendor = Vendor.Create(name: ctx.name).toModel()
try await vendor.save(on: req.db)
if let branchString = ctx.branches {
let branches = branchString.split(separator: ",")
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
for branch in branches {
try await vendor.$branches.create(
VendorBranch(name: String(branch), vendorId: vendor.requireID()),
on: req.db
)
}
}
return try await req.view.render("vendors/table", makeCtx(req: req))
}
@Sendable
func delete(req: Request) async throws -> HTTPStatus {
try await api.delete(req: req)
}
@Sendable
func update(req: Request) async throws -> View {
_ = try await api.update(req: req)
return try await req.view.render("vendors/table", makeCtx(req: req, oob: true))
}
private func makeCtx(req: Request, vendor: Vendor? = nil, oob: Bool = false) async throws -> VendorsCTX {
let vendors = try await Vendor.query(on: req.db)
.with(\.$branches)
.sort(\.$name, .ascending)
.all()
.map { $0.toDTO() }
return .init(
vendors: vendors,
form: .init(vendor: vendor, oob: oob)
)
}
}
struct VendorFormCTX: Content {
let htmxForm: HtmxFormCTX<Context>
init(vendor: Vendor? = nil, oob: Bool = false) {
self.htmxForm = .init(
formClass: "vendor-form",
formId: "vendor-form",
htmxTargetUrl: vendor == nil ? .post("/vendors") : .put("/vendors"),
htmxTarget: "#vendor-table",
htmxPushUrl: false,
htmxResetAfterRequest: true,
htmxSwapOob: oob ? .outerHTML : nil,
htmxSwap: oob ? nil : .outerHTML,
context: .init(vendor: vendor)
)
}
struct Context: Content {
let vendor: Vendor?
let branches: String?
let buttonLabel: String
init(vendor: Vendor? = nil) {
self.vendor = vendor
self.branches = vendor?.branches.map(\.name).joined(separator: ", ")
self.buttonLabel = vendor == nil ? "Create" : "Update"
}
}
}
private struct VendorsCTX: Content {
let vendors: [Vendor.DTO]
let form: VendorFormCTX
}
private struct CreateVendorCTX: Content {
let name: String
let branches: String?
}
// import Fluent
// import Vapor
//
// struct VendorViewController: RouteCollection {
// private let api = VendorApiController()
//
// func boot(routes: any RoutesBuilder) throws {
// let vendors = routes.protected.grouped("vendors")
//
// vendors.get(use: index(req:))
// vendors.post(use: create(req:))
// vendors.group(":vendorID") {
// $0.delete(use: delete(req:))
// $0.put(use: update(req:))
// }
// }
//
// @Sendable
// func index(req: Request) async throws -> View {
// return try await req.view.render("vendors/index", makeCtx(req: req))
// }
//
// @Sendable
// func create(req: Request) async throws -> View {
// let ctx = try req.content.decode(CreateVendorCTX.self)
// req.logger.debug("CTX: \(ctx)")
// let vendor = Vendor.Create(name: ctx.name).toModel()
// try await vendor.save(on: req.db)
//
// if let branchString = ctx.branches {
// let branches = branchString.split(separator: ",")
// .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
//
// for branch in branches {
// try await vendor.$branches.create(
// VendorBranch(name: String(branch), vendorId: vendor.requireID()),
// on: req.db
// )
// }
// }
//
// return try await req.view.render("vendors/table", makeCtx(req: req))
// }
//
// @Sendable
// func delete(req: Request) async throws -> HTTPStatus {
// try await api.delete(req: req)
// }
//
// @Sendable
// func update(req: Request) async throws -> View {
// _ = try await api.update(req: req)
// return try await req.view.render("vendors/table", makeCtx(req: req, oob: true))
// }
//
// private func makeCtx(req: Request, vendor: Vendor? = nil, oob: Bool = false) async throws -> VendorsCTX {
// let vendors = try await Vendor.query(on: req.db)
// .with(\.$branches)
// .sort(\.$name, .ascending)
// .all()
// .map { $0.toDTO() }
//
// return .init(
// vendors: vendors,
// form: .init(vendor: vendor, oob: oob)
// )
// }
// }
//
// struct VendorFormCTX: Content {
// let htmxForm: HtmxFormCTX<Context>
//
// init(vendor: Vendor? = nil, oob: Bool = false) {
// self.htmxForm = .init(
// formClass: "vendor-form",
// formId: "vendor-form",
// htmxTargetUrl: vendor == nil ? .post("/vendors") : .put("/vendors"),
// htmxTarget: "#vendor-table",
// htmxPushUrl: false,
// htmxResetAfterRequest: true,
// htmxSwapOob: oob ? .outerHTML : nil,
// htmxSwap: oob ? nil : .outerHTML,
// context: .init(vendor: vendor)
// )
// }
//
// struct Context: Content {
// let vendor: Vendor?
// let branches: String?
// let buttonLabel: String
//
// init(vendor: Vendor? = nil) {
// self.vendor = vendor
// self.branches = vendor?.branches.map(\.name).joined(separator: ", ")
// self.buttonLabel = vendor == nil ? "Create" : "Update"
// }
// }
// }
//
// private struct VendorsCTX: Content {
// let vendors: [Vendor.DTO]
// let form: VendorFormCTX
// }
//
// private struct CreateVendorCTX: Content {
// let name: String
// let branches: String?
// }