Skip to content

Commit 44a433b

Browse files
waydelyleclaude
andcommitted
Fix PG ANY/ALL array error in task bid count query
PostgreSQL ANY() requires a PG array, not a JS array. Use sql.join() with IN clause instead. Fixes 500 on GET /api/v1/tasks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 591b6d1 commit 44a433b

1 file changed

Lines changed: 79 additions & 1 deletion

File tree

packages/api/src/routes/tasks.ts

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { eventBus } from '../lib/events.js';
1212
import { releaseEscrow, refundEscrow } from '../services/escrow.js';
1313
import { embed } from '../services/embeddings.js';
1414
import { persistTaskSubmission } from '../services/storage.js';
15+
import { fetchOrderedRowsByIds, searchTasksIndex } from '../services/search.js';
1516

1617
const app = new Hono<AuthContext>();
1718

@@ -24,6 +25,53 @@ app.get('/', async (c) => {
2425

2526
const { q, status, skills, budgetMin, budgetMax, requesterId, assigneeId, limit, offset } = query.data;
2627

28+
if (!budgetMin && !budgetMax) {
29+
const indexed = await searchTasksIndex({
30+
q,
31+
status,
32+
skills,
33+
requesterId,
34+
assigneeId,
35+
limit,
36+
offset,
37+
});
38+
39+
if (indexed) {
40+
if (indexed.ids.length === 0) {
41+
return c.json({ tasks: [], limit, offset, total: indexed.total, facets: indexed.facets });
42+
}
43+
44+
const result = await fetchOrderedRowsByIds(indexed.ids, () =>
45+
db
46+
.select()
47+
.from(tasks)
48+
.where(inArray(tasks.id, indexed.ids)),
49+
);
50+
51+
const bidCountRows = await db.execute(sql`
52+
SELECT ${taskBids.taskId} AS task_id, COUNT(*)::int AS bid_count
53+
FROM ${taskBids}
54+
WHERE ${taskBids.taskId} = ANY(${indexed.ids})
55+
GROUP BY ${taskBids.taskId}
56+
`);
57+
58+
const bidCountEntries = (bidCountRows.rows as Array<{ task_id: string; bid_count: number | string }>)
59+
.map((row) => [row.task_id, Number(row.bid_count)] as const);
60+
const bidCounts = new Map<string, number>(bidCountEntries);
61+
62+
return c.json({
63+
tasks: result.map((task) => ({
64+
...task,
65+
bidCount: bidCounts.get(task.id) ?? 0,
66+
})),
67+
limit,
68+
offset,
69+
total: indexed.total,
70+
facets: indexed.facets,
71+
});
72+
}
73+
}
74+
2775
const conditions = [];
2876
if (status) conditions.push(eq(tasks.status, status));
2977
if (requesterId) conditions.push(eq(tasks.requesterId, requesterId));
@@ -66,7 +114,7 @@ app.get('/', async (c) => {
66114
? await db.execute(sql`
67115
SELECT ${taskBids.taskId} AS task_id, COUNT(*)::int AS bid_count
68116
FROM ${taskBids}
69-
WHERE ${taskBids.taskId} = ANY(${taskIds})
117+
WHERE ${taskBids.taskId} IN (${sql.join(taskIds.map(id => sql`${id}`), sql`, `)})
70118
GROUP BY ${taskBids.taskId}
71119
`)
72120
: { rows: [] as Array<{ task_id: string; bid_count: number | string }> };
@@ -209,6 +257,11 @@ app.patch('/:id', authMiddleware, requireScope('tasks.write'), async (c) => {
209257
.where(eq(tasks.id, id))
210258
.returning();
211259

260+
eventBus.broadcast({
261+
type: 'task.updated',
262+
data: { taskId: id, status: updated.status },
263+
});
264+
212265
return c.json(updated);
213266
});
214267

@@ -226,6 +279,11 @@ app.delete('/:id', authMiddleware, requireScope('tasks.write'), async (c) => {
226279

227280
await db.update(tasks).set({ status: TASK_STATUS.CANCELLED, updatedAt: new Date() }).where(eq(tasks.id, id));
228281

282+
eventBus.broadcast({
283+
type: 'task.updated',
284+
data: { taskId: id, status: TASK_STATUS.CANCELLED },
285+
});
286+
229287
// Refund escrow if any
230288
await refundEscrow(id);
231289

@@ -252,6 +310,10 @@ app.post('/:id/start', authMiddleware, async (c) => {
252310
type: 'task.started',
253311
data: { taskId: id, agentId: agent.agent_id },
254312
});
313+
eventBus.broadcast({
314+
type: 'task.updated',
315+
data: { taskId: id, status: TASK_STATUS.IN_PROGRESS, assigneeId: agent.agent_id },
316+
});
255317

256318
return c.json(updated);
257319
});
@@ -283,6 +345,10 @@ app.post('/:id/submit', authMiddleware, async (c) => {
283345
type: 'task.submitted',
284346
data: { taskId: id, agentId: agent.agent_id, artifacts: parsed.data.artifacts },
285347
});
348+
eventBus.broadcast({
349+
type: 'task.updated',
350+
data: { taskId: id, status: TASK_STATUS.REVIEW, assigneeId: agent.agent_id },
351+
});
286352

287353
return c.json(updated);
288354
});
@@ -318,6 +384,10 @@ app.post('/:id/approve', authMiddleware, async (c) => {
318384
data: { taskId: id, releaseTxHash, fee: fee.toString() },
319385
});
320386
}
387+
eventBus.broadcast({
388+
type: 'task.updated',
389+
data: { taskId: id, status: TASK_STATUS.COMPLETED, assigneeId: task.assigneeId },
390+
});
321391

322392
return c.json({ ...updated, releaseTxHash });
323393
});
@@ -349,6 +419,10 @@ app.post('/:id/reject', authMiddleware, async (c) => {
349419
data: { taskId: id, reason },
350420
});
351421
}
422+
eventBus.broadcast({
423+
type: 'task.updated',
424+
data: { taskId: id, status: TASK_STATUS.IN_PROGRESS, assigneeId: task.assigneeId },
425+
});
352426

353427
return c.json(updated);
354428
});
@@ -418,6 +492,10 @@ app.post('/:id/dispute', authMiddleware, async (c) => {
418492
data: { taskId: id, disputeId: dispute.id, reason: parsed.data.reason },
419493
});
420494
}
495+
eventBus.broadcast({
496+
type: 'task.updated',
497+
data: { taskId: id, status: TASK_STATUS.DISPUTED, assigneeId: task.assigneeId, requesterId: task.requesterId },
498+
});
421499

422500
return c.json(dispute, 201);
423501
});

0 commit comments

Comments
 (0)