backend tag options

This commit is contained in:
Brieuc Dubois 2023-12-31 01:38:17 +01:00
parent c69dadc4bf
commit 950963d7dc
8 changed files with 265 additions and 24 deletions

View File

@ -18,7 +18,7 @@ func GetCardTags(cardID int, projectID int) ([]types.FullCardTag, error) {
projectID = card.ProjectID projectID = card.ProjectID
} }
rows, err := db.Query(`SELECT t.id, t.title, COALESCE(ct.value, '') rows, err := db.Query(`SELECT t.id, t.title, t.type, COALESCE(ct.value, '')
FROM tags t FROM tags t
LEFT JOIN cardtags ct ON ct.tag_id = t.id AND ct.card_id = ? LEFT JOIN cardtags ct ON ct.tag_id = t.id AND ct.card_id = ?
WHERE t.project_id = ? WHERE t.project_id = ?
@ -31,7 +31,7 @@ func GetCardTags(cardID int, projectID int) ([]types.FullCardTag, error) {
var cardtags []types.FullCardTag var cardtags []types.FullCardTag
for rows.Next() { for rows.Next() {
ct := types.FullCardTag{CardID: cardID} ct := types.FullCardTag{CardID: cardID}
if err := rows.Scan(&ct.TagID, &ct.TagTitle, &ct.Value); err != nil { if err := rows.Scan(&ct.TagID, &ct.TagTitle, &ct.TagType, &ct.Value); err != nil {
return nil, err return nil, err
} }
cardtags = append(cardtags, ct) cardtags = append(cardtags, ct)

View File

@ -46,6 +46,12 @@ func InitDB(driver string, connStr string) error {
PRIMARY KEY(card_id, tag_id), PRIMARY KEY(card_id, tag_id),
FOREIGN KEY(card_id) REFERENCES cards(id) FOREIGN KEY(card_id) REFERENCES cards(id)
FOREIGN KEY(tag_id) REFERENCES tags(id) FOREIGN KEY(tag_id) REFERENCES tags(id)
);
CREATE TABLE IF NOT EXISTS tagsoptions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tag_id INTEGER,
value TEXT,
FOREIGN KEY(tag_id) REFERENCES tags(id)
); );
`) `)
if err != nil { if err != nil {

View File

@ -16,19 +16,25 @@ func CreateTag(t types.Tag) (int, error) {
return int(id), nil return int(id), nil
} }
func GetProjectTags(projectID int) ([]types.Tag, error) { func GetProjectTags(projectID int) ([]types.FullTag, error) {
rows, err := db.Query("SELECT * FROM tags WHERE project_id = ?", projectID) rows, err := db.Query("SELECT * FROM tags WHERE project_id = ?", projectID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer rows.Close() defer rows.Close()
var tags []types.Tag var tags []types.FullTag
for rows.Next() { for rows.Next() {
var t types.Tag var t types.FullTag
if err := rows.Scan(&t.ID, &t.ProjectID, &t.Title, &t.Type); err != nil { if err := rows.Scan(&t.ID, &t.ProjectID, &t.Title, &t.Type); err != nil {
return nil, err return nil, err
} }
t.Options, err = GetTagOptions(t.ID)
if err != nil {
return nil, err
}
tags = append(tags, t) tags = append(tags, t)
} }
@ -81,3 +87,61 @@ func ExistTag(id int) (bool, error) {
return count > 0, nil return count > 0, nil
} }
func CreateTagOption(to types.TagOption) (int, error) {
res, err := db.Exec("INSERT INTO tagsoptions (tag_id, value) VALUES (?, ?)", to.TagID, to.Value)
if err != nil {
return 0, err
}
id, err := res.LastInsertId()
if err != nil {
return 0, err
}
return int(id), nil
}
func GetTagOptions(tagID int) ([]types.TagOption, error) {
rows, err := db.Query("SELECT * FROM tagsoptions WHERE tag_id = ?", tagID)
if err != nil {
return nil, err
}
defer rows.Close()
var options []types.TagOption
for rows.Next() {
var to types.TagOption
if err := rows.Scan(&to.ID, &to.TagID, &to.Value); err != nil {
return nil, err
}
options = append(options, to)
}
if err = rows.Err(); err != nil {
return nil, err
}
return options, nil
}
func DeleteTagOption(id int) (int64, error) {
res, err := db.Exec("DELETE FROM tagsoptions WHERE id = ?", id)
if err != nil {
return 0, err
}
return res.RowsAffected()
}
func UpdateTagOption(to types.TagOption) (int64, error) {
res, err := db.Exec("UPDATE tagsoptions SET tag_id = ?, value = ? WHERE id = ?", to.TagID, to.Value, to.ID)
if err != nil {
return 0, err
}
return res.RowsAffected()
}
func DeleteTagOptions(tagID int) error {
_, err := db.Exec("DELETE FROM tagsoptions WHERE tag_id = ?", tagID)
return err
}

View File

@ -11,8 +11,6 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.50.0 // indirect github.com/valyala/fasthttp v1.50.0 // indirect
@ -20,7 +18,4 @@ require (
golang.org/x/sys v0.14.0 // indirect golang.org/x/sys v0.14.0 // indirect
) )
require ( require github.com/mattn/go-sqlite3 v1.14.19
github.com/json-iterator/go v1.1.12
github.com/mattn/go-sqlite3 v1.14.19
)

View File

@ -1,14 +1,9 @@
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gofiber/fiber/v2 v2.51.0 h1:JNACcZy5e2tGApWB2QrRpenTWn0fq0hkFm6k0C86gKQ= github.com/gofiber/fiber/v2 v2.51.0 h1:JNACcZy5e2tGApWB2QrRpenTWn0fq0hkFm6k0C86gKQ=
github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U= github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
@ -20,15 +15,8 @@ github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZ
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI= github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M= github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M=

View File

@ -14,6 +14,11 @@ func tagsRouter(router fiber.Router) {
router.Get("/:id", GetTag) router.Get("/:id", GetTag)
router.Delete("/:id", DeleteTag) router.Delete("/:id", DeleteTag)
router.Put("/:id", UpdateTag) router.Put("/:id", UpdateTag)
router.Post("/:id/options", CreateTagOption)
router.Get("/:id/options", GetTagOptions)
router.Delete("/:id/options/:option_id", DeleteTagOption)
router.Put("/:id/options/:option_id", UpdateTagOption)
router.Delete("/:id/options", DeleteTagOptions)
} }
func CreateTag(c *fiber.Ctx) error { func CreateTag(c *fiber.Ctx) error {
@ -50,7 +55,7 @@ func GetTag(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusNotFound) return c.SendStatus(fiber.StatusNotFound)
} }
return c.JSON(tag) return c.Status(fiber.StatusOK).JSON(tag)
} }
func DeleteTag(c *fiber.Ctx) error { func DeleteTag(c *fiber.Ctx) error {
@ -99,3 +104,171 @@ func UpdateTag(c *fiber.Ctx) error {
return c.SendStatus(fiber.StatusOK) return c.SendStatus(fiber.StatusOK)
} }
func CreateTagOption(c *fiber.Ctx) error {
tagID, err := strconv.Atoi(c.Params("id"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid tag ID"})
}
option := types.TagOption{TagID: tagID}
if err := c.BodyParser(&option); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cannot parse request"})
}
exist, err := db.ExistTag(tagID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Error finding tag",
"trace": fmt.Sprint(err),
})
}
if !exist {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Tag not found"})
}
id, err := db.CreateTagOption(option)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Cannot create tag option",
"trace": fmt.Sprint(err),
})
}
return c.Status(fiber.StatusCreated).JSON(fiber.Map{"id": id})
}
func GetTagOptions(c *fiber.Ctx) error {
tagID, err := strconv.Atoi(c.Params("id"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid tag ID"})
}
exist, err := db.ExistTag(tagID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Error finding tag",
"trace": fmt.Sprint(err),
})
}
if !exist {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Tag not found"})
}
options, err := db.GetTagOptions(tagID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Cannot retrieve tag options",
"trace": fmt.Sprint(err),
})
}
return c.Status(fiber.StatusOK).JSON(options)
}
func DeleteTagOption(c *fiber.Ctx) error {
tagID, err := strconv.Atoi(c.Params("id"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid tag ID"})
}
optionID, err := strconv.Atoi(c.Params("option_id"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid tag option ID"})
}
exist, err := db.ExistTag(tagID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Error finding tag",
"trace": fmt.Sprint(err),
})
}
if !exist {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Tag not found"})
}
count, err := db.DeleteTagOption(optionID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Cannot delete tag option",
"trace": fmt.Sprint(err),
})
}
if count == 0 {
return c.SendStatus(fiber.StatusNotFound)
}
return c.SendStatus(fiber.StatusNoContent)
}
func UpdateTagOption(c *fiber.Ctx) error {
tagID, err := strconv.Atoi(c.Params("id"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid tag ID"})
}
optionID, err := strconv.Atoi(c.Params("option_id"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid tag option ID"})
}
option := types.TagOption{ID: optionID, TagID: tagID}
if err := c.BodyParser(&option); err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Cannot parse request"})
}
exist, err := db.ExistTag(tagID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Error finding tag",
"trace": fmt.Sprint(err),
})
}
if !exist {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Tag not found"})
}
count, err := db.UpdateTagOption(option)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Cannot update tag option",
"trace": fmt.Sprint(err),
})
}
if count == 0 {
return c.SendStatus(fiber.StatusNotFound)
}
return c.SendStatus(fiber.StatusOK)
}
func DeleteTagOptions(c *fiber.Ctx) error {
tagID, err := strconv.Atoi(c.Params("id"))
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid tag ID"})
}
exist, err := db.ExistTag(tagID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Error finding tag",
"trace": fmt.Sprint(err),
})
}
if !exist {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Tag not found"})
}
err = db.DeleteTagOptions(tagID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Cannot delete tag options",
"trace": fmt.Sprint(err),
})
}
return c.SendStatus(fiber.StatusNoContent)
}

View File

@ -10,5 +10,6 @@ type FullCardTag struct {
CardID int `json:"card_id"` CardID int `json:"card_id"`
TagID int `json:"tag_id"` TagID int `json:"tag_id"`
TagTitle string `json:"tag_title"` TagTitle string `json:"tag_title"`
TagType int `json:"tag_type"`
Value string `json:"value"` Value string `json:"value"`
} }

View File

@ -6,3 +6,17 @@ type Tag struct {
Title string `json:"title"` Title string `json:"title"`
Type int `json:"type"` Type int `json:"type"`
} }
type TagOption struct {
ID int `json:"id"`
TagID int `json:"tag_id"`
Value string `json:"value"`
}
type FullTag struct {
ID int `json:"id"`
ProjectID int `json:"project_id"`
Title string `json:"title"`
Type int `json:"type"`
Options []TagOption `json:"options"`
}