Initial commit: SKEEN Derma Experts - Sistema Integral de Gestión Clínica
- Frontend React (SKEEN Brand) con Vite, TypeScript, Tailwind - Frontend Homenest (versión alternativa) - Módulos Odoo 17 custom (citas, pacientes, monedero, pagos, ventas, inventario, whatsapp) - WACRM fork (Next.js 16 + Supabase) - Hermes + Bridge + Skills (Qwen3.6 via Nan Builders) - Scripts de migración y operación - Documentación extensiva en docs/
This commit is contained in:
86
wacrm/src/app/api/flows/[id]/runs/route.ts
Normal file
86
wacrm/src/app/api/flows/[id]/runs/route.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { createClient } from '@/lib/supabase/server'
|
||||
|
||||
/**
|
||||
* GET /api/flows/[id]/runs
|
||||
*
|
||||
* Newest-first list of flow runs for a single flow, with the latest
|
||||
* event timeline embedded for each. Used by the run-history viewer
|
||||
* page (`/flows/[id]/runs`) to give the owner end-to-end visibility
|
||||
* into what the bot did with each customer.
|
||||
*
|
||||
* RLS does the ownership check (flow_runs has a `user_id` policy);
|
||||
* we also gate on the per-account beta flag so the route 404s for
|
||||
* non-beta accounts matching the rest of /api/flows.
|
||||
*
|
||||
* Limited to the 50 most recent runs. Pagination can come later;
|
||||
* the dashboard surface here is for debugging, not heavy querying.
|
||||
*/
|
||||
export async function GET(
|
||||
_request: Request,
|
||||
context: { params: Promise<{ id: string }> },
|
||||
) {
|
||||
const { id } = await context.params
|
||||
|
||||
const supabase = await createClient()
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser()
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Confirm flow exists + caller owns it (RLS does this) before doing
|
||||
// the run query — gives us a clean 404 instead of empty array.
|
||||
const { data: flow } = await supabase
|
||||
.from('flows')
|
||||
.select('id, name')
|
||||
.eq('id', id)
|
||||
.maybeSingle()
|
||||
if (!flow) {
|
||||
return NextResponse.json({ error: 'Not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
// Pull runs + each run's contact name + each run's events. Two
|
||||
// joined selects keep the round-trip count to the runs query + one
|
||||
// per-run events query.
|
||||
const { data: runs, error: runsErr } = await supabase
|
||||
.from('flow_runs')
|
||||
.select(
|
||||
'id, status, current_node_key, started_at, last_advanced_at, ended_at, end_reason, vars, reprompt_count, contact:contacts(id, name, phone)',
|
||||
)
|
||||
.eq('flow_id', id)
|
||||
.order('started_at', { ascending: false })
|
||||
.limit(50)
|
||||
if (runsErr) {
|
||||
return NextResponse.json({ error: runsErr.message }, { status: 500 })
|
||||
}
|
||||
|
||||
const runIds = (runs ?? []).map((r) => (r as { id: string }).id)
|
||||
let events: Array<{
|
||||
flow_run_id: string
|
||||
event_type: string
|
||||
node_key: string | null
|
||||
payload: Record<string, unknown>
|
||||
created_at: string
|
||||
}> = []
|
||||
if (runIds.length > 0) {
|
||||
const { data: evs, error: evsErr } = await supabase
|
||||
.from('flow_run_events')
|
||||
.select('flow_run_id, event_type, node_key, payload, created_at')
|
||||
.in('flow_run_id', runIds)
|
||||
.order('created_at', { ascending: true })
|
||||
if (evsErr) {
|
||||
// Non-fatal — the page can still show runs without timelines.
|
||||
console.error('[flows-runs] events fetch failed:', evsErr.message)
|
||||
} else if (evs) {
|
||||
events = evs as typeof events
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
flow,
|
||||
runs: runs ?? [],
|
||||
events,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user