dev_arc_aws/backend/src/routes/tunnels.ts

94 lines
3.2 KiB
TypeScript
Raw Normal View History

import type { FastifyInstance } from 'fastify'
import { z } from 'zod'
import { db, logEvent } from '../db/index.js'
import {
getStatus,
getTunnelRow,
startTunnel,
stopTunnel,
deleteTunnelRuntime,
type TunnelRow,
} from '../tunnels/manager.js'
const createSchema = z.object({
name: z.string().min(1).max(128),
integrationId: z.number().int(),
mode: z.enum(['local', 'remote', 'dynamic']),
sourcePort: z.number().int().min(1).max(65535),
endpointHost: z.string().default(''),
endpointPort: z.number().int().min(0).max(65535).default(0),
autoStart: z.boolean().default(false),
maxRetries: z.number().int().min(0).max(20).default(3),
retryIntervalMs: z.number().int().min(500).max(60000).default(5000),
})
function serialize(row: TunnelRow) {
return {
id: row.id,
name: row.name,
integrationId: row.integration_id,
mode: row.mode,
sourcePort: row.source_port,
endpointHost: row.endpoint_host,
endpointPort: row.endpoint_port,
autoStart: !!row.auto_start,
maxRetries: row.max_retries,
retryIntervalMs: row.retry_interval_ms,
createdAt: row.created_at,
...getStatus(row.id),
}
}
export async function tunnelRoutes(app: FastifyInstance) {
app.addHook('onRequest', app.authenticate)
app.get('/api/tunnels', async () => {
const rows = db.prepare('SELECT * FROM tunnels ORDER BY created_at').all() as TunnelRow[]
return { tunnels: rows.map(serialize) }
})
app.post('/api/tunnels', async (req, reply) => {
const parsed = createSchema.safeParse(req.body)
if (!parsed.success) {
return reply.code(400).send({ error: parsed.error.issues[0]?.message ?? 'Invalid input' })
}
const d = parsed.data
const result = db
.prepare(
`INSERT INTO tunnels (name, integration_id, mode, source_port, endpoint_host, endpoint_port, auto_start, max_retries, retry_interval_ms)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
)
.run(d.name, d.integrationId, d.mode, d.sourcePort, d.endpointHost, d.endpointPort, d.autoStart ? 1 : 0, d.maxRetries, d.retryIntervalMs)
const id = Number(result.lastInsertRowid)
const row = getTunnelRow(id)!
logEvent('tunnel_created', `${d.name} tunnel added`, d.mode)
return reply.code(201).send({ tunnel: serialize(row) })
})
app.delete('/api/tunnels/:id', async (req, reply) => {
const id = Number((req.params as { id: string }).id)
const row = getTunnelRow(id)
if (!row) return reply.code(404).send({ error: 'Tunnel not found' })
deleteTunnelRuntime(id)
db.prepare('DELETE FROM tunnels WHERE id = ?').run(id)
logEvent('tunnel_deleted', `${row.name} tunnel removed`, row.mode)
return { ok: true }
})
app.post('/api/tunnels/:id/connect', async (req, reply) => {
const id = Number((req.params as { id: string }).id)
const row = getTunnelRow(id)
if (!row) return reply.code(404).send({ error: 'Tunnel not found' })
startTunnel(id)
return { ok: true, ...getStatus(id) }
})
app.post('/api/tunnels/:id/disconnect', async (req, reply) => {
const id = Number((req.params as { id: string }).id)
const row = getTunnelRow(id)
if (!row) return reply.code(404).send({ error: 'Tunnel not found' })
stopTunnel(id)
return { ok: true, ...getStatus(id) }
})
}