mirror of
https://github.com/gravitl/netmaker.git
synced 2025-10-10 03:00:46 +08:00
Tag CRUD APIs
This commit is contained in:
@@ -34,6 +34,7 @@ var HttpHandlers = []interface{}{
|
||||
loggerHandlers,
|
||||
hostHandlers,
|
||||
enrollmentKeyHandlers,
|
||||
tagHandlers,
|
||||
legacyHandlers,
|
||||
}
|
||||
|
||||
|
80
controllers/tags.go
Normal file
80
controllers/tags.go
Normal 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")
|
||||
}
|
@@ -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 {
|
||||
|
@@ -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
104
logic/tags.go
Normal 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
|
||||
})
|
||||
}
|
@@ -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
26
models/tags.go
Normal 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"`
|
||||
}
|
Reference in New Issue
Block a user