import DatabaseClient import Dependencies import Elementary import Logging import SharedModels import Vapor import ViewController public extension SiteRoute.View { @Sendable func view( isHtmxRequest: Bool, logger: Logger, authenticate: @escaping @Sendable (User) -> Void, currentUser: @escaping @Sendable () throws -> User ) async throws -> AnySendableHTML { @Dependency(\.database.users) var users switch self { case let .employee(route): return try await route.view(isHtmxRequest: isHtmxRequest) case let .login(route): switch route { case let .index(next: next): return MainPage(displayNav: false, route: .login) { UserForm(context: .login(next: next)) } case let .post(login): let token = try await users.login(.init(username: login.username, password: login.password)) let user = try await users.get(token.userID)! authenticate(user) logger.info("Logged in next: \(login.next ?? "N/A")") return MainPage.loggedIn(next: login.next) } case let .purchaseOrder(route): return try await route.view(isHtmxRequest: isHtmxRequest, currentUser: currentUser) case let .resetPassword(route): return try await route.view(isHtmxRequest: isHtmxRequest) case let .user(route): return try await route.view(isHtmxRequest: isHtmxRequest) case let .vendor(route): return try await route.view(isHtmxRequest: isHtmxRequest) case let .vendorBranch(route): return try await route.view(isHtmxRequest: isHtmxRequest) } } } extension SiteRoute.View.EmployeeRoute { private func mainPage( _ html: C ) async throws -> AnySendableHTML where C: Sendable { @Dependency(\.database) var database let employees = try await database.employees.fetchAll() // return EmployeeMainPage(employees: employees, html: html) return MainPage(displayNav: true, route: .employees) { div(.class("container")) { html EmployeeTable(employees: employees) } } } @Sendable func view(isHtmxRequest: Bool) async throws -> AnySendableHTML { @Dependency(\.database.employees) var employees switch self { case .form: return try await render(mainPage, isHtmxRequest, EmployeeForm(shouldShow: true)) case let .select(context: context): return try await context.toHTML(employees: employees.fetchAll(.active)) case .index: return try await mainPage(EmployeeForm()) case let .get(id: id): guard let employee = try await employees.get(id) else { throw Abort(.badRequest, reason: "Employee id not found.") } return try await render(mainPage, isHtmxRequest, EmployeeForm(employee: employee)) case let .create(employee): return try await EmployeeTable.Row(employee: employees.create(employee)) case let .update(id: id, updates: updates): return try await EmployeeTable.Row(employee: employees.update(id, updates)) } } } extension SiteRoute.View.PurchaseOrderRoute { private func mainPage( _ html: C ) async throws -> AnySendableHTML where C: Sendable { @Dependency(\.database.purchaseOrders) var purchaseOrders let page = try await purchaseOrders.fetchPage(.init(page: 1, per: 25)) return MainPage(displayNav: true, route: .purchaseOrders) { div(.class("container"), .id("purchase-order-content")) { html PurchaseOrderTable(page: page) } } } @Sendable func view( isHtmxRequest: Bool, currentUser: @escaping @Sendable () throws -> User ) async throws -> AnySendableHTML { @Dependency(\.database.purchaseOrders) var purchaseOrders switch self { case .form: return try await render(mainPage, isHtmxRequest) { PurchaseOrderForm(shouldShow: true) } case let .search(route): return try await route.view(isHtmxRequest: isHtmxRequest) case let .create(purchaseOrder): return try await PurchaseOrderTable.Row( purchaseOrder: purchaseOrders.create(purchaseOrder.toCreate(createdByID: currentUser().id)) ) case .index: return 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 render(mainPage, isHtmxRequest) { PurchaseOrderForm(purchaseOrder: purchaseOrder, shouldShow: true) } case let .page(page: page, limit: limit): return try await PurchaseOrderTable.Rows( page: purchaseOrders.fetchPage(.init(page: page, per: limit)) ) } } } extension SiteRoute.View.PurchaseOrderRoute.Search { func mainPage(search: PurchaseOrderSearch = .init()) -> AnySendableHTML { 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)), context: .search ) } } } @Sendable func view(isHtmxRequest: Bool) async throws -> AnySendableHTML { @Dependency(\.database) var database switch self { case let .index(context: context, table: table): let html = PurchaseOrderSearch(context: context) if table == true || !isHtmxRequest { return mainPage(search: html) } return html case let .request(context): let results = try await database.purchaseOrders.search(context.toDatabaseQuery(), .init(page: 1, per: 25)) return PurchaseOrderTable(page: results, context: .search) } } } extension SiteRoute.View.ResetPasswordRoute { @Sendable func view(isHtmxRequest: Bool) async throws -> AnySendableHTML { @Dependency(\.database) var database switch self { case let .index(id: id): return UserForm(context: .resetPassword(id: id)) case let .submit(id: id, request: request): try await database.users.resetPassword(id, request) let user = try await database.users.get(id) return UserDetail(user: user) } } } extension SiteRoute.View.UserRoute { private func mainPage(_ html: C) async throws -> AnySendableHTML where C: Sendable { @Dependency(\.database) var database let users = try await database.users.fetchAll() return MainPage(displayNav: true, route: .users) { div(.class("container")) { html UserTable(users: users) } } } @Sendable func view(isHtmxRequest: Bool) async throws -> AnySendableHTML { @Dependency(\.database.users) var users switch self { case .form: return try await render(mainPage, isHtmxRequest, UserForm(context: .create)) case let .create(user): return try await UserTable.Row(user: users.create(user)) case .index: return try await mainPage(UserDetail(user: nil)) case let .get(id: id): guard let user = try await users.get(id) else { throw Abort(.badRequest, reason: "User not found.") } return UserDetail(user: user) case let .update(id: id, updates: updates): return try await UserTable.Row(user: users.update(id, updates)) } } } extension SiteRoute.View.VendorRoute { private func mainPage(_ html: C) async throws -> AnySendableHTML where C: Sendable { @Dependency(\.database) var database let vendors = try await database.vendors.fetchAll(.withBranches) return MainPage(displayNav: true, route: .vendors) { div(.class("container"), .id("content")) { html VendorTable(vendors: vendors) } } } @Sendable func view(isHtmxRequest: Bool) async throws -> AnySendableHTML { @Dependency(\.database) var database switch self { case .form: return try await render(mainPage, isHtmxRequest, VendorForm(.float(shouldShow: true))) case .index: return try await mainPage(VendorForm()) case let .create(vendor): let vendor = try await database.vendors.create(vendor) let table = try await VendorTable(vendors: database.vendors.fetchAll(.withBranches)) return try await render(mainPage, isHtmxRequest) { div(.class("container"), .id("content")) { VendorDetail(vendor: vendor) table } } case let .get(id: id): guard let vendor = try await database.vendors.get(id, .withBranches) else { throw Abort(.badRequest, reason: "Vendor not found.") } return try await render(mainPage, isHtmxRequest, VendorDetail(vendor: vendor)) case let .update(id: id, updates: updates): return try await VendorDetail( vendor: database.vendors.update(id, with: updates, returnWithBranches: true) ) } } } extension SiteRoute.View.VendorBranchRoute { @Sendable func view(isHtmxRequest: Bool) async throws -> AnySendableHTML { @Dependency(\.database) var database switch self { case let .index(for: vendorID): guard let vendorID else { throw Abort(.badRequest, reason: "Vendor id not supplied") } return try await VendorBranchList( vendorID: vendorID, branches: database.vendorBranches.fetchAll(.for(vendorID: vendorID)) ) case let .select(context: context): return try await context.toHTML(branches: database.vendorBranches.fetchAllWithDetail()) case let .create(branch): return try await VendorBranchList.Row(branch: database.vendorBranches.create(branch)) } } } extension SiteRoute.View.PurchaseOrderRoute.Search.Request { @Sendable 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 SiteRoute.View.SelectContext { @Sendable 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) } } } @Sendable private func render( _ mainPage: (C) async throws -> AnySendableHTML, _ isHtmxRequest: Bool, @HTMLBuilder html: () -> C ) async rethrows -> AnySendableHTML where C: Sendable { guard isHtmxRequest else { return try await mainPage(html()) } return html() } @Sendable private func render( _ mainPage: (C) async throws -> AnySendableHTML, _ isHtmxRequest: Bool, _ html: @autoclosure @escaping () -> C ) async rethrows -> AnySendableHTML where C: Sendable { try await render(mainPage, isHtmxRequest) { html() } }