|
| 1 | +import type Redis from 'ioredis' |
| 2 | + |
| 3 | +import type { HonoEnv } from '../types/hono' |
| 4 | + |
| 5 | +import { useLogger } from '@guiiai/logg' |
| 6 | +import { Hono } from 'hono' |
| 7 | +import { object, optional, safeParse, string } from 'valibot' |
| 8 | + |
| 9 | +import { createBadRequestError } from '../utils/error' |
| 10 | + |
| 11 | +const logger = useLogger('pour-completed') |
| 12 | + |
| 13 | +const POUR_COMPLETED_CHANNEL = 'robot:pour-completed' |
| 14 | + |
| 15 | +/** |
| 16 | + * Inbound payload for `POST /api/robot/pour-completed`. |
| 17 | + * |
| 18 | + * Posted by main_task_node when an `ExecuteDrinkTask` action returns success |
| 19 | + * (excluding the `__go_home__` cleanup goal). Republished verbatim to the |
| 20 | + * `robot:pour-completed` Redis channel so the BT supervisor can drive the |
| 21 | + * post-pour follow-up branch. |
| 22 | + */ |
| 23 | +const PourCompletedSchema = object({ |
| 24 | + drink_type: string(), |
| 25 | + table_id: optional(string()), |
| 26 | +}) |
| 27 | + |
| 28 | +/** |
| 29 | + * Build the `/api/robot/pour-completed` Hono sub-app. |
| 30 | + * |
| 31 | + * Use when: |
| 32 | + * - Wiring routes in `app.ts`. Mount under `/api/robot/pour-completed`. |
| 33 | + * |
| 34 | + * Expects: |
| 35 | + * - `redis` is the primary connected client; reused for the publish. |
| 36 | + * |
| 37 | + * Returns: |
| 38 | + * - A Hono sub-app exposing `POST /`. |
| 39 | + */ |
| 40 | +export function createPourCompletedRoutes(redis: Redis) { |
| 41 | + return new Hono<HonoEnv>() |
| 42 | + .post('/', async (c) => { |
| 43 | + const raw = await c.req.json().catch(() => null) |
| 44 | + if (raw === null) |
| 45 | + throw createBadRequestError('Body must be JSON', 'INVALID_REQUEST') |
| 46 | + |
| 47 | + const parsed = safeParse(PourCompletedSchema, raw) |
| 48 | + if (!parsed.success) |
| 49 | + throw createBadRequestError('Invalid Request', 'INVALID_REQUEST', parsed.issues) |
| 50 | + |
| 51 | + const payload = JSON.stringify({ |
| 52 | + drink_type: parsed.output.drink_type, |
| 53 | + table_id: parsed.output.table_id ?? '', |
| 54 | + at: new Date().toISOString(), |
| 55 | + }) |
| 56 | + |
| 57 | + redis.publish(POUR_COMPLETED_CHANNEL, payload).catch((err) => { |
| 58 | + logger.withError(err as Error).warn('Failed to publish robot:pour-completed') |
| 59 | + }) |
| 60 | + |
| 61 | + logger.withFields({ ...parsed.output }).log('Pour completed event published') |
| 62 | + |
| 63 | + return c.json({ success: true }, 201) |
| 64 | + }) |
| 65 | +} |
0 commit comments