HAL for /api/projects

This commit is contained in:
Brieuc Dubois 2023-12-30 04:07:53 +01:00
parent daee67d6c3
commit 70c8f93c69
7 changed files with 149 additions and 189 deletions

View File

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

View File

@ -61,3 +61,13 @@ func UpdateProject(p types.Project) error {
_, err := db.Exec("UPDATE projects SET title = ? WHERE id = ?", p.Title, p.ID) _, err := db.Exec("UPDATE projects SET title = ? WHERE id = ?", p.Title, p.ID)
return err 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
}

View File

@ -6,21 +6,33 @@ import (
"git.bhasher.com/bhasher/focus/backend/db" "git.bhasher.com/bhasher/focus/backend/db"
"git.bhasher.com/bhasher/focus/backend/types" "git.bhasher.com/bhasher/focus/backend/types"
"git.bhasher.com/bhasher/focus/backend/utils"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
func CreateCard(c *fiber.Ctx) error { func CreateCard(c *fiber.Ctx) error {
card := types.Card{} card := types.Card{}
if err := c.BodyParser(&card); err != nil { 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) id, err := db.CreateCard(card)
if err != nil { 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 { func GetAllCardsOf(c *fiber.Ctx) error {

View File

@ -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)
}

View File

@ -1,18 +1,39 @@
package handlers package handlers
import ( import (
"fmt"
"git.bhasher.com/bhasher/focus/backend/db" "git.bhasher.com/bhasher/focus/backend/db"
"git.bhasher.com/bhasher/focus/backend/types" "git.bhasher.com/bhasher/focus/backend/types"
"git.bhasher.com/bhasher/focus/backend/utils"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
func GetAllProjects(c *fiber.Ctx) error { func GetAllProjects(c *fiber.Ctx) error {
projects, err := db.GetAllProjects() projects, err := db.GetAllProjects()
if err != nil { 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 { func GetProject(c *fiber.Ctx) error {
@ -23,12 +44,19 @@ func GetProject(c *fiber.Ctx) error {
project, err := db.GetProject(id) project, err := db.GetProject(id)
if err != nil { 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 { 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 { func CreateProject(c *fiber.Ctx) error {
@ -39,10 +67,17 @@ func CreateProject(c *fiber.Ctx) error {
id, err := db.CreateProject(*p) id, err := db.CreateProject(*p)
if err != nil { 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 { 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"}) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Error parsing request"})
} }
err = db.UpdateProject(p) exists, err := db.ExistProject(id)
if err != nil { 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 { 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"}) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid project ID"})
} }
err = db.DeleteProject(id) exists, err := db.ExistProject(id)
if err != nil { 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)
} }

View File

@ -30,35 +30,29 @@ func main() {
AllowHeaders: "Origin, Content-Type, Accept", 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/projects", handlers.GetAllProjects)
app.Get("/api/project/:id", handlers.GetProject) app.Get("/api/projects/:id", handlers.GetProject)
app.Put("/api/project/:id", handlers.UpdateProject) app.Put("/api/projects/:id", handlers.UpdateProject)
app.Delete("/api/project/:id", handlers.DeleteProject) 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.Post("/api/cards", handlers.CreateCard)
app.Get("/api/lists/:project_id", handlers.GetAllListsOf) app.Get("/api/cards/:id", handlers.GetCard)
app.Get("/api/list/:id", handlers.GetList) app.Put("/api/cards/:id", handlers.UpdateCard)
app.Delete("/api/list/:id", handlers.DeleteList) app.Delete("/api/cards/:id", handlers.DeleteCard)
app.Put("/api/list/:id", handlers.UpdateList)
app.Post("/api/card", handlers.CreateCard) app.Post("/api/tags", handlers.CreateTag)
app.Get("/api/cards/:project_id", handlers.GetAllCardsOf) app.Get("/api/tags/:id", handlers.GetTag)
app.Get("/api/card/:id", handlers.GetCard) app.Delete("/api/tags/:id", handlers.DeleteTag)
app.Delete("/api/card/:id", handlers.DeleteCard) app.Put("/api/tags/:id", handlers.UpdateTag)
app.Put("/api/card/:id", handlers.UpdateCard)
app.Post("/api/tag", handlers.CreateTag) app.Post("/api/cards/:card_id/tags/:tag_id", handlers.CreateTagOfCard)
app.Get("/api/tags/:project_id", handlers.GetAllTagsOf) app.Get("/api/cards/:card_id/tags", handlers.GetAllTagsOfCard)
app.Get("/api/tag/:id", handlers.GetTag) app.Put("/api/cards/:card_id/tags/:tag_id", handlers.UpdateTagOfCard)
app.Delete("/api/tag/:id", handlers.DeleteTag) app.Delete("/api/cards/:card_id/tags/:tag_id", handlers.DeleteTagOfCard)
app.Put("/api/tag/:id", handlers.UpdateTag) app.Delete("/api/cards/:card_id/tags", handlers.DeleteTagsOfCard)
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)
log.Fatal(app.Listen(fmt.Sprintf(":%v", port))) log.Fatal(app.Listen(fmt.Sprintf(":%v", port)))
} }

27
backend/utils/hal.go Normal file
View File

@ -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)},
},
}
}