mirror of
https://github.com/EZ-Api/ez-api.git
synced 2026-01-13 17:47:51 +00:00
Update DeleteMasterKey endpoint to use MapData instead of dto.DeleteResponse and add blank references for model registry DTOs to ensure proper swagger documentation generation.
571 lines
20 KiB
Go
571 lines
20 KiB
Go
package api
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/ez-api/ez-api/internal/model"
|
|
"github.com/ez-api/ez-api/internal/service"
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type AdminHandler struct {
|
|
db *gorm.DB
|
|
logDB *gorm.DB
|
|
masterService *service.MasterService
|
|
syncService *service.SyncService
|
|
statsService *service.StatsService
|
|
logPartitioner *service.LogPartitioner
|
|
}
|
|
|
|
func NewAdminHandler(db *gorm.DB, logDB *gorm.DB, masterService *service.MasterService, syncService *service.SyncService, statsService *service.StatsService, partitioner *service.LogPartitioner) *AdminHandler {
|
|
if logDB == nil {
|
|
logDB = db
|
|
}
|
|
return &AdminHandler{db: db, logDB: logDB, masterService: masterService, syncService: syncService, statsService: statsService, logPartitioner: partitioner}
|
|
}
|
|
|
|
func (h *AdminHandler) logDBConn() *gorm.DB {
|
|
if h == nil || h.logDB == nil {
|
|
return h.db
|
|
}
|
|
return h.logDB
|
|
}
|
|
|
|
func (h *AdminHandler) logBaseQuery() *gorm.DB {
|
|
return logBaseQuery(h.logDBConn(), h.logPartitioner)
|
|
}
|
|
|
|
type CreateMasterRequest struct {
|
|
Name string `json:"name" binding:"required"`
|
|
Group string `json:"group" binding:"required"`
|
|
MaxChildKeys int `json:"max_child_keys"`
|
|
GlobalQPS int `json:"global_qps"`
|
|
}
|
|
|
|
// CreateMaster godoc
|
|
// @Summary Create a new master tenant
|
|
// @Description Create a new master account (tenant)
|
|
// @Tags admin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security AdminAuth
|
|
// @Param master body CreateMasterRequest true "Master Info"
|
|
// @Success 201 {object} ResponseEnvelope{data=MapData}
|
|
// @Failure 400 {object} ResponseEnvelope{data=MapData}
|
|
// @Failure 500 {object} ResponseEnvelope{data=MapData}
|
|
// @Router /admin/masters [post]
|
|
func (h *AdminHandler) CreateMaster(c *gin.Context) {
|
|
var req CreateMasterRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Use defaults if not provided
|
|
if req.MaxChildKeys == 0 {
|
|
req.MaxChildKeys = 5
|
|
}
|
|
if req.GlobalQPS < 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "global_qps must be >= 0"})
|
|
return
|
|
}
|
|
|
|
master, rawMasterKey, err := h.masterService.CreateMaster(req.Name, req.Group, req.MaxChildKeys, req.GlobalQPS)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create master key", "details": err.Error()})
|
|
return
|
|
}
|
|
|
|
if err := h.syncService.SyncMaster(master); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to sync master key", "details": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"id": master.ID,
|
|
"name": master.Name,
|
|
"group": master.Group,
|
|
"master_key": rawMasterKey, // Only show this on creation
|
|
"max_child_keys": master.MaxChildKeys,
|
|
"global_qps": master.GlobalQPS,
|
|
})
|
|
}
|
|
|
|
type MasterView struct {
|
|
ID uint `json:"id"`
|
|
Name string `json:"name"`
|
|
Group string `json:"group"`
|
|
DefaultNamespace string `json:"default_namespace"`
|
|
Namespaces string `json:"namespaces"`
|
|
Epoch int64 `json:"epoch"`
|
|
Status string `json:"status"`
|
|
MaxChildKeys int `json:"max_child_keys"`
|
|
GlobalQPS int `json:"global_qps"`
|
|
CreatedAt int64 `json:"created_at"`
|
|
UpdatedAt int64 `json:"updated_at"`
|
|
Realtime *MasterRealtimeView `json:"realtime,omitempty"`
|
|
}
|
|
|
|
func toMasterView(m model.Master) MasterView {
|
|
return MasterView{
|
|
ID: m.ID,
|
|
Name: m.Name,
|
|
Group: m.Group,
|
|
DefaultNamespace: strings.TrimSpace(m.DefaultNamespace),
|
|
Namespaces: strings.TrimSpace(m.Namespaces),
|
|
Epoch: m.Epoch,
|
|
Status: m.Status,
|
|
MaxChildKeys: m.MaxChildKeys,
|
|
GlobalQPS: m.GlobalQPS,
|
|
CreatedAt: m.CreatedAt.UTC().Unix(),
|
|
UpdatedAt: m.UpdatedAt.UTC().Unix(),
|
|
}
|
|
}
|
|
|
|
// ListMasters godoc
|
|
// @Summary List masters
|
|
// @Description List all master tenants
|
|
// @Tags admin
|
|
// @Produce json
|
|
// @Security AdminAuth
|
|
// @Param page query int false "page (1-based)"
|
|
// @Param limit query int false "limit (default 50, max 200)"
|
|
// @Param search query string false "search by name/group"
|
|
// @Success 200 {object} ResponseEnvelope{data=[]MasterView}
|
|
// @Failure 500 {object} ResponseEnvelope{data=MapData}
|
|
// @Router /admin/masters [get]
|
|
func (h *AdminHandler) ListMasters(c *gin.Context) {
|
|
var masters []model.Master
|
|
q := h.db.Model(&model.Master{}).Order("id desc")
|
|
query := parseListQuery(c)
|
|
q = applyListSearch(q, query.Search, "name", `"group"`)
|
|
q = applyListPagination(q, query)
|
|
if err := q.Find(&masters).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list masters", "details": err.Error()})
|
|
return
|
|
}
|
|
out := make([]MasterView, 0, len(masters))
|
|
for _, m := range masters {
|
|
out = append(out, toMasterView(m))
|
|
}
|
|
c.JSON(http.StatusOK, out)
|
|
}
|
|
|
|
// GetMaster godoc
|
|
// @Summary Get master
|
|
// @Description Get a master tenant by id
|
|
// @Tags admin
|
|
// @Produce json
|
|
// @Security AdminAuth
|
|
// @Param id path int true "Master ID"
|
|
// @Success 200 {object} ResponseEnvelope{data=MasterView}
|
|
// @Failure 400 {object} ResponseEnvelope{data=MapData}
|
|
// @Failure 404 {object} ResponseEnvelope{data=MapData}
|
|
// @Failure 500 {object} ResponseEnvelope{data=MapData}
|
|
// @Router /admin/masters/{id} [get]
|
|
func (h *AdminHandler) GetMaster(c *gin.Context) {
|
|
idRaw := strings.TrimSpace(c.Param("id"))
|
|
idU64, err := strconv.ParseUint(idRaw, 10, 64)
|
|
if err != nil || idU64 == 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid master id"})
|
|
return
|
|
}
|
|
var m model.Master
|
|
if err := h.db.First(&m, uint(idU64)).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "master not found"})
|
|
return
|
|
}
|
|
view := toMasterView(m)
|
|
if h.statsService != nil {
|
|
if stats, err := h.statsService.GetMasterRealtimeSnapshot(c.Request.Context(), m.ID); err == nil {
|
|
if stats.QPSLimit == 0 && m.GlobalQPS > 0 {
|
|
stats.QPSLimit = int64(m.GlobalQPS)
|
|
}
|
|
view.Realtime = toMasterRealtimeView(stats)
|
|
}
|
|
}
|
|
c.JSON(http.StatusOK, view)
|
|
}
|
|
|
|
type UpdateMasterRequest struct {
|
|
Name *string `json:"name,omitempty"`
|
|
Group *string `json:"group,omitempty"`
|
|
MaxChildKeys *int `json:"max_child_keys,omitempty"`
|
|
GlobalQPS *int `json:"global_qps,omitempty"`
|
|
PropagateToKeys bool `json:"propagate_to_keys,omitempty"`
|
|
}
|
|
|
|
// UpdateMaster godoc
|
|
// @Summary Update master
|
|
// @Description Update master fields; optionally propagate group to existing keys
|
|
// @Tags admin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security AdminAuth
|
|
// @Param id path int true "Master ID"
|
|
// @Param request body UpdateMasterRequest true "Update payload"
|
|
// @Success 200 {object} ResponseEnvelope{data=MasterView}
|
|
// @Failure 400 {object} ResponseEnvelope{data=MapData}
|
|
// @Failure 404 {object} ResponseEnvelope{data=MapData}
|
|
// @Failure 500 {object} ResponseEnvelope{data=MapData}
|
|
// @Router /admin/masters/{id} [put]
|
|
func (h *AdminHandler) UpdateMaster(c *gin.Context) {
|
|
idRaw := strings.TrimSpace(c.Param("id"))
|
|
idU64, err := strconv.ParseUint(idRaw, 10, 64)
|
|
if err != nil || idU64 == 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid master id"})
|
|
return
|
|
}
|
|
var req UpdateMasterRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
var m model.Master
|
|
if err := h.db.First(&m, uint(idU64)).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "master not found"})
|
|
return
|
|
}
|
|
|
|
update := make(map[string]any)
|
|
if req.Name != nil {
|
|
update["name"] = strings.TrimSpace(*req.Name)
|
|
}
|
|
if req.Group != nil {
|
|
g := strings.TrimSpace(*req.Group)
|
|
if g != "" {
|
|
update["group"] = g
|
|
}
|
|
}
|
|
if req.MaxChildKeys != nil && *req.MaxChildKeys > 0 {
|
|
update["max_child_keys"] = *req.MaxChildKeys
|
|
}
|
|
if req.GlobalQPS != nil {
|
|
if *req.GlobalQPS < 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "global_qps must be >= 0"})
|
|
return
|
|
}
|
|
update["global_qps"] = *req.GlobalQPS
|
|
}
|
|
if len(update) == 0 && !req.PropagateToKeys {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "no fields to update"})
|
|
return
|
|
}
|
|
|
|
if len(update) > 0 {
|
|
if err := h.db.Model(&m).Updates(update).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update master", "details": err.Error()})
|
|
return
|
|
}
|
|
}
|
|
if err := h.db.First(&m, uint(idU64)).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to reload master", "details": err.Error()})
|
|
return
|
|
}
|
|
|
|
if req.PropagateToKeys && req.Group != nil {
|
|
g := strings.TrimSpace(*req.Group)
|
|
if g != "" {
|
|
if err := h.db.Model(&model.Key{}).Where("master_id = ?", m.ID).Updates(map[string]any{"group": g}).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to propagate group to keys", "details": err.Error()})
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sync master metadata and (if propagated) keys.
|
|
if err := h.syncService.SyncMaster(&m); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to sync master", "details": err.Error()})
|
|
return
|
|
}
|
|
if req.PropagateToKeys {
|
|
var keys []model.Key
|
|
if err := h.db.Where("master_id = ?", m.ID).Find(&keys).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to reload keys for sync", "details": err.Error()})
|
|
return
|
|
}
|
|
for i := range keys {
|
|
if err := h.syncService.SyncKey(&keys[i]); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to sync key", "details": err.Error()})
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, toMasterView(m))
|
|
}
|
|
|
|
type ManageMasterRequest struct {
|
|
Action string `json:"action" binding:"required"` // freeze/unfreeze
|
|
}
|
|
|
|
// ManageMaster godoc
|
|
// @Summary Manage master status
|
|
// @Description Freeze or unfreeze a master tenant
|
|
// @Tags admin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security AdminAuth
|
|
// @Param id path int true "Master ID"
|
|
// @Param request body ManageMasterRequest true "Action"
|
|
// @Success 200 {object} ResponseEnvelope{data=MasterView}
|
|
// @Failure 400 {object} ResponseEnvelope{data=MapData}
|
|
// @Failure 404 {object} ResponseEnvelope{data=MapData}
|
|
// @Failure 500 {object} ResponseEnvelope{data=MapData}
|
|
// @Router /admin/masters/{id}/manage [post]
|
|
func (h *AdminHandler) ManageMaster(c *gin.Context) {
|
|
idRaw := strings.TrimSpace(c.Param("id"))
|
|
idU64, err := strconv.ParseUint(idRaw, 10, 64)
|
|
if err != nil || idU64 == 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid master id"})
|
|
return
|
|
}
|
|
var req ManageMasterRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
action := strings.ToLower(strings.TrimSpace(req.Action))
|
|
var status string
|
|
switch action {
|
|
case "freeze":
|
|
status = "suspended"
|
|
case "unfreeze":
|
|
status = "active"
|
|
default:
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid action"})
|
|
return
|
|
}
|
|
|
|
var m model.Master
|
|
if err := h.db.First(&m, uint(idU64)).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "master not found"})
|
|
return
|
|
}
|
|
|
|
if err := h.db.Model(&m).Update("status", status).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update master status", "details": err.Error()})
|
|
return
|
|
}
|
|
if err := h.db.First(&m, uint(idU64)).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to reload master", "details": err.Error()})
|
|
return
|
|
}
|
|
if err := h.syncService.SyncMaster(&m); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to sync master", "details": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, toMasterView(m))
|
|
}
|
|
|
|
// DeleteMaster godoc
|
|
// @Summary Delete (revoke) master
|
|
// @Description Suspends a master and revokes all existing keys by bumping epoch and syncing to Redis
|
|
// @Tags admin
|
|
// @Produce json
|
|
// @Security AdminAuth
|
|
// @Param id path int true "Master ID"
|
|
// @Success 200 {object} ResponseEnvelope{data=MapData}
|
|
// @Failure 400 {object} ResponseEnvelope{data=MapData}
|
|
// @Failure 404 {object} ResponseEnvelope{data=MapData}
|
|
// @Failure 500 {object} ResponseEnvelope{data=MapData}
|
|
// @Router /admin/masters/{id} [delete]
|
|
func (h *AdminHandler) DeleteMaster(c *gin.Context) {
|
|
idRaw := strings.TrimSpace(c.Param("id"))
|
|
idU64, err := strconv.ParseUint(idRaw, 10, 64)
|
|
if err != nil || idU64 == 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid master id"})
|
|
return
|
|
}
|
|
|
|
if err := h.revokeMasterByID(uint(idU64)); err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "master not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to revoke master", "details": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"status": "revoked"})
|
|
}
|
|
|
|
// IssueChildKeyForMaster godoc
|
|
// @Summary Issue a child key on behalf of a master
|
|
// @Description Issue a new access token (child key) for a specified master. The key still belongs to the master; issuer is recorded as admin for audit.
|
|
// @Tags admin
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security AdminAuth
|
|
// @Param id path int true "Master ID"
|
|
// @Param request body IssueChildKeyRequest true "Key Request"
|
|
// @Success 201 {object} ResponseEnvelope{data=MapData}
|
|
// @Failure 400 {object} ResponseEnvelope{data=MapData}
|
|
// @Failure 403 {object} ResponseEnvelope{data=MapData}
|
|
// @Failure 404 {object} ResponseEnvelope{data=MapData}
|
|
// @Failure 500 {object} ResponseEnvelope{data=MapData}
|
|
// @Router /admin/masters/{id}/keys [post]
|
|
func (h *AdminHandler) IssueChildKeyForMaster(c *gin.Context) {
|
|
idRaw := strings.TrimSpace(c.Param("id"))
|
|
if idRaw == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "master id required"})
|
|
return
|
|
}
|
|
idU64, err := strconv.ParseUint(idRaw, 10, 64)
|
|
if err != nil || idU64 == 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid master id"})
|
|
return
|
|
}
|
|
masterID := uint(idU64)
|
|
|
|
var req IssueChildKeyRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
modelLimits := strings.TrimSpace(req.ModelLimits)
|
|
modelLimitsEnabled := false
|
|
if req.ModelLimitsEnabled != nil {
|
|
modelLimitsEnabled = *req.ModelLimitsEnabled
|
|
} else if modelLimits != "" {
|
|
modelLimitsEnabled = true
|
|
}
|
|
|
|
key, rawChildKey, err := h.masterService.IssueChildKeyAsAdmin(masterID, service.IssueKeyOptions{
|
|
Group: strings.TrimSpace(req.Group),
|
|
Scopes: strings.TrimSpace(req.Scopes),
|
|
ModelLimits: modelLimits,
|
|
ModelLimitsEnabled: modelLimitsEnabled,
|
|
ExpiresAt: req.ExpiresAt,
|
|
AllowIPs: strings.TrimSpace(req.AllowIPs),
|
|
DenyIPs: strings.TrimSpace(req.DenyIPs),
|
|
})
|
|
if err != nil {
|
|
switch {
|
|
case errors.Is(err, service.ErrMasterNotFound):
|
|
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
|
case errors.Is(err, service.ErrMasterNotActive):
|
|
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
|
case errors.Is(err, service.ErrModelLimitForbidden):
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
case errors.Is(err, service.ErrChildKeyGroupForbidden):
|
|
c.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
|
|
case errors.Is(err, service.ErrChildKeyLimitReached):
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
default:
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to issue child key", "details": err.Error()})
|
|
}
|
|
return
|
|
}
|
|
|
|
if err := h.syncService.SyncKey(key); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to sync child key", "details": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, gin.H{
|
|
"id": key.ID,
|
|
"key_secret": rawChildKey,
|
|
"group": key.Group,
|
|
"scopes": key.Scopes,
|
|
"issued_by": key.IssuedBy,
|
|
})
|
|
}
|
|
|
|
// ListKeysForMaster godoc
|
|
// @Summary List child keys for a master
|
|
// @Description List child keys issued under a master (admin view)
|
|
// @Tags admin
|
|
// @Produce json
|
|
// @Security AdminAuth
|
|
// @Param id path int true "Master ID"
|
|
// @Param page query int false "page (1-based)"
|
|
// @Param limit query int false "limit (default 50, max 200)"
|
|
// @Param search query string false "search by group/scopes/namespaces/status"
|
|
// @Success 200 {object} ResponseEnvelope{data=[]TokenView}
|
|
// @Failure 400 {object} ResponseEnvelope{data=MapData}
|
|
// @Failure 500 {object} ResponseEnvelope{data=MapData}
|
|
// @Router /admin/masters/{id}/keys [get]
|
|
func (h *AdminHandler) ListKeysForMaster(c *gin.Context) {
|
|
masterID, ok := parseUintParam(c, "id")
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var keys []model.Key
|
|
q := h.db.Model(&model.Key{}).Where("master_id = ?", masterID).Order("id desc")
|
|
query := parseListQuery(c)
|
|
q = applyListSearch(q, query.Search, `"group"`, "scopes", "default_namespace", "namespaces", "status")
|
|
q = applyListPagination(q, query)
|
|
if err := q.Find(&keys).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list tokens", "details": err.Error()})
|
|
return
|
|
}
|
|
|
|
out := make([]TokenView, 0, len(keys))
|
|
for _, k := range keys {
|
|
out = append(out, toTokenView(k))
|
|
}
|
|
c.JSON(http.StatusOK, out)
|
|
}
|
|
|
|
// DeleteKeyForMaster godoc
|
|
// @Summary Delete child key
|
|
// @Description Revokes and removes a child key under the specified master
|
|
// @Tags admin
|
|
// @Produce json
|
|
// @Security AdminAuth
|
|
// @Param id path int true "Master ID"
|
|
// @Param key_id path int true "Token ID"
|
|
// @Success 200 {object} ResponseEnvelope{data=MapData}
|
|
// @Failure 400 {object} ResponseEnvelope{data=MapData}
|
|
// @Failure 404 {object} ResponseEnvelope{data=MapData}
|
|
// @Failure 500 {object} ResponseEnvelope{data=MapData}
|
|
// @Router /admin/masters/{id}/keys/{key_id} [delete]
|
|
func (h *AdminHandler) DeleteKeyForMaster(c *gin.Context) {
|
|
masterID, ok := parseUintParam(c, "id")
|
|
if !ok {
|
|
return
|
|
}
|
|
keyID, ok := parseUintParam(c, "key_id")
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var k model.Key
|
|
if err := h.db.Where("master_id = ? AND id = ?", masterID, keyID).First(&k).Error; err != nil {
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "token not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load token", "details": err.Error()})
|
|
return
|
|
}
|
|
|
|
if err := h.db.Model(&k).Update("status", "suspended").Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to revoke token", "details": err.Error()})
|
|
return
|
|
}
|
|
if err := h.db.Where("master_id = ? AND id = ?", masterID, keyID).First(&k).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to reload token", "details": err.Error()})
|
|
return
|
|
}
|
|
if err := h.syncService.SyncKey(&k); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to sync token", "details": err.Error()})
|
|
return
|
|
}
|
|
|
|
if err := h.db.Delete(&k).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete token", "details": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"status": "deleted"})
|
|
}
|