Feat v0.3.2 workflow engine (#16)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #16.
This commit is contained in:
172
server/handlers/workflow_assignment_handlers.go
Normal file
172
server/handlers/workflow_assignment_handlers.go
Normal file
@@ -0,0 +1,172 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"switchboard-core/models"
|
||||
"switchboard-core/store"
|
||||
"switchboard-core/workflow"
|
||||
)
|
||||
|
||||
// ── Assignment Handlers ─────────────────────
|
||||
|
||||
// WorkflowAssignmentHandler manages workflow assignment HTTP endpoints.
|
||||
type WorkflowAssignmentHandler struct {
|
||||
engine *workflow.Engine
|
||||
stores store.Stores
|
||||
}
|
||||
|
||||
// NewWorkflowAssignmentHandler creates an assignment handler.
|
||||
func NewWorkflowAssignmentHandler(engine *workflow.Engine, stores store.Stores) *WorkflowAssignmentHandler {
|
||||
return &WorkflowAssignmentHandler{engine: engine, stores: stores}
|
||||
}
|
||||
|
||||
// Claim claims an assignment for the current user.
|
||||
// POST /api/v1/assignments/:id/claim
|
||||
func (h *WorkflowAssignmentHandler) Claim(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
assignmentID := c.Param("id")
|
||||
|
||||
if err := h.stores.Workflows.ClaimAssignment(c.Request.Context(), assignmentID, userID); err != nil {
|
||||
c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"claimed": true})
|
||||
}
|
||||
|
||||
// Unclaim releases a claimed assignment back to the queue.
|
||||
// POST /api/v1/assignments/:id/unclaim
|
||||
func (h *WorkflowAssignmentHandler) Unclaim(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
assignmentID := c.Param("id")
|
||||
|
||||
// Verify current user is the claimer
|
||||
userAssignments, err := h.stores.Workflows.ListAssignmentsByUser(c.Request.Context(), userID, models.AssignmentStatusClaimed)
|
||||
if err == nil {
|
||||
found := false
|
||||
for _, a := range userAssignments {
|
||||
if a.ID == assignmentID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "assignment not claimed by you"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.stores.Workflows.UnclaimAssignment(c.Request.Context(), assignmentID); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"unclaimed": true})
|
||||
}
|
||||
|
||||
// Complete marks a claimed assignment as completed.
|
||||
// POST /api/v1/assignments/:id/complete
|
||||
func (h *WorkflowAssignmentHandler) Complete(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
assignmentID := c.Param("id")
|
||||
|
||||
var body struct {
|
||||
ReviewData json.RawMessage `json:"review_data"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&body); err != nil && err.Error() != "EOF" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
// Verify current user is the claimer
|
||||
userAssignments, err := h.stores.Workflows.ListAssignmentsByUser(c.Request.Context(), userID, models.AssignmentStatusClaimed)
|
||||
if err == nil {
|
||||
found := false
|
||||
for _, a := range userAssignments {
|
||||
if a.ID == assignmentID {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "assignment not claimed by you"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.stores.Workflows.CompleteAssignment(c.Request.Context(), assignmentID, body.ReviewData); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// If the stage has auto_transition, advance the instance.
|
||||
// Look up the assignment's instance to get context.
|
||||
userAssignments, _ = h.stores.Workflows.ListAssignmentsByUser(c.Request.Context(), userID, models.AssignmentStatusCompleted)
|
||||
for _, a := range userAssignments {
|
||||
if a.ID == assignmentID {
|
||||
// Try to advance — engine will handle validation
|
||||
h.engine.Advance(c.Request.Context(), a.InstanceID, body.ReviewData, userID)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"completed": true})
|
||||
}
|
||||
|
||||
// Cancel cancels an open assignment.
|
||||
// POST /api/v1/assignments/:id/cancel
|
||||
func (h *WorkflowAssignmentHandler) Cancel(c *gin.Context) {
|
||||
assignmentID := c.Param("id")
|
||||
|
||||
if err := h.stores.Workflows.CancelAssignment(c.Request.Context(), assignmentID); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"cancelled": true})
|
||||
}
|
||||
|
||||
// ListByTeam returns assignments for a team.
|
||||
// GET /api/v1/teams/:teamId/assignments?status=
|
||||
func (h *WorkflowAssignmentHandler) ListByTeam(c *gin.Context) {
|
||||
teamID := c.Param("teamId")
|
||||
status := c.Query("status")
|
||||
|
||||
result, err := h.stores.Workflows.ListAssignmentsByTeam(c.Request.Context(), teamID, status)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
c.JSON(http.StatusOK, gin.H{"data": []struct{}{}})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list assignments"})
|
||||
return
|
||||
}
|
||||
if result == nil {
|
||||
c.JSON(http.StatusOK, gin.H{"data": []struct{}{}})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": result})
|
||||
}
|
||||
|
||||
// ListMine returns assignments claimed by or assigned to the current user.
|
||||
// GET /api/v1/assignments/mine?status=
|
||||
func (h *WorkflowAssignmentHandler) ListMine(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
status := c.Query("status")
|
||||
|
||||
result, err := h.stores.Workflows.ListAssignmentsByUser(c.Request.Context(), userID, status)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list assignments"})
|
||||
return
|
||||
}
|
||||
if result == nil {
|
||||
c.JSON(http.StatusOK, gin.H{"data": []struct{}{}})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"data": result})
|
||||
}
|
||||
Reference in New Issue
Block a user