import CasePathsCore import DatabaseClientLive import Dependencies import Elementary import ElementaryHTMX import Fluent import SharedModels import Vapor import VaporElementary import VaporRouting func routes(_ app: Application) throws { // try app.register(collection: ApiController()) app.mount(SiteRoute.router, use: siteHandler) try app.register(collection: UserViewController()) try app.register(collection: VendorViewController()) try app.register(collection: EmployeeViewController()) try app.register(collection: PurchaseOrderViewController()) try app.register(collection: PurchaseOrderSearchViewController()) try app.register(collection: UtilsViewController()) app.get { _ in HTMLResponse { MainPage(displayNav: false, route: .purchaseOrders) { div(.class("container")) { h1 { "iT WORKS" } } } } } app.get("login") { req in let context = try req.query.decode(LoginContext.self) return await req.render { MainPage(displayNav: false, route: .login) { UserForm(context: .login(next: context.next)) } } } app.post("login") { req in @Dependency(\.database.users) var users let loginForm = try req.content.decode(User.Login.self) let token = try await users.login(loginForm) let user = try await users.get(token.userID)! req.session.authenticate(user) let context = try req.query.decode(LoginContext.self) return await req.render { MainPage(displayNav: true, route: .purchaseOrders) { div( .hx.get(context.next ?? "/purchase-orders"), .hx.pushURL(true), .hx.target("body"), .hx.trigger(.event(.revealed)), .hx.indicator(".hx-indicator") ) { Img.spinner().attributes(.class("hx-indicator")) } } } } } private struct LoginContext: Content { let next: String? } func siteHandler( request: Request, route: SiteRoute ) async throws -> any AsyncResponseEncodable { switch route { case let .api(route): switch route { case let .employee(route): return try await route.handle(request: request) case let .purchaseOrder(route): return try await route.handle(request: request) case let .user(route): return try await route.handle(request: request) case let .vendor(route): return try await route.handle(request: request) case let .vendorBranch(route): return try await route.handle(request: request) } case .health: return HTTPStatus.ok case let .view(route): switch route { case let .employee(route): return try await route.handle(request: request) case .login: // TODO: Needs to have login context. return await request.render { MainPage(displayNav: false, route: .login) { UserForm(context: .login(next: nil)) } } case .purchaseOrder: fatalError() case .select: fatalError() case let .user(route): return try await route.handle(request: request) case let .vendor(route): return try await route.handle(request: request) } } } extension ApiRoute.EmployeeApiRoute { func handle(request: Request) async throws -> any AsyncResponseEncodable { @Dependency(\.database) var database switch self { case let .create(employee): return try await database.employees.create(employee) case let .delete(id: id): try await database.employees.delete(id) return HTTPStatus.ok case .index: return try await database.employees.fetchAll() case let .get(id: id): guard let employee = try await database.employees.get(id) else { throw Abort(.badRequest, reason: "Employee not found") } return employee case let .update(id: id, updates: updates): return try await database.employees.update(id, updates) } } } extension ApiRoute.PurchaseOrderApiRoute { func handle(request: Request) async throws -> any AsyncResponseEncodable { @Dependency(\.database.purchaseOrders) var purchaseOrders switch self { case .index: return try await purchaseOrders.fetchAll() case let .create(purchaseOrder): return try await purchaseOrders.create(purchaseOrder) case let .delete(id: id): try await purchaseOrders.delete(id) return HTTPStatus.ok case let .get(id: id): guard let output = try await purchaseOrders.get(id) else { throw Abort(.badRequest, reason: "Purchase order not found.") } return output case let .page(page: page, limit: limit): return try await purchaseOrders.fetchPage(.init(page: page, per: limit)) } } } extension ApiRoute.UserApiRoute { func handle(request: Request) async throws -> any AsyncResponseEncodable { @Dependency(\.database.users) var users switch self { case let .create(user): return try await users.create(user) case let .delete(id: id): try await users.delete(id) return HTTPStatus.ok case .index: return try await users.fetchAll() case let .get(id: id): guard let user = try await users.get(id) else { throw Abort(.badRequest, reason: "Employee not found") } return user case let .login(user): return try await users.login(user) case let .update(id: id, updates: updates): return try await users.update(id, updates) } } } extension ApiRoute.VendorApiRoute { func handle(request: Request) async throws -> any AsyncResponseEncodable { @Dependency(\.database.vendors) var vendors switch self { case let .create(vendor): return try await vendors.create(vendor) case let .delete(id: id): try await vendors.delete(id) return HTTPStatus.ok case let .index(withBranches: withBranches): guard withBranches == true else { return try await vendors.fetchAll() } return try await vendors.fetchAll(.withBranches) case let .get(id: id): guard let vendor = try await vendors.get(id) else { throw Abort(.badRequest, reason: "Employee not found") } return vendor case let .update(id: id, updates: updates): return try await vendors.update(id, with: updates) } } } extension ApiRoute.VendorBranchApiRoute { func handle(request: Request) async throws -> any AsyncResponseEncodable { @Dependency(\.database.vendorBranches) var vendorBranches switch self { case let .create(branch): return try await vendorBranches.create(branch) case let .delete(id: id): try await vendorBranches.delete(id) return HTTPStatus.ok case let .index(for: optionalVendorID): guard let vendorID = optionalVendorID else { return try await vendorBranches.fetchAll() } return try await vendorBranches.fetchAll(.for(vendorID: vendorID)) case let .get(id: id): guard let branch = try await vendorBranches.get(id) else { throw Abort(.badRequest, reason: "Employee not found") } return branch case let .update(id: id, updates: updates): return try await vendorBranches.update(id, updates) } } } extension SharedModels.ViewRoute.EmployeeRoute { private func mainPage( _ html: C ) async throws -> some SendableHTMLDocument where C: Sendable { @Dependency(\.database) var database let employees = try await database.employees.fetchAll() return MainPage(displayNav: true, route: .employees) { div(.class("container")) { html EmployeeTable(employees: employees) } } } func handle(request: Request) async throws -> any AsyncResponseEncodable { @Dependency(\.database.employees) var employees switch self { case .form: return try await request.render(mainPage: mainPage) { EmployeeForm(shouldShow: true) } case let .shared(route): switch route { case .index: return try await request.render { 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 request.render(mainPage: mainPage) { EmployeeForm(employee: employee) } case let .create(employee): return try await request.render { try await EmployeeTable.Row(employee: employees.create(employee)) } case let .delete(id: id): try await employees.delete(id) return HTTPStatus.ok case let .update(id: id, updates: updates): return try await request.render { try await EmployeeTable.Row(employee: employees.update(id, updates)) } } } } } extension SharedModels.ViewRoute.UserRoute { private func mainPage(_ html: C) async throws -> some SendableHTMLDocument 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) } } } func handle(request: Request) async throws -> any AsyncResponseEncodable { @Dependency(\.database.users) var users switch self { case .form: return try await request.render(mainPage: mainPage) { UserForm(context: .create) } case let .shared(route): switch route { case let .create(user): return try await request.render { try await UserTable.Row(user: users.create(user)) } case let .delete(id: id): try await users.delete(id) return HTTPStatus.ok case .index: return try await request.render { 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 try await request.render(mainPage: mainPage) { UserDetail(user: user) } case let .login(login): let token = try await users.login(login) let user = try await users.get(token.userID)! request.session.authenticate(user) return await request.render { MainPage(displayNav: true, route: .purchaseOrders) { div( .hx.get("/purchase-orders"), .hx.pushURL(true), .hx.target("body"), .hx.trigger(.event(.revealed)), .hx.indicator(".hx-indicator") ) { Img.spinner().attributes(.class("hx-indicator")) } } } case let .update(id: id, updates: updates): return try await request.render { try await UserTable.Row(user: users.update(id, updates)) } } } } } extension SharedModels.ViewRoute.VendorRoute { private func mainPage(_ html: C) async throws -> some SendableHTMLDocument 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) } } } func handle(request: Request) async throws -> any AsyncResponseEncodable { @Dependency(\.database) var database switch self { case .form: return try await request.render(mainPage: mainPage) { VendorForm(.float(shouldShow: true)) } case let .createBranch(branch): return try await request.render { try await VendorDetail.BranchRow(branch: database.vendorBranches.create(branch)) } case let .shared(route): switch route { case let .create(vendor): let vendor = try await database.vendors.create(vendor) return await request.render { div(.class("container"), .id("content")) { VendorDetail(vendor: vendor) try await VendorTable(vendors: database.vendors.fetchAll(.withBranches)) } } case let .delete(id: id): try await database.vendors.delete(id) return HTTPStatus.ok 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 request.render(mainPage: mainPage) { VendorDetail(vendor: vendor) } case .index: return try await request.render { try await mainPage(VendorForm()) } case let .update(id: id, updates: updates): return try await request.render { try await VendorDetail( vendor: database.vendors.update(id, with: updates, returnWithBranches: true) ) } } } } }