File size: 3,806 Bytes
30a3fac
 
 
 
 
 
 
 
8d0f3eb
 
 
 
 
 
 
 
 
 
 
 
30a3fac
 
 
 
 
 
 
 
 
 
 
8d0f3eb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30a3fac
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import { Router } from 'express';
import { supabase } from '../supabase.js';
import { STATUSES, statusToInt, intToStatus } from '../status.js';

// Lightweight "flow" API for agents. Mounted at /flow/tickets.
const router = Router();
const TABLE = 'ztickets';

// DB stores images as text (JSON array of URLs); expose as an array.
function parseImages(v) {
  if (!v) return [];
  if (Array.isArray(v)) return v;
  try {
    const a = JSON.parse(v);
    return Array.isArray(a) ? a : [];
  } catch {
    return String(v).split(/[\n,]+/).map((s) => s.trim()).filter(Boolean);
  }
}

// GET /flow/tickets -> [{ id, title, status }]
router.get('/', async (req, res) => {
  const { data, error } = await supabase
    .from(TABLE)
    .select('id, title, status')
    .order('sort', { ascending: true })
    .order('id', { ascending: true });
  if (error) return res.status(500).json({ error: error.message });
  res.json(data.map((t) => ({ id: t.id, title: t.title, status: intToStatus(t.status) })));
});

// GET /flow/tickets/queue?project_name=<name>
// Returns the next queued ticket for a project: status = 'queued', lowest sort first.
// Defined before '/:id' so the literal path is not treated as an id.
router.get('/queue', async (req, res) => {
  const projectName = req.query.project_name;
  if (!projectName) return res.status(400).json({ error: 'project_name is required' });

  const { data: project, error: projErr } = await supabase
    .from('zprojects')
    .select('id, name')
    .eq('name', projectName)
    .maybeSingle();
  if (projErr) return res.status(500).json({ error: projErr.message });
  if (!project) return res.status(404).json({ error: `project not found: ${projectName}` });

  const { data, error } = await supabase
    .from(TABLE)
    .select('*')
    .eq('projectid', project.id)
    .eq('status', statusToInt('queued'))
    .order('sort', { ascending: true })
    .order('id', { ascending: true })
    .limit(1)
    .maybeSingle();
  if (error) return res.status(500).json({ error: error.message });
  if (!data) return res.status(404).json({ error: 'no queued ticket found' });
  res.json({
    ...data,
    status: intToStatus(data.status),
    images: parseImages(data.images),
    project_name: project.name
  });
});

// GET /flow/tickets/:id -> { id, title, detail, status, message }
router.get('/:id', async (req, res) => {
  const { data, error } = await supabase
    .from(TABLE)
    .select('id, title, detail, status, message')
    .eq('id', req.params.id)
    .single();
  if (error) return res.status(404).json({ error: error.message });
  res.json({
    id: data.id,
    title: data.title,
    detail: data.detail,
    status: intToStatus(data.status),
    message: data.message
  });
});

// PUT /flow/tickets  body: { id, status?, message?, detail? } -> updated ticket
router.put('/', async (req, res) => {
  const { id, status, message, detail } = req.body;
  if (id === undefined || id === null || id === '') {
    return res.status(400).json({ error: 'id is required' });
  }

  const payload = {};
  if (status !== undefined) {
    if (!STATUSES.includes(status)) {
      return res.status(400).json({ error: `status must be one of ${STATUSES.join(', ')}` });
    }
    payload.status = statusToInt(status);
  }
  if (message !== undefined) payload.message = message;
  if (detail !== undefined) payload.detail = detail;

  if (Object.keys(payload).length === 0) {
    return res.status(400).json({ error: 'nothing to update (provide status, message or detail)' });
  }

  const { data, error } = await supabase
    .from(TABLE)
    .update(payload)
    .eq('id', id)
    .select()
    .single();
  if (error) return res.status(500).json({ error: error.message });
  res.json({ ...data, status: intToStatus(data.status) });
});

export default router;