mirror of
https://github.com/gravitl/netmaker.git
synced 2025-10-10 11:12:25 +08:00
Tag CRUD APIs
This commit is contained in:
@@ -34,6 +34,7 @@ var HttpHandlers = []interface{}{
|
|||||||
loggerHandlers,
|
loggerHandlers,
|
||||||
hostHandlers,
|
hostHandlers,
|
||||||
enrollmentKeyHandlers,
|
enrollmentKeyHandlers,
|
||||||
|
tagHandlers,
|
||||||
legacyHandlers,
|
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"
|
PENDING_USERS_TABLE_NAME = "pending_users"
|
||||||
// USER_INVITES - table for user invites
|
// USER_INVITES - table for user invites
|
||||||
USER_INVITES_TABLE_NAME = "user_invites"
|
USER_INVITES_TABLE_NAME = "user_invites"
|
||||||
|
// TAG_TABLE_NAME - table for tags
|
||||||
|
TAG_TABLE_NAME = "tags"
|
||||||
// == ERROR CONSTS ==
|
// == ERROR CONSTS ==
|
||||||
// NO_RECORD - no singular result found
|
// NO_RECORD - no singular result found
|
||||||
NO_RECORD = "no result found"
|
NO_RECORD = "no result found"
|
||||||
@@ -152,6 +154,7 @@ func createTables() {
|
|||||||
CreateTable(PENDING_USERS_TABLE_NAME)
|
CreateTable(PENDING_USERS_TABLE_NAME)
|
||||||
CreateTable(USER_PERMISSIONS_TABLE_NAME)
|
CreateTable(USER_PERMISSIONS_TABLE_NAME)
|
||||||
CreateTable(USER_INVITES_TABLE_NAME)
|
CreateTable(USER_INVITES_TABLE_NAME)
|
||||||
|
CreateTable(TAG_TABLE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateTable(tableName string) error {
|
func CreateTable(tableName string) error {
|
||||||
|
@@ -572,3 +572,35 @@ func SortApiHosts(unsortedHosts []models.ApiHost) {
|
|||||||
return unsortedHosts[i].ID < unsortedHosts[j].ID
|
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"`
|
NatType string `json:"nat_type,omitempty" yaml:"nat_type,omitempty"`
|
||||||
TurnEndpoint *netip.AddrPort `json:"turn_endpoint,omitempty" yaml:"turn_endpoint,omitempty"`
|
TurnEndpoint *netip.AddrPort `json:"turn_endpoint,omitempty" yaml:"turn_endpoint,omitempty"`
|
||||||
PersistentKeepalive time.Duration `json:"persistentkeepalive" yaml:"persistentkeepalive"`
|
PersistentKeepalive time.Duration `json:"persistentkeepalive" yaml:"persistentkeepalive"`
|
||||||
|
Tags map[TagID]struct{} `json:"tags" yaml:"tags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatBool converts a boolean to a [yes|no] string
|
// 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