File size: 4,645 Bytes
b6ecafa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import { NextResponse } from 'next/server'
import { requireRole } from '@/lib/auth'
import { config } from '@/lib/config'
import { logger } from '@/lib/logger'

const GATEWAY_BASE = `http://${config.gatewayHost}:${config.gatewayPort}`

async function gatewayFetch(
  path: string,
  options: { method?: string; body?: string; timeoutMs?: number } = {}
): Promise<Response> {
  const { method = 'GET', body, timeoutMs = 5000 } = options
  const controller = new AbortController()
  const timer = setTimeout(() => controller.abort(), timeoutMs)

  try {
    const res = await fetch(`${GATEWAY_BASE}${path}`, {
      method,
      signal: controller.signal,
      headers: body ? { 'Content-Type': 'application/json' } : undefined,
      body,
    })
    return res
  } finally {
    clearTimeout(timer)
  }
}

export async function GET(request: Request) {
  const auth = requireRole(request, 'admin')
  if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })

  const { searchParams } = new URL(request.url)
  const action = searchParams.get('action') || 'status'

  try {
    switch (action) {
      case 'status': {
        try {
          const res = await gatewayFetch('/api/status')
          const data = await res.json()
          return NextResponse.json(data)
        } catch (err) {
          logger.warn({ err }, 'debug: gateway unreachable for status')
          return NextResponse.json({ gatewayReachable: false })
        }
      }

      case 'health': {
        try {
          const res = await gatewayFetch('/api/health')
          const data = await res.json()
          return NextResponse.json(data)
        } catch (err) {
          logger.warn({ err }, 'debug: gateway unreachable for health')
          return NextResponse.json({ healthy: false, error: 'Gateway unreachable' })
        }
      }

      case 'models': {
        try {
          const res = await gatewayFetch('/api/models')
          const data = await res.json()
          return NextResponse.json(data)
        } catch (err) {
          logger.warn({ err }, 'debug: gateway unreachable for models')
          return NextResponse.json({ models: [] })
        }
      }

      case 'heartbeat': {
        const start = performance.now()
        try {
          const res = await gatewayFetch('/api/heartbeat', { timeoutMs: 3000 })
          const latencyMs = Math.round(performance.now() - start)
          const ok = res.ok
          return NextResponse.json({ ok, latencyMs, timestamp: Date.now() })
        } catch {
          const latencyMs = Math.round(performance.now() - start)
          return NextResponse.json({ ok: false, latencyMs, timestamp: Date.now() })
        }
      }

      default:
        return NextResponse.json({ error: `Unknown action: ${action}` }, { status: 400 })
    }
  } catch (err) {
    logger.error({ err }, 'debug: unexpected error')
    return NextResponse.json({ error: 'Internal error' }, { status: 500 })
  }
}

export async function POST(request: Request) {
  const auth = requireRole(request, 'admin')
  if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status })

  const { searchParams } = new URL(request.url)
  const action = searchParams.get('action')

  if (action !== 'call') {
    return NextResponse.json({ error: 'POST only supports action=call' }, { status: 400 })
  }

  let body: { method?: string; path?: string; body?: any }
  try {
    body = await request.json()
  } catch {
    return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
  }

  const { method, path, body: callBody } = body

  if (!method || !['GET', 'POST'].includes(method)) {
    return NextResponse.json({ error: 'method must be GET or POST' }, { status: 400 })
  }

  if (!path || typeof path !== 'string' || !path.startsWith('/api/')) {
    return NextResponse.json({ error: 'path must start with /api/' }, { status: 400 })
  }

  try {
    const res = await gatewayFetch(path, {
      method,
      body: callBody ? JSON.stringify(callBody) : undefined,
      timeoutMs: 5000,
    })

    let responseBody: any
    const contentType = res.headers.get('content-type') || ''
    if (contentType.includes('application/json')) {
      responseBody = await res.json()
    } else {
      responseBody = await res.text()
    }

    return NextResponse.json({
      status: res.status,
      statusText: res.statusText,
      contentType,
      body: responseBody,
    })
  } catch (err) {
    logger.warn({ err, path }, 'debug: gateway call failed')
    return NextResponse.json({ error: 'Gateway unreachable', path }, { status: 502 })
  }
}