Tag CRUD APIs

This commit is contained in:
abhishek9686
2024-09-17 19:34:45 +04:00
parent 2e7d9ad826
commit d64f098181
7 changed files with 278 additions and 31 deletions

View File

@@ -34,6 +34,7 @@ var HttpHandlers = []interface{}{
loggerHandlers,
hostHandlers,
enrollmentKeyHandlers,
tagHandlers,
legacyHandlers,
}

80
controllers/tags.go Normal file
View File

@@ -0,0 +1,80 @@
package controller
import (
"encoding/json"
"net/http"
"github.com/gorilla/mux"
"github.com/gravitl/netmaker/logger"
"github.com/gravitl/netmaker/logic"
"github.com/gravitl/netmaker/models"
)
func tagHandlers(r *mux.Router) {
r.HandleFunc("/api/v1/tags", logic.SecurityCheck(true, http.HandlerFunc(getAllTags))).
Methods(http.MethodGet)
r.HandleFunc("/api/v1/tags", logic.SecurityCheck(true, http.HandlerFunc(createTag))).
Methods(http.MethodPost)
r.HandleFunc("/api/v1/tags", logic.SecurityCheck(true, http.HandlerFunc(updateTag))).
Methods(http.MethodPut)
}
// @Summary Get all Tag entries
// @Router /api/v1/tags [get]
// @Tags TAG
// @Accept json
// @Success 200 {array} models.SuccessResponse
// @Failure 500 {object} models.ErrorResponse
func getAllTags(w http.ResponseWriter, r *http.Request) {
tags, err := logic.ListTagsWithHosts()
if err != nil {
logger.Log(0, r.Header.Get("user"), "failed to get all DNS entries: ", err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal"))
return
}
logic.SortTagEntrys(tags[:])
logic.ReturnSuccessResponseWithJson(w, r, tags, "fetched all tags")
}
// @Summary Create Tag
// @Router /api/v1/tags [post]
// @Tags TAG
// @Accept json
// @Success 200 {array} models.SuccessResponse
// @Failure 500 {object} models.ErrorResponse
func createTag(w http.ResponseWriter, r *http.Request) {
var tag models.Tag
err := json.NewDecoder(r.Body).Decode(&tag)
if err != nil {
logger.Log(0, "error decoding request body: ",
err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
err = logic.InsertTag(tag)
if err != nil {
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
logic.ReturnSuccessResponseWithJson(w, r, tag, "created tag successfully")
}
// @Summary Update Tag
// @Router /api/v1/tags [put]
// @Tags TAG
// @Accept json
// @Success 200 {array} models.SuccessResponse
// @Failure 500 {object} models.ErrorResponse
func updateTag(w http.ResponseWriter, r *http.Request) {
var updateTag models.UpdateTagReq
err := json.NewDecoder(r.Body).Decode(&updateTag)
if err != nil {
logger.Log(0, "error decoding request body: ",
err.Error())
logic.ReturnErrorResponse(w, r, logic.FormatError(err, "badrequest"))
return
}
go logic.UpdateTag(updateTag)
logic.ReturnSuccessResponse(w, r, "updating tags")
}

View File

@@ -67,6 +67,8 @@ const (
PENDING_USERS_TABLE_NAME = "pending_users"
// USER_INVITES - table for user invites
USER_INVITES_TABLE_NAME = "user_invites"
// TAG_TABLE_NAME - table for tags
TAG_TABLE_NAME = "tags"
// == ERROR CONSTS ==
// NO_RECORD - no singular result found
NO_RECORD = "no result found"
@@ -152,6 +154,7 @@ func createTables() {
CreateTable(PENDING_USERS_TABLE_NAME)
CreateTable(USER_PERMISSIONS_TABLE_NAME)
CreateTable(USER_INVITES_TABLE_NAME)
CreateTable(TAG_TABLE_NAME)
}
func CreateTable(tableName string) error {

View File

@@ -572,3 +572,35 @@ func SortApiHosts(unsortedHosts []models.ApiHost) {
return unsortedHosts[i].ID < unsortedHosts[j].ID
})
}
func GetTagMapWithHosts() (tagHostMap map[models.TagID][]models.Host) {
tagHostMap = make(map[models.TagID][]models.Host)
hosts, _ := GetAllHosts()
for _, hostI := range hosts {
if hostI.Tags == nil {
continue
}
for hostTagID := range hostI.Tags {
if _, ok := tagHostMap[hostTagID]; ok {
tagHostMap[hostTagID] = append(tagHostMap[hostTagID], hostI)
} else {
tagHostMap[hostTagID] = []models.Host{hostI}
}
}
}
return
}
func GetHostsWithTag(tagID models.TagID) map[string]models.Host {
hMap := make(map[string]models.Host)
hosts, _ := GetAllHosts()
for _, hostI := range hosts {
if hostI.Tags == nil {
continue
}
if _, ok := hostI.Tags[tagID]; ok {
hMap[hostI.ID.String()] = hostI
}
}
return hMap
}

104
logic/tags.go Normal file
View File

@@ -0,0 +1,104 @@
package logic
import (
"encoding/json"
"fmt"
"sort"
"github.com/gravitl/netmaker/database"
"github.com/gravitl/netmaker/models"
)
func GetTag(tagID string) (models.Tag, error) {
data, err := database.FetchRecord(database.TAG_TABLE_NAME, tagID)
if err != nil && !database.IsEmptyRecord(err) {
return models.Tag{}, err
}
tag := models.Tag{}
err = json.Unmarshal([]byte(data), &tag)
if err != nil {
return tag, err
}
return tag, nil
}
func InsertTag(tag models.Tag) error {
_, err := database.FetchRecord(database.TAG_TABLE_NAME, tag.ID.String())
if err == nil {
return fmt.Errorf("tag `%s` exists already", tag.ID)
}
d, err := json.Marshal(tag)
if err != nil {
return err
}
return database.Insert(tag.ID.String(), string(d), database.TAG_TABLE_NAME)
}
func DeleteTag(tagID string) error {
return database.DeleteRecord(database.TAG_TABLE_NAME, tagID)
}
func ListTagsWithHosts() ([]models.TagListResp, error) {
tags, err := ListTags()
if err != nil {
return []models.TagListResp{}, err
}
tagsHostMap := GetTagMapWithHosts()
resp := []models.TagListResp{}
for _, tagI := range tags {
tagRespI := models.TagListResp{
Tag: tagI,
UsedByCnt: len(tagsHostMap[tagI.ID]),
TaggedHosts: tagsHostMap[tagI.ID],
}
resp = append(resp, tagRespI)
}
return resp, nil
}
func ListTags() ([]models.Tag, error) {
data, err := database.FetchRecords(database.TAG_TABLE_NAME)
if err != nil && !database.IsEmptyRecord(err) {
return []models.Tag{}, err
}
tags := []models.Tag{}
for _, dataI := range data {
tag := models.Tag{}
err := json.Unmarshal([]byte(dataI), &tag)
if err != nil {
continue
}
tags = append(tags, tag)
}
return tags, nil
}
func UpdateTag(req models.UpdateTagReq) {
tagHostsMap := GetHostsWithTag(req.ID)
for _, hostI := range req.TaggedHosts {
hostI := hostI
if _, ok := tagHostsMap[hostI.ID.String()]; !ok {
if hostI.Tags == nil {
hostI.Tags = make(map[models.TagID]struct{})
}
hostI.Tags[req.ID] = struct{}{}
UpsertHost(&hostI)
} else {
delete(tagHostsMap, hostI.ID.String())
}
}
for _, deletedTaggedHost := range tagHostsMap {
deletedTaggedHost := deletedTaggedHost
delete(deletedTaggedHost.Tags, req.ID)
UpsertHost(&deletedTaggedHost)
}
}
// SortTagEntrys - Sorts slice of Tag entries by their id
func SortTagEntrys(tags []models.TagListResp) {
sort.Slice(tags, func(i, j int) bool {
return tags[i].ID < tags[j].ID
})
}

View File

@@ -72,6 +72,7 @@ type Host struct {
NatType string `json:"nat_type,omitempty" yaml:"nat_type,omitempty"`
TurnEndpoint *netip.AddrPort `json:"turn_endpoint,omitempty" yaml:"turn_endpoint,omitempty"`
PersistentKeepalive time.Duration `json:"persistentkeepalive" yaml:"persistentkeepalive"`
Tags map[TagID]struct{} `json:"tags" yaml:"tags"`
}
// FormatBool converts a boolean to a [yes|no] string

26
models/tags.go Normal file
View File

@@ -0,0 +1,26 @@
package models
import "time"
type TagID string
func (id TagID) String() string {
return string(id)
}
type Tag struct {
ID TagID `json:"id"`
CreatedBy string `json:"created_by"`
CreatedAt time.Time `json:"created_at"`
}
type TagListResp struct {
Tag
UsedByCnt int `json:"used_by_count"`
TaggedHosts []Host `json:"tagged_hosts"`
}
type UpdateTagReq struct {
Tag
TaggedHosts []Host `json:"tagged_hosts"`
}