This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/handlers/workflow_assignments.go
2026-03-19 18:50:27 +00:00

124 lines
3.7 KiB
Go

package handlers
// workflow_assignments.go — Assignment queue for human review stages.
//
// v0.29.0: Raw SQL replaced with WorkflowStore + ChannelStore methods.
import (
"encoding/json"
"net/http"
"time"
"github.com/gin-gonic/gin"
"chat-switchboard/events"
"chat-switchboard/notifications"
"chat-switchboard/store"
)
// ── Workflow Assignment Handler ─────────────
type WorkflowAssignmentHandler struct {
stores store.Stores
hub *events.Hub
}
func NewWorkflowAssignmentHandler(stores store.Stores, hub ...*events.Hub) *WorkflowAssignmentHandler {
h := &WorkflowAssignmentHandler{stores: stores}
if len(hub) > 0 {
h.hub = hub[0]
}
return h
}
// ListForTeam returns unassigned + claimed assignments for a team.
// GET /api/v1/teams/:teamId/assignments
func (h *WorkflowAssignmentHandler) ListForTeam(c *gin.Context) {
teamID := c.Param("teamId")
status := c.DefaultQuery("status", "unassigned")
result, err := h.stores.Workflows.ListAssignmentsForTeam(c.Request.Context(), teamID, status)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list assignments"})
return
}
c.JSON(http.StatusOK, gin.H{"data": result})
}
// ListMine returns assignments claimed by the current user plus
// unassigned assignments for teams the user belongs to.
// GET /api/v1/workflow-assignments/mine
func (h *WorkflowAssignmentHandler) ListMine(c *gin.Context) {
userID := c.GetString("user_id")
result, err := h.stores.Workflows.ListAssignmentsMine(c.Request.Context(), userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list assignments"})
return
}
c.JSON(http.StatusOK, gin.H{"data": result})
}
// Claim assigns a workflow to the current user via optimistic lock.
// POST /api/v1/workflow-assignments/:id/claim
func (h *WorkflowAssignmentHandler) Claim(c *gin.Context) {
assignmentID := c.Param("id")
userID := c.GetString("user_id")
now := time.Now().UTC()
n, err := h.stores.Workflows.ClaimAssignment(c.Request.Context(), assignmentID, userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to claim assignment"})
return
}
if n == 0 {
c.JSON(http.StatusConflict, gin.H{"error": "assignment already claimed or not found"})
return
}
// Look up channel for WS delivery + notification
channelID, _ := h.stores.Workflows.GetAssignmentChannelID(c.Request.Context(), assignmentID)
// Emit workflow.claimed WS event to all user participants in the channel
if h.hub != nil && channelID != "" {
payload, _ := json.Marshal(map[string]any{
"assignment_id": assignmentID,
"claimed_by": userID,
"channel_id": channelID,
})
evt := events.Event{
Label: "workflow.claimed",
Payload: payload,
Ts: now.UnixMilli(),
}
pids, _ := h.stores.Channels.ListUserParticipantIDs(c.Request.Context(), channelID, "")
for _, uid := range pids {
h.hub.PublishToUser(uid, evt)
}
}
// Persist notification for bell/inbox
if svc := notifications.Default(); svc != nil && channelID != "" {
notifications.NotifyWorkflowClaimed(svc, userID, assignmentID, channelID)
}
c.JSON(http.StatusOK, gin.H{"claimed": true, "assignment_id": assignmentID})
}
// Complete marks an assignment as completed.
// POST /api/v1/workflow-assignments/:id/complete
func (h *WorkflowAssignmentHandler) Complete(c *gin.Context) {
assignmentID := c.Param("id")
n, err := h.stores.Workflows.CompleteAssignment(c.Request.Context(), assignmentID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to complete assignment"})
return
}
if n == 0 {
c.JSON(http.StatusConflict, gin.H{"error": "assignment not in claimed state"})
return
}
c.JSON(http.StatusOK, gin.H{"completed": true, "assignment_id": assignmentID})
}