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) }