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 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? }