diff --git a/backend/db/cardtags.go b/backend/db/cardtags.go new file mode 100644 index 0000000..4bfc347 --- /dev/null +++ b/backend/db/cardtags.go @@ -0,0 +1,52 @@ +package db + +import ( + "git.bhasher.com/bhasher/focus/backend/types" +) + +func AddTagToCard(ct types.CardTag) error { + _, err := db.Exec("INSERT INTO cardtags (card_id, tag_id, value) VALUES (?, ?, ?)", ct.CardID, ct.TagID, ct.Value) + return err +} + +func GetAllTagsOfCard(cardID int) ([]types.FullCardTag, error) { + rows, err := db.Query(`SELECT ct.card_id, ct.tag_id, t.title, ct.value + FROM cardtags ct + JOIN tags t ON ct.tag_id = t.id + WHERE ct.card_id = ?; + `, cardID) + if err != nil { + return nil, err + } + defer rows.Close() + + var cardtags []types.FullCardTag + for rows.Next() { + var ct types.FullCardTag + if err := rows.Scan(&ct.CardID, &ct.TagID, &ct.TagTitle, &ct.Value); err != nil { + return nil, err + } + cardtags = append(cardtags, ct) + } + + if err = rows.Err(); err != nil { + return nil, err + } + + return cardtags, nil +} + +func DeleteTagOfCard(card_id int, tag_id int) error { + _, err := db.Exec("DELETE FROM cardtags WHERE card_id = ? AND tag_id = ?", card_id, tag_id) + return err +} + +func DeleteTagsOfCard(card_id int) error { + _, err := db.Exec("DELETE FROM cardtags WHERE card_id = ?", card_id) + return err +} + +func UpdateTagOfCard(ct types.CardTag) error { + _, err := db.Exec("UPDATE cardtags SET value = ? WHERE card_id = ? AND tag_id = ?", ct.Value, ct.CardID, ct.TagID) + return err +} diff --git a/backend/db/main.go b/backend/db/main.go index f22f25f..591cf48 100644 --- a/backend/db/main.go +++ b/backend/db/main.go @@ -31,6 +31,20 @@ func InitDB(driver string, connStr string) error { title TEXT, content TEXT, FOREIGN KEY(project_id) REFERENCES projects(id) + ); + CREATE TABLE IF NOT EXISTS tags ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + project_id INTEGER, + title TEXT, + type int, + FOREIGN KEY(project_id) REFERENCES projects(id) + ); + CREATE TABLE IF NOT EXISTS cardtags ( + card_id INTEGER, + tag_id INTEGER, + value TEXT, + FOREIGN KEY(card_id) REFERENCES cards(id) + FOREIGN KEY(tag_id) REFERENCES tags(id) ); `) if err != nil { diff --git a/backend/db/tags.go b/backend/db/tags.go new file mode 100644 index 0000000..eb62322 --- /dev/null +++ b/backend/db/tags.go @@ -0,0 +1,61 @@ +package db + +import "git.bhasher.com/bhasher/focus/backend/types" + +func CreateTag(t types.Tag) (int, error) { + res, err := db.Exec("INSERT INTO tags (project_id, title, type) VALUES (?, ?, ?)", t.ProjectID, t.Title, t.Type) + if err != nil { + return 0, err + } + + id, err := res.LastInsertId() + if err != nil { + return 0, err + } + + return int(id), nil +} + +func GetAllTagsOf(projectID int) ([]types.Tag, error) { + rows, err := db.Query("SELECT * FROM tags WHERE project_id = ?", projectID) + if err != nil { + return nil, err + } + defer rows.Close() + + var tags []types.Tag + for rows.Next() { + var t types.Tag + if err := rows.Scan(&t.ID, &t.ProjectID, &t.Title, &t.Type); err != nil { + return nil, err + } + tags = append(tags, t) + } + + if err = rows.Err(); err != nil { + return nil, err + } + + return tags, nil +} + +func GetTag(id int) (*types.Tag, error) { + var t types.Tag + + err := db.QueryRow("SELECT * FROM tags WHERE id = ?", id).Scan(&t.ID, &t.ProjectID, &t.Title, &t.Type) + if err != nil { + return nil, err + } + + return &t, nil +} + +func DeleteTag(id int) error { + _, err := db.Exec("DELETE FROM tags WHERE id = ?", id) + return err +} + +func UpdateTag(t types.Tag) error { + _, err := db.Exec("UPDATE tags SET project_id = ?, title = ?, type = ? WHERE id = ?", t.ProjectID, t.Title, t.Type, t.ID) + return err +} diff --git a/backend/handlers/cards.go b/backend/handlers/cards.go index dce7bfd..e1e4090 100644 --- a/backend/handlers/cards.go +++ b/backend/handlers/cards.go @@ -9,7 +9,7 @@ import ( "github.com/gofiber/fiber/v2" ) -func CreateCards(c *fiber.Ctx) error { +func CreateCard(c *fiber.Ctx) error { card := types.Card{} if err := c.BodyParser(&card); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cannot parse request"}) diff --git a/backend/handlers/cardtags.go b/backend/handlers/cardtags.go new file mode 100644 index 0000000..91772b6 --- /dev/null +++ b/backend/handlers/cardtags.go @@ -0,0 +1,95 @@ +package handlers + +import ( + "fmt" + "strconv" + + "git.bhasher.com/bhasher/focus/backend/db" + "git.bhasher.com/bhasher/focus/backend/types" + "github.com/gofiber/fiber/v2" +) + +func CreateTagOfCard(c *fiber.Ctx) error { + cardtag := types.CardTag{} + if err := c.BodyParser(&cardtag); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cannot parse request"}) + } + + err := db.AddTagToCard(cardtag) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Cannot add tag to card"}) + } + + return c.SendStatus(fiber.StatusOK) +} + +func GetAllTagsOfCard(c *fiber.Ctx) error { + cardID, err := strconv.Atoi(c.Params("card_id")) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"status": "error", "error": "Invalid card_id"}) + } + + cardtags, err := db.GetAllTagsOfCard(cardID) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"status": "error", "error": "Cannot retrieve tags of card", "stack": fmt.Sprint(err)}) + } + + return c.JSON(fiber.Map{"status": "ok", "data": cardtags}) +} + +func DeleteTagOfCard(c *fiber.Ctx) error { + card_id, err := strconv.Atoi(c.Params("card_id")) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid card ID"}) + } + + tag_id, err := strconv.Atoi(c.Params("tag_id")) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid tag ID"}) + } + + err = db.DeleteTagOfCard(card_id, tag_id) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Cannot delete tag of card"}) + } + + return c.SendStatus(fiber.StatusNoContent) +} + +func DeleteTagsOfCard(c *fiber.Ctx) error { + card_id, err := strconv.Atoi(c.Params("card_id")) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid card ID"}) + } + + err = db.DeleteTagsOfCard(card_id) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Cannot delete tags of card"}) + } + + return c.SendStatus(fiber.StatusNoContent) +} + +func UpdateTagOfCard(c *fiber.Ctx) error { + card_id, err := strconv.Atoi(c.Params("card_id")) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid card ID"}) + } + + tag_id, err := strconv.Atoi(c.Params("tag_id")) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid tag ID"}) + } + + cardtag := types.CardTag{CardID: card_id, TagID: tag_id} + if err := c.BodyParser(&cardtag); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cannot parse request"}) + } + + err = db.UpdateTagOfCard(cardtag) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Cannot update tag of card"}) + } + + return c.SendStatus(fiber.StatusOK) +} diff --git a/backend/handlers/tags.go b/backend/handlers/tags.go new file mode 100644 index 0000000..ed0ebd1 --- /dev/null +++ b/backend/handlers/tags.go @@ -0,0 +1,88 @@ +package handlers + +import ( + "fmt" + "strconv" + + "git.bhasher.com/bhasher/focus/backend/db" + "git.bhasher.com/bhasher/focus/backend/types" + "github.com/gofiber/fiber/v2" +) + +func CreateTag(c *fiber.Ctx) error { + tag := types.Tag{} + if err := c.BodyParser(&tag); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cannot parse request"}) + } + + id, err := db.CreateTag(tag) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Cannot create tag", "trace": fmt.Sprint(err)}) + } + + return c.Status(fiber.StatusCreated).JSON(fiber.Map{"id": id}) +} + +func GetAllTagsOf(c *fiber.Ctx) error { + projectID, err := strconv.Atoi(c.Params("project_id")) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"status": "error", "error": "Invalid project_id"}) + } + + projects, err := db.GetAllTagsOf(projectID) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Cannot retrieve tags"}) + } + + return c.JSON(fiber.Map{"status": "ok", "data": projects}) +} + +func GetTag(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid tag ID"}) + } + + tag, err := db.GetTag(id) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Cannot retrieve tag"}) + } + if tag == nil { + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Tag not found"}) + } + + return c.JSON(tag) +} + +func DeleteTag(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid tag ID"}) + } + + err = db.DeleteTag(id) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Cannot delete tag"}) + } + + return c.SendStatus(fiber.StatusNoContent) +} + +func UpdateTag(c *fiber.Ctx) error { + id, err := strconv.Atoi(c.Params("id")) + if err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid tag ID"}) + } + + tag := types.Tag{ID: id} + if err := c.BodyParser(&tag); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cannot parse request"}) + } + + err = db.UpdateTag(tag) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Cannot update tag"}) + } + + return c.SendStatus(fiber.StatusOK) +} diff --git a/backend/main.go b/backend/main.go index bd523e6..7d07b94 100644 --- a/backend/main.go +++ b/backend/main.go @@ -42,11 +42,23 @@ func main() { app.Delete("/api/list/:id", handlers.DeleteList) app.Put("/api/list/:id", handlers.UpdateList) - app.Post("/api/card", handlers.CreateCards) + 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/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.UpdateTag) + log.Fatal(app.Listen(fmt.Sprintf(":%v", port))) } diff --git a/backend/types/cards.go b/backend/types/card.go similarity index 100% rename from backend/types/cards.go rename to backend/types/card.go diff --git a/backend/types/cardtag.go b/backend/types/cardtag.go new file mode 100644 index 0000000..ddbefb0 --- /dev/null +++ b/backend/types/cardtag.go @@ -0,0 +1,14 @@ +package types + +type CardTag struct { + CardID int `json:"card_id"` + TagID int `json:"tag_id"` + Value string `json:"value"` +} + +type FullCardTag struct { + CardID int `json:"card_id"` + TagID int `json:"tag_id"` + TagTitle string `json:"tag_title"` + Value string `json:"value"` +} diff --git a/backend/types/lists.go b/backend/types/list.go similarity index 100% rename from backend/types/lists.go rename to backend/types/list.go diff --git a/backend/types/projects.go b/backend/types/project.go similarity index 100% rename from backend/types/projects.go rename to backend/types/project.go diff --git a/backend/types/tag.go b/backend/types/tag.go new file mode 100644 index 0000000..ebf4283 --- /dev/null +++ b/backend/types/tag.go @@ -0,0 +1,8 @@ +package types + +type Tag struct { + ID int `json:"id"` + ProjectID int `json:"project_id"` + Title string `json:"title"` + Type int `json:"type"` +} diff --git a/frontend/src/components/card.svelte b/frontend/src/components/card.svelte index 1067964..899b926 100644 --- a/frontend/src/components/card.svelte +++ b/frontend/src/components/card.svelte @@ -1,4 +1,9 @@ @@ -18,8 +41,13 @@
{card.title}
-
- HIGH - PERSONAL -
+ {#if tags} +
+ + {#each tags as tag} + {tag.value} + {/each} +
+ {/if}