From 70c8f93c69d001b9dffba51578f194f11682a08a Mon Sep 17 00:00:00 2001 From: Bhasher Date: Sat, 30 Dec 2023 04:07:53 +0100 Subject: [PATCH] HAL for /api/projects --- backend/db/lists.go | 61 ------------------------ backend/db/projects.go | 10 ++++ backend/handlers/cards.go | 18 +++++-- backend/handlers/lists.go | 87 ---------------------------------- backend/handlers/projects.go | 91 ++++++++++++++++++++++++++++++------ backend/main.go | 44 ++++++++--------- backend/utils/hal.go | 27 +++++++++++ 7 files changed, 149 insertions(+), 189 deletions(-) delete mode 100644 backend/db/lists.go delete mode 100644 backend/handlers/lists.go create mode 100644 backend/utils/hal.go diff --git a/backend/db/lists.go b/backend/db/lists.go deleted file mode 100644 index 1e94fea..0000000 --- a/backend/db/lists.go +++ /dev/null @@ -1,61 +0,0 @@ -package db - -import "git.bhasher.com/bhasher/focus/backend/types" - -func CreateList(l types.List) (int, error) { - res, err := db.Exec("INSERT INTO lists (project_id, title, color) VALUES (?, ?, ?)", l.ProjectID, l.Title, l.Color) - if err != nil { - return 0, err - } - - id, err := res.LastInsertId() - if err != nil { - return 0, err - } - - return int(id), nil -} - -func GetAllListsOf(projectID int) ([]types.List, error) { - rows, err := db.Query("SELECT * FROM lists WHERE project_id = ?", projectID) - if err != nil { - return nil, err - } - defer rows.Close() - - var lists []types.List - for rows.Next() { - var l types.List - if err := rows.Scan(&l.ID, &l.ProjectID, &l.Title, &l.Color); err != nil { - return nil, err - } - lists = append(lists, l) - } - - if err = rows.Err(); err != nil { - return nil, err - } - - return lists, nil -} - -func GetList(id int) (*types.List, error) { - var l types.List - - err := db.QueryRow("SELECT * FROM lists WHERE id = ?", id).Scan(&l.ID, &l.ProjectID, &l.Title, &l.Color) - if err != nil { - return nil, err - } - - return &l, nil -} - -func DeleteList(id int) error { - _, err := db.Exec("DELETE FROM lists WHERE id = ?", id) - return err -} - -func UpdateList(l types.List) error { - _, err := db.Exec("UPDATE lists SET project_id = ?, title = ?, color = ? WHERE id = ?", l.ProjectID, l.Title, l.Color, l.ID) - return err -} diff --git a/backend/db/projects.go b/backend/db/projects.go index f5bbbb3..de94a1e 100644 --- a/backend/db/projects.go +++ b/backend/db/projects.go @@ -61,3 +61,13 @@ func UpdateProject(p types.Project) error { _, err := db.Exec("UPDATE projects SET title = ? WHERE id = ?", p.Title, p.ID) return err } + +func ExistProject(id int) (bool, error) { + var count int + err := db.QueryRow("SELECT COUNT(*) FROM projects WHERE id = ?", id).Scan(&count) + if err != nil { + return false, err + } + + return count > 0, nil +} diff --git a/backend/handlers/cards.go b/backend/handlers/cards.go index 82c185e..8eaab3b 100644 --- a/backend/handlers/cards.go +++ b/backend/handlers/cards.go @@ -6,21 +6,33 @@ import ( "git.bhasher.com/bhasher/focus/backend/db" "git.bhasher.com/bhasher/focus/backend/types" + "git.bhasher.com/bhasher/focus/backend/utils" "github.com/gofiber/fiber/v2" ) func CreateCard(c *fiber.Ctx) error { card := types.Card{} if err := c.BodyParser(&card); err != nil { - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"status": "error", "error": "Cannot parse request", "trace": fmt.Sprint(err)}) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "Cannot parse request", + "trace": fmt.Sprint(err), + }) } id, err := db.CreateCard(card) if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"status": "error", "error": "Cannot create card", "trace": fmt.Sprint(err)}) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Cannot create card", + "trace": fmt.Sprint(err), + }) } - return c.Status(fiber.StatusCreated).JSON(fiber.Map{"status": "ok", "id": id}) + c.Status(fiber.StatusCreated) + c.Location(fmt.Sprintf("/api/cards/%v", id)) + return c.JSON(fiber.Map{ + "id": id, + "_links": utils.HALProjectLinks(id), + }) } func GetAllCardsOf(c *fiber.Ctx) error { diff --git a/backend/handlers/lists.go b/backend/handlers/lists.go deleted file mode 100644 index dee7024..0000000 --- a/backend/handlers/lists.go +++ /dev/null @@ -1,87 +0,0 @@ -package handlers - -import ( - "strconv" - - "git.bhasher.com/bhasher/focus/backend/db" - "git.bhasher.com/bhasher/focus/backend/types" - "github.com/gofiber/fiber/v2" -) - -func CreateList(c *fiber.Ctx) error { - list := types.List{} - if err := c.BodyParser(&list); err != nil { - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cannot parse request"}) - } - - id, err := db.CreateList(list) - if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Cannot create list"}) - } - - return c.Status(fiber.StatusCreated).JSON(fiber.Map{"id": id}) -} - -func GetAllListsOf(c *fiber.Ctx) error { - projectID, err := strconv.Atoi(c.Params("project_id")) - if err != nil { - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid project ID"}) - } - - lists, err := db.GetAllListsOf(projectID) - if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Cannot retrieve lists"}) - } - - return c.JSON(lists) -} - -func GetList(c *fiber.Ctx) error { - id, err := strconv.Atoi(c.Params("id")) - if err != nil { - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid list ID"}) - } - - list, err := db.GetList(id) - if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Cannot retrieve list"}) - } - if list == nil { - return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "List not found"}) - } - - return c.JSON(list) -} - -func DeleteList(c *fiber.Ctx) error { - id, err := strconv.Atoi(c.Params("id")) - if err != nil { - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid list ID"}) - } - - err = db.DeleteList(id) - if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Cannot delete list"}) - } - - return c.SendStatus(fiber.StatusNoContent) -} - -func UpdateList(c *fiber.Ctx) error { - id, err := strconv.Atoi(c.Params("id")) - if err != nil { - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid list ID"}) - } - - list := types.List{ID: id} - if err := c.BodyParser(&list); err != nil { - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cannot parse request"}) - } - - err = db.UpdateList(list) - if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Cannot update list"}) - } - - return c.SendStatus(fiber.StatusOK) -} diff --git a/backend/handlers/projects.go b/backend/handlers/projects.go index 7455310..68e4830 100644 --- a/backend/handlers/projects.go +++ b/backend/handlers/projects.go @@ -1,18 +1,39 @@ package handlers import ( + "fmt" + "git.bhasher.com/bhasher/focus/backend/db" "git.bhasher.com/bhasher/focus/backend/types" + "git.bhasher.com/bhasher/focus/backend/utils" "github.com/gofiber/fiber/v2" ) func GetAllProjects(c *fiber.Ctx) error { projects, err := db.GetAllProjects() if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Cannot retrieve projects"}) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Cannot retrieve projects", + "trace": fmt.Sprint(err), + }) } - return c.JSON(projects) + halProjects := make([]fiber.Map, len(projects)) + for i, p := range projects { + halProjects[i] = fiber.Map{ + "project": p, + "_links": utils.HALProjectLinks(p.ID), + } + } + + return utils.SendHAL(c, fiber.StatusOK, fiber.Map{ + "_links": fiber.Map{ + "self": fiber.Map{"href": "/api/projects"}, + }, + "_embedded": fiber.Map{ + "projects": halProjects, + }, + }) } func GetProject(c *fiber.Ctx) error { @@ -23,12 +44,19 @@ func GetProject(c *fiber.Ctx) error { project, err := db.GetProject(id) if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Error fetching project"}) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Error fetching project", + "trace": fmt.Sprint(err), + }) } if project == nil { - return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Project not found"}) + return c.SendStatus(fiber.StatusNotFound) } - return c.JSON(project) + + return utils.SendHAL(c, fiber.StatusOK, fiber.Map{ + "project": project, + "_links": utils.HALProjectLinks(project.ID), + }) } func CreateProject(c *fiber.Ctx) error { @@ -39,10 +67,17 @@ func CreateProject(c *fiber.Ctx) error { id, err := db.CreateProject(*p) if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Error creating project"}) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Error creating project", + "trace": fmt.Sprint(err), + }) } - return c.Status(fiber.StatusCreated).JSON(fiber.Map{"id": id}) + c.Location(fmt.Sprintf("/api/projects/%v", id)) + return utils.SendHAL(c, fiber.StatusCreated, fiber.Map{ + "id": id, + "_links": utils.HALProjectLinks(id), + }) } func UpdateProject(c *fiber.Ctx) error { @@ -56,12 +91,27 @@ func UpdateProject(c *fiber.Ctx) error { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Error parsing request"}) } - err = db.UpdateProject(p) + exists, err := db.ExistProject(id) if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Error updating project"}) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Error finding project", + "trace": fmt.Sprint(err), + }) } - return c.SendStatus(fiber.StatusOK) + if !exists { + return c.SendStatus(fiber.StatusNotFound) + } + + err = db.UpdateProject(p) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Error updating project", + "trace": fmt.Sprint(err), + }) + } + + return c.SendStatus(fiber.StatusNoContent) } func DeleteProject(c *fiber.Ctx) error { @@ -70,10 +120,25 @@ func DeleteProject(c *fiber.Ctx) error { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid project ID"}) } - err = db.DeleteProject(id) + exists, err := db.ExistProject(id) if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Error deleting project"}) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Error finding project", + "trace": fmt.Sprint(err), + }) } - return c.SendStatus(fiber.StatusOK) + if !exists { + return c.SendStatus(fiber.StatusNotFound) + } + + err = db.DeleteProject(id) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": "Error deleting project", + "trace": fmt.Sprint(err), + }) + } + + return c.SendStatus(fiber.StatusNoContent) } diff --git a/backend/main.go b/backend/main.go index 9d8505f..72c7a13 100644 --- a/backend/main.go +++ b/backend/main.go @@ -30,35 +30,29 @@ func main() { AllowHeaders: "Origin, Content-Type, Accept", })) - app.Post("/api/project", handlers.CreateProject) + app.Post("/api/projects", handlers.CreateProject) app.Get("/api/projects", handlers.GetAllProjects) - app.Get("/api/project/:id", handlers.GetProject) - app.Put("/api/project/:id", handlers.UpdateProject) - app.Delete("/api/project/:id", handlers.DeleteProject) + app.Get("/api/projects/:id", handlers.GetProject) + app.Put("/api/projects/:id", handlers.UpdateProject) + app.Delete("/api/projects/:id", handlers.DeleteProject) + app.Get("/api/projects/:project_id/cards", handlers.GetAllCardsOf) + app.Get("/api/projects/:project_id/tags", handlers.GetAllTagsOf) - app.Post("/api/list", handlers.CreateList) - app.Get("/api/lists/:project_id", handlers.GetAllListsOf) - app.Get("/api/list/:id", handlers.GetList) - app.Delete("/api/list/:id", handlers.DeleteList) - app.Put("/api/list/:id", handlers.UpdateList) + app.Post("/api/cards", handlers.CreateCard) + app.Get("/api/cards/:id", handlers.GetCard) + app.Put("/api/cards/:id", handlers.UpdateCard) + app.Delete("/api/cards/:id", handlers.DeleteCard) - app.Post("/api/card", handlers.CreateCard) - app.Get("/api/cards/:project_id", handlers.GetAllCardsOf) - app.Get("/api/card/:id", handlers.GetCard) - app.Delete("/api/card/:id", handlers.DeleteCard) - app.Put("/api/card/:id", handlers.UpdateCard) + app.Post("/api/tags", handlers.CreateTag) + app.Get("/api/tags/:id", handlers.GetTag) + app.Delete("/api/tags/:id", handlers.DeleteTag) + app.Put("/api/tags/:id", handlers.UpdateTag) - app.Post("/api/tag", handlers.CreateTag) - app.Get("/api/tags/:project_id", handlers.GetAllTagsOf) - app.Get("/api/tag/:id", handlers.GetTag) - app.Delete("/api/tag/:id", handlers.DeleteTag) - app.Put("/api/tag/:id", handlers.UpdateTag) - - app.Post("/api/cardtag", handlers.CreateTagOfCard) - app.Get("/api/cardtags/:card_id", handlers.GetAllTagsOfCard) - app.Delete("/api/cardtag/:card_id/:tag_id", handlers.DeleteTagOfCard) - app.Delete("/api/cardtags/:card_id", handlers.DeleteTagsOfCard) - app.Put("/api/cardtag/:card_id/:tag_id", handlers.UpdateTagOfCard) + app.Post("/api/cards/:card_id/tags/:tag_id", handlers.CreateTagOfCard) + app.Get("/api/cards/:card_id/tags", handlers.GetAllTagsOfCard) + app.Put("/api/cards/:card_id/tags/:tag_id", handlers.UpdateTagOfCard) + app.Delete("/api/cards/:card_id/tags/:tag_id", handlers.DeleteTagOfCard) + app.Delete("/api/cards/:card_id/tags", handlers.DeleteTagsOfCard) log.Fatal(app.Listen(fmt.Sprintf(":%v", port))) } diff --git a/backend/utils/hal.go b/backend/utils/hal.go new file mode 100644 index 0000000..345bf74 --- /dev/null +++ b/backend/utils/hal.go @@ -0,0 +1,27 @@ +package utils + +import ( + "fmt" + + "github.com/gofiber/fiber/v2" +) + +func SendHAL(c *fiber.Ctx, status int, data fiber.Map) error { + if err := c.JSON(data); err != nil { + return err + } + + c.Status(status) + c.Context().SetContentType("application/hal+json") + return nil +} + +func HALProjectLinks(id int) fiber.Map { + return fiber.Map{ + "_links": fiber.Map{ + "self": fiber.Map{"href": fmt.Sprintf("/api/projects/%v", id)}, + "cards": fiber.Map{"href": fmt.Sprintf("/api/projects/%v/cards", id)}, + "tags": fiber.Map{"href": fmt.Sprintf("/api/projects/%v/tags", id)}, + }, + } +}