Everywhere: Add title sorting, add error toasts for HTMX

This commit is contained in:
Riyyi
2024-11-23 00:54:30 +01:00
parent f0cb958ea2
commit 293503d7a2
8 changed files with 112 additions and 33 deletions
@@ -1,6 +1,8 @@
import Fluent
import Vapor
// The code in this file is unused
struct TodoAPIController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
let todos = routes.grouped("todos")
+45 -16
View File
@@ -9,26 +9,31 @@ import VaporElementary
struct TodoController: RouteCollection {
func boot(routes: RoutesBuilder) throws {
routes.group("todos") { todos in
todos.get(use: index)
todos.get(use: index)
todos.post(use: create)
todos.group(":id") { todo in
todo.delete(use: delete)
}
todos.group("sort") { todo in
todo.get(use: sort)
}
}
}
@Sendable
func index(req: Request) async throws -> HTMLResponse {
let todos = try await todos(db: req.db)
let state = try getState(request: req)
state.todos.table.name = "todos"
state.todos.table.sort["title"] = state.todos.table.sort["title"] ?? .ascending
let todos = try await todos(db: req.db, title: state.todos.table.sort["title"]!)
state.todos.table.todos = todos
state.todos.table.refresh = false
return HTMLResponse {
MainLayout(title: "Todos") {
TodosTableComponent(name: "todos", todos: todos)
TodosFormComponent(name: "todos-form", target: "todos")
button(.class("btn btn-primary"), .type(.button), .hx.get("/test/toast")) { "Toast" }
TodosPage(table: state.todos.table)
}
}
}
@@ -37,44 +42,68 @@ struct TodoController: RouteCollection {
func create(req: Request) async throws -> HTMLResponse {
do {
try TodoDTO.validate(content: req)
}
catch let error as ValidationsError {
} catch let error as ValidationsError {
return HTMLResponse {
TodosFormComponent(name: "todos-form", target: "todos", errors: ["title": error.description])
TodosFormComponent(
name: "todos-form", target: "todos", errors: ["title": error.description])
}
}
let todo = try req.content.decode(TodoDTO.self).toModel()
try await todo.save(on: req.db)
let todos = try await todos(db: req.db)
let state = try getState(request: req)
let todos = try await todos(db: req.db, title: state.todos.table.sort["title"] ?? .ascending)
state.todos.table.todos = todos
state.todos.table.refresh = true
return HTMLResponse {
// Return the empty form
TodosFormComponent(name: "todos-form", target: "todos")
// Also update the todos table
TodosTableComponent(name: "todos", todos: todos, refresh: true) // TODO: Put component names inside variables
TodosTableComponent(state: state.todos.table) // TODO: Put component names inside variables
}
}
@Sendable
func delete(req: Request) async throws -> HTMLResponse {
guard let uuid = hexToUUID(hex: req.parameters.get("id")!),
let todo = try await Todo.find(uuid, on: req.db) else {
let todo = try await Todo.find(uuid, on: req.db)
else {
throw Abort(.notFound)
}
try await todo.delete(on: req.db)
return HTMLResponse {} // TODO: Return 204 No Content
return HTMLResponse {} // TODO: Return 204 No Content
}
struct Sort: Content {
let title: String
}
@Sendable
func sort(req: Request) async throws -> HTMLResponse {
let state = try getState(request: req)
let sort = try req.query.decode(Sort.self)
state.todos.table.sort["title"] = sort.title == "descending" ? .descending : .ascending
let todos = try await todos(db: req.db, title: state.todos.table.sort["title"]!)
state.todos.table.todos = todos
state.todos.table.refresh = true
return HTMLResponse {
TodosTableComponent(state: state.todos.table)
}
}
// -------------------------------------
@Sendable
private func todos(db: any Database) async throws -> [TodoDTO] {
try await Todo.query(on: db).all().map { $0.toDTO() }
private func todos(db: any Database, title: DatabaseQuery.Sort.Direction = .ascending) async throws -> [TodoDTO] {
try await Todo.query(on: db).sort("title", title).all().map { $0.toDTO() }
}
private func hexToUUID(hex: String) -> UUID? {