75f3dd141c
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CI and Release / lint-frontend (push) Has been cancelled
CI and Release / dockerfile-scan (push) Has been cancelled
CI and Release / test-frontend (push) Has been cancelled
CI and Release / lint-verification-agent (push) Has been cancelled
CI and Release / test-verification-agent (push) Has been cancelled
CI and Release / e2e-verification-agent (push) Has been cancelled
CI and Release / test-backend (push) Has been cancelled
CI and Release / build-dev-image (push) Has been cancelled
CI and Release / push-dev-image (push) Has been cancelled
CI and Release / build-image (push) Has been cancelled
CI and Release / build-verification-image (push) Has been cancelled
CI and Release / determine-version (push) Has been cancelled
CI and Release / push-image (push) Has been cancelled
CI and Release / push-verification-image (12) (push) Has been cancelled
CI and Release / lint-backend (push) Has been cancelled
CI and Release / push-verification-image (17) (push) Has been cancelled
CI and Release / push-verification-image (18) (push) Has been cancelled
CI and Release / release (push) Has been cancelled
CI and Release / publish-helm-chart (push) Has been cancelled
CI and Release / push-verification-image (13) (push) Has been cancelled
CI and Release / push-verification-image (14) (push) Has been cancelled
CI and Release / push-verification-image (15) (push) Has been cancelled
CI and Release / push-verification-image (16) (push) Has been cancelled
186 lines
5.6 KiB
Go
186 lines
5.6 KiB
Go
package verification_agents
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
|
|
users_enums "databasus-backend/internal/features/users/enums"
|
|
users_middleware "databasus-backend/internal/features/users/middleware"
|
|
)
|
|
|
|
type AgentController struct {
|
|
agentService *AgentService
|
|
}
|
|
|
|
func (c *AgentController) RegisterRoutes(router *gin.RouterGroup) {
|
|
publicAuth := router.Group("/verification/agents")
|
|
publicAuth.GET("/availability", c.GetAvailability)
|
|
|
|
adminOnly := router.Group("/verification/agents")
|
|
adminOnly.Use(users_middleware.RequireRole(users_enums.UserRoleAdmin))
|
|
|
|
adminOnly.POST("", c.CreateAgent)
|
|
adminOnly.GET("", c.ListAgents)
|
|
adminOnly.POST("/:id/rotate-token", c.RotateToken)
|
|
adminOnly.DELETE("/:id", c.DeleteAgent)
|
|
}
|
|
|
|
// GetAvailability
|
|
// @Summary Get verification agent availability
|
|
// @Description Returns the count of live verification agents. Available to any authenticated user so the UI can decide whether to render verification-related actions. No agent identifiers or tokens are exposed.
|
|
// @Tags verification-agents
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Success 200 {object} AvailabilityResponse
|
|
// @Failure 401 {object} map[string]string
|
|
// @Router /verification/agents/availability [get]
|
|
func (c *AgentController) GetAvailability(ctx *gin.Context) {
|
|
count, err := c.agentService.CountLiveAgents()
|
|
if err != nil {
|
|
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, AvailabilityResponse{
|
|
Count: count,
|
|
HasAgents: count > 0,
|
|
})
|
|
}
|
|
|
|
// CreateAgent
|
|
// @Summary Create a verification agent
|
|
// @Description Create a new verification-worker agent (ADMIN only). The raw token is returned exactly once.
|
|
// @Tags verification-agents
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param request body CreateAgentRequest true "Agent name"
|
|
// @Success 201 {object} CreatedAgentResponse
|
|
// @Failure 400 {object} map[string]string
|
|
// @Failure 401 {object} map[string]string
|
|
// @Failure 403 {object} map[string]string
|
|
// @Router /verification/agents [post]
|
|
func (c *AgentController) CreateAgent(ctx *gin.Context) {
|
|
user, ok := users_middleware.GetUserFromContext(ctx)
|
|
if !ok {
|
|
ctx.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
|
|
return
|
|
}
|
|
|
|
var request CreateAgentRequest
|
|
if err := ctx.ShouldBindJSON(&request); err != nil {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
response, err := c.agentService.CreateAgent(user, &request)
|
|
if err != nil {
|
|
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
ctx.JSON(http.StatusCreated, response)
|
|
}
|
|
|
|
// ListAgents
|
|
// @Summary List verification agents
|
|
// @Description List all live verification agents (ADMIN only).
|
|
// @Tags verification-agents
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Success 200 {array} Agent
|
|
// @Failure 401 {object} map[string]string
|
|
// @Failure 403 {object} map[string]string
|
|
// @Router /verification/agents [get]
|
|
func (c *AgentController) ListAgents(ctx *gin.Context) {
|
|
agents, err := c.agentService.ListAgents()
|
|
if err != nil {
|
|
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, agents)
|
|
}
|
|
|
|
// RotateToken
|
|
// @Summary Rotate a verification agent's token
|
|
// @Description Mint a new token for the agent and invalidate the old one (ADMIN only). The raw token is returned exactly once.
|
|
// @Tags verification-agents
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param id path string true "Agent ID"
|
|
// @Success 200 {object} RotateTokenResponse
|
|
// @Failure 400 {object} map[string]string
|
|
// @Failure 401 {object} map[string]string
|
|
// @Failure 403 {object} map[string]string
|
|
// @Failure 404 {object} map[string]string
|
|
// @Router /verification/agents/{id}/rotate-token [post]
|
|
func (c *AgentController) RotateToken(ctx *gin.Context) {
|
|
user, ok := users_middleware.GetUserFromContext(ctx)
|
|
if !ok {
|
|
ctx.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
|
|
return
|
|
}
|
|
|
|
agentID, err := uuid.Parse(ctx.Param("id"))
|
|
if err != nil {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
|
return
|
|
}
|
|
|
|
token, err := c.agentService.RotateToken(user, agentID)
|
|
if err != nil {
|
|
if errors.Is(err, ErrAgentNotFound) {
|
|
ctx.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
ctx.JSON(http.StatusOK, RotateTokenResponse{Token: token})
|
|
}
|
|
|
|
// DeleteAgent
|
|
// @Summary Delete a verification agent
|
|
// @Description Soft-delete a verification agent (ADMIN only). The agent's token is preserved so a still-running worker receives a 404 on its next heartbeat rather than a 401.
|
|
// @Tags verification-agents
|
|
// @Produce json
|
|
// @Security BearerAuth
|
|
// @Param id path string true "Agent ID"
|
|
// @Success 204
|
|
// @Failure 400 {object} map[string]string
|
|
// @Failure 401 {object} map[string]string
|
|
// @Failure 403 {object} map[string]string
|
|
// @Failure 404 {object} map[string]string
|
|
// @Router /verification/agents/{id} [delete]
|
|
func (c *AgentController) DeleteAgent(ctx *gin.Context) {
|
|
user, ok := users_middleware.GetUserFromContext(ctx)
|
|
if !ok {
|
|
ctx.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
|
|
return
|
|
}
|
|
|
|
agentID, err := uuid.Parse(ctx.Param("id"))
|
|
if err != nil {
|
|
ctx.JSON(http.StatusBadRequest, gin.H{"error": "invalid agent ID"})
|
|
return
|
|
}
|
|
|
|
if err := c.agentService.DeleteAgent(user, agentID); err != nil {
|
|
if errors.Is(err, ErrAgentNotFound) {
|
|
ctx.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
ctx.Status(http.StatusNoContent)
|
|
}
|