145 lines
4.4 KiB
Go
145 lines
4.4 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"git.gobha.me/xcaliber/chat-switchboard/database"
|
|
)
|
|
|
|
// ── Workflow Assignment Handler ─────────────
|
|
// Manages the assignment queue for human review stages.
|
|
|
|
type WorkflowAssignmentHandler struct{}
|
|
|
|
func NewWorkflowAssignmentHandler() *WorkflowAssignmentHandler {
|
|
return &WorkflowAssignmentHandler{}
|
|
}
|
|
|
|
type assignmentRow struct {
|
|
ID string `json:"id"`
|
|
ChannelID string `json:"channel_id"`
|
|
Stage int `json:"stage"`
|
|
TeamID string `json:"team_id"`
|
|
AssignedTo *string `json:"assigned_to"`
|
|
Status string `json:"status"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
ClaimedAt *time.Time `json:"claimed_at"`
|
|
CompletedAt *time.Time `json:"completed_at"`
|
|
}
|
|
|
|
// 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")
|
|
|
|
rows, err := database.DB.QueryContext(c.Request.Context(), database.Q(`
|
|
SELECT id, channel_id, stage, team_id, assigned_to, status,
|
|
created_at, claimed_at, completed_at
|
|
FROM workflow_assignments
|
|
WHERE team_id = $1 AND status = $2
|
|
ORDER BY created_at ASC
|
|
`), teamID, status)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list assignments"})
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
var result []assignmentRow
|
|
for rows.Next() {
|
|
var a assignmentRow
|
|
if err := rows.Scan(&a.ID, &a.ChannelID, &a.Stage, &a.TeamID,
|
|
&a.AssignedTo, &a.Status, &a.CreatedAt, &a.ClaimedAt, &a.CompletedAt); err != nil {
|
|
continue
|
|
}
|
|
result = append(result, a)
|
|
}
|
|
if result == nil {
|
|
result = []assignmentRow{}
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": result})
|
|
}
|
|
|
|
// ListForUser returns assignments claimed by the current user.
|
|
// GET /api/v1/workflow-assignments/mine
|
|
func (h *WorkflowAssignmentHandler) ListMine(c *gin.Context) {
|
|
userID := c.GetString("user_id")
|
|
rows, err := database.DB.QueryContext(c.Request.Context(), database.Q(`
|
|
SELECT id, channel_id, stage, team_id, assigned_to, status,
|
|
created_at, claimed_at, completed_at
|
|
FROM workflow_assignments
|
|
WHERE assigned_to = $1 AND status = 'claimed'
|
|
ORDER BY claimed_at DESC
|
|
`), userID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list assignments"})
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
var result []assignmentRow
|
|
for rows.Next() {
|
|
var a assignmentRow
|
|
if err := rows.Scan(&a.ID, &a.ChannelID, &a.Stage, &a.TeamID,
|
|
&a.AssignedTo, &a.Status, &a.CreatedAt, &a.ClaimedAt, &a.CompletedAt); err != nil {
|
|
continue
|
|
}
|
|
result = append(result, a)
|
|
}
|
|
if result == nil {
|
|
result = []assignmentRow{}
|
|
}
|
|
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()
|
|
|
|
res, err := database.DB.ExecContext(c.Request.Context(), database.Q(`
|
|
UPDATE workflow_assignments
|
|
SET assigned_to = $1, status = 'claimed', claimed_at = $2
|
|
WHERE id = $3 AND status = 'unassigned'
|
|
`), userID, now, assignmentID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to claim assignment"})
|
|
return
|
|
}
|
|
n, _ := res.RowsAffected()
|
|
if n == 0 {
|
|
c.JSON(http.StatusConflict, gin.H{"error": "assignment already claimed or not found"})
|
|
return
|
|
}
|
|
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")
|
|
now := time.Now().UTC()
|
|
|
|
res, err := database.DB.ExecContext(c.Request.Context(), database.Q(`
|
|
UPDATE workflow_assignments
|
|
SET status = 'completed', completed_at = $1
|
|
WHERE id = $2 AND status = 'claimed'
|
|
`), now, assignmentID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to complete assignment"})
|
|
return
|
|
}
|
|
n, _ := res.RowsAffected()
|
|
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})
|
|
}
|