refactor: replace custom API fetch logic with requestJson utility across multiple pages
Updated the API fetching mechanism in various components to utilize the new requestJson function for improved consistency and error handling. This change affects the alerts, dashboard, data collection, filters, gre, network map, probes, recursive routes, route optimizer, servers, settings, traffic, and uptime pages.
This commit is contained in:
1 parent
5f31bb47fb
commit
6d8379501c
42 files changed
+1336
-631
No files matched your search
@@ -1,5 +1,4 @@
|
||||
import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod"
|
||||
import { z } from "zod"
|
||||
import {
|
||||
getAlertsMeta,
|
||||
getTelegramBotToken,
|
||||
@@ -13,90 +12,11 @@ import {
|
||||
sendTelegramAlertMessage,
|
||||
updateTelegramSettings,
|
||||
} from "../services/alerts-service.js"
|
||||
|
||||
const AlertTypeSchema = z.enum([
|
||||
"gre-tunnel",
|
||||
"bgp-peer",
|
||||
"bgp-prefix",
|
||||
"gre-client",
|
||||
"server",
|
||||
"rtt",
|
||||
"loss",
|
||||
"traffic",
|
||||
])
|
||||
const AlertSeveritySchema = z.enum(["critical", "warning", "info"])
|
||||
const AlertCooldownSchema = z.enum(["1м", "5м", "15м", "1ч", "4ч", "24ч"])
|
||||
const RecoveryModeSchema = z.enum(["always", "never", "conditional"])
|
||||
|
||||
const AlertGroupBodySchema = z.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
combineMode: z.enum(["any", "all"]),
|
||||
enabled: z.boolean(),
|
||||
cooldownOverride: z.union([AlertCooldownSchema, z.null()]),
|
||||
})
|
||||
|
||||
const AlertRuleBodySchema = z
|
||||
.object({
|
||||
id: z.string().min(1),
|
||||
name: z.string().min(1),
|
||||
type: AlertTypeSchema,
|
||||
target: z.string().optional(),
|
||||
targets: z.array(z.string().min(1)).optional(),
|
||||
groupId: z.string().nullable().optional(),
|
||||
/** Сводка; если переданы `conditions`, может дублировать первую строку */
|
||||
condition: z.string().optional(),
|
||||
/** Несколько условий (OR); приоритет над одной строкой `condition` */
|
||||
conditions: z.array(z.string().min(1)).optional(),
|
||||
severity: AlertSeveritySchema,
|
||||
enabled: z.boolean(),
|
||||
cooldown: AlertCooldownSchema,
|
||||
/** Секунды подтверждения стабильности; 0 / null / не передано — выкл. */
|
||||
confirmStabilitySec: z.union([z.number().int().min(0).max(86400), z.null()]).optional(),
|
||||
recoveryMode: RecoveryModeSchema.optional(),
|
||||
recoveryStabilitySec: z.union([z.number().int().min(0).max(86400), z.null()]).optional(),
|
||||
chatId: z.string(),
|
||||
})
|
||||
.refine((r) => (r.targets != null && r.targets.length > 0) || Boolean(r.target?.trim()), {
|
||||
message: "Нужен хотя бы один объект: targets или target",
|
||||
path: ["targets"],
|
||||
})
|
||||
.refine(
|
||||
(r) =>
|
||||
(r.conditions != null && r.conditions.length > 0) || Boolean(r.condition?.trim()),
|
||||
{ message: "Нужно хотя бы одно условие: conditions или condition", path: ["conditions"] },
|
||||
)
|
||||
|
||||
const PutRulesSchema = z.object({
|
||||
rules: z.array(AlertRuleBodySchema),
|
||||
groups: z.array(AlertGroupBodySchema).optional(),
|
||||
})
|
||||
|
||||
const PutTelegramSchema = z.object({
|
||||
/** undefined — не менять; null или "" — очистить токен */
|
||||
token: z.union([z.string(), z.null()]).optional(),
|
||||
chatId: z.string().optional(),
|
||||
/** undefined — не менять; null — сбросить (общий чат супергруппы) */
|
||||
messageThreadId: z.union([z.number().int().positive(), z.null()]).optional(),
|
||||
})
|
||||
|
||||
const RulePreviewTelegramSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
targets: z.array(z.string().min(1)).min(1),
|
||||
conditionLine: z.string().min(1),
|
||||
severity: AlertSeveritySchema,
|
||||
cooldown: AlertCooldownSchema,
|
||||
})
|
||||
|
||||
const TestTelegramSchema = z.object({
|
||||
/** Если не передан — взять сохранённый токен из БД */
|
||||
token: z.string().optional(),
|
||||
chatId: z.string().optional(),
|
||||
/** Если не передан — из БД; для проверки черновика до сохранения */
|
||||
messageThreadId: z.coerce.number().int().positive().optional(),
|
||||
/** Если задан — вместо короткого текста отправляется предпросмотр правила (создание / правка). */
|
||||
rulePreview: RulePreviewTelegramSchema.optional(),
|
||||
})
|
||||
import {
|
||||
putRulesSchema,
|
||||
putTelegramSchema,
|
||||
testTelegramSchema,
|
||||
} from "../../../packages/contracts/dist/alerts.js"
|
||||
|
||||
const alertsRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
app.get("/alerts", async (_req, reply) => {
|
||||
@@ -112,7 +32,7 @@ const alertsRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
})
|
||||
|
||||
app.put("/alerts/rules", async (req, reply) => {
|
||||
const parsed = PutRulesSchema.safeParse(req.body)
|
||||
const parsed = putRulesSchema.safeParse(req.body)
|
||||
if (!parsed.success) {
|
||||
return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
|
||||
}
|
||||
@@ -152,7 +72,7 @@ const alertsRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
})
|
||||
|
||||
app.put("/alerts/telegram", async (req, reply) => {
|
||||
const parsed = PutTelegramSchema.safeParse(req.body)
|
||||
const parsed = putTelegramSchema.safeParse(req.body)
|
||||
if (!parsed.success) {
|
||||
return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
|
||||
}
|
||||
@@ -166,7 +86,7 @@ const alertsRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
})
|
||||
|
||||
app.post("/alerts/telegram/test", async (req, reply) => {
|
||||
const parsed = TestTelegramSchema.safeParse(req.body ?? {})
|
||||
const parsed = testTelegramSchema.safeParse(req.body ?? {})
|
||||
if (!parsed.success) {
|
||||
return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
|
||||
}
|
||||
|
||||
@@ -4,14 +4,15 @@ import { db } from "../db/index.js"
|
||||
import { servers } from "../db/schema.js"
|
||||
import { parseBgpSessions } from "../services/bgp-parse-sessions.js"
|
||||
import { MikrotikClient } from "../services/mikrotik.js"
|
||||
import { ServerIdParamSchema } from "../types/server.js"
|
||||
import { ServerIdParamSchema, type ServerIdParams } from "../types/server.js"
|
||||
import type { BgpSessionRead } from "../types/server.js"
|
||||
|
||||
const bgpRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
|
||||
// GET /api/bgp/sessions/raw/:id — raw RouterOS response for a single server (debug)
|
||||
app.get("/bgp/sessions/raw/:id", { schema: { params: ServerIdParamSchema } }, async (req, reply) => {
|
||||
const server = db.select().from(servers).where(eq(servers.id, req.params.id)).limit(1).all()[0]
|
||||
const params = req.params as ServerIdParams
|
||||
const server = db.select().from(servers).where(eq(servers.id, params.id)).limit(1).all()[0]
|
||||
if (!server) return reply.status(404).send({ error: "Server not found" })
|
||||
try {
|
||||
const client = MikrotikClient.fromServer(server)
|
||||
@@ -44,9 +45,10 @@ const bgpRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
|
||||
// GET /api/servers/:id/bgp/sessions — single server
|
||||
app.get("/servers/:id/bgp/sessions", { schema: { params: ServerIdParamSchema } }, async (req, reply) => {
|
||||
const params = req.params as ServerIdParams
|
||||
const server = db
|
||||
.select().from(servers)
|
||||
.where(eq(servers.id, req.params.id))
|
||||
.where(eq(servers.id, params.id))
|
||||
.limit(1).all()[0]
|
||||
|
||||
if (!server) return reply.status(404).send({ error: "Server not found" })
|
||||
|
||||
@@ -4,7 +4,7 @@ import { z } from "zod"
|
||||
import { db } from "../db/index.js"
|
||||
import { servers } from "../db/schema.js"
|
||||
import { MikrotikClient } from "../services/mikrotik.js"
|
||||
import { ServerIdParamSchema } from "../types/server.js"
|
||||
import { ServerIdParamSchema, type ServerIdParams } from "../types/server.js"
|
||||
import type {
|
||||
RosIpAddress, RosInterface, RosResource, RosIdentity, RosIpRoute,
|
||||
RosFirewallFilter, RosLogEntry, RosBgpSession, RosPingResult,
|
||||
@@ -313,9 +313,10 @@ const execRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
"/servers/:id/exec",
|
||||
{ schema: { params: ServerIdParamSchema, body: ExecBodySchema } },
|
||||
async (req, reply) => {
|
||||
const params = req.params as ServerIdParams
|
||||
const server = db
|
||||
.select().from(servers)
|
||||
.where(eq(servers.id, req.params.id))
|
||||
.where(eq(servers.id, params.id))
|
||||
.limit(1).all()[0]
|
||||
|
||||
if (!server) return reply.status(404).send({ error: "Server not found" })
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod"
|
||||
import { db } from "../db/index.js"
|
||||
import { serverSnapshots, servers, trafficSamples, uptimeSpeedProbes } from "../db/schema.js"
|
||||
import { MikrotikClient } from "../services/mikrotik.js"
|
||||
import { ServerIdParamSchema } from "../types/server.js"
|
||||
import { ServerIdParamSchema, type ServerIdParams } from "../types/server.js"
|
||||
import type {
|
||||
RosOspfNeighbor, RosOspfArea, RosOspfInterfaceTemplate, RosOspfInstance,
|
||||
RosBfdSession,
|
||||
@@ -630,9 +630,10 @@ const ospfRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
|
||||
// GET /api/servers/:id/ospf — single server OSPF + BFD data
|
||||
app.get("/servers/:id/ospf", { schema: { params: ServerIdParamSchema } }, async (req, reply) => {
|
||||
const params = req.params as ServerIdParams
|
||||
const server = db
|
||||
.select().from(servers)
|
||||
.where(eq(servers.id, req.params.id))
|
||||
.where(eq(servers.id, params.id))
|
||||
.limit(1).all()[0]
|
||||
|
||||
if (!server) return reply.status(404).send({ error: "Server not found" })
|
||||
@@ -657,9 +658,10 @@ const ospfRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
"/servers/:id/ospf/optimize/preview",
|
||||
{ schema: { params: ServerIdParamSchema, body: OspfOptimizeBodySchema } },
|
||||
async (req, reply) => {
|
||||
const params = req.params as ServerIdParams
|
||||
const server = db
|
||||
.select().from(servers)
|
||||
.where(eq(servers.id, req.params.id))
|
||||
.where(eq(servers.id, params.id))
|
||||
.limit(1).all()[0]
|
||||
|
||||
if (!server) return reply.status(404).send({ error: "Server not found" })
|
||||
@@ -688,9 +690,10 @@ const ospfRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
"/servers/:id/ospf/optimize",
|
||||
{ schema: { params: ServerIdParamSchema, body: OspfOptimizeBodySchema } },
|
||||
async (req, reply) => {
|
||||
const params = req.params as ServerIdParams
|
||||
const server = db
|
||||
.select().from(servers)
|
||||
.where(eq(servers.id, req.params.id))
|
||||
.where(eq(servers.id, params.id))
|
||||
.limit(1).all()[0]
|
||||
|
||||
if (!server) return reply.status(404).send({ error: "Server not found" })
|
||||
|
||||
+36
-161
@@ -1,95 +1,28 @@
|
||||
import { desc, eq } from "drizzle-orm"
|
||||
import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod"
|
||||
import { db } from "../db/index.js"
|
||||
import { servers, serverSnapshots } from "../db/schema.js"
|
||||
import type { ServerRead, WanUplinkRead } from "../types/server.js"
|
||||
import {
|
||||
ServerCreateSchema,
|
||||
ServerUpdateSchema,
|
||||
ServerIdParamSchema,
|
||||
SnapshotsQuerySchema,
|
||||
TestConnectionSchema,
|
||||
type ServerCreateRequest,
|
||||
type ServerIdParams,
|
||||
type ServerUpdateRequest,
|
||||
type SnapshotsQuery,
|
||||
type TestConnectionRequest,
|
||||
} from "../types/server.js"
|
||||
import { pollServer, toSnapshotRead } from "../services/poller.js"
|
||||
import { pollServer } from "../services/poller.js"
|
||||
import { MikrotikClient, MikrotikError } from "../services/mikrotik.js"
|
||||
import { resolveRosSrcIpv4 } from "../utils/ros-src-address.js"
|
||||
|
||||
// ── helpers ────────────────────────────────────────────────────────────────────
|
||||
|
||||
type ServerRow = typeof servers.$inferSelect
|
||||
type SnapshotRow = typeof serverSnapshots.$inferSelect
|
||||
|
||||
function parseWanUplinksJson(raw: string | null | undefined): WanUplinkRead[] {
|
||||
if (raw == null || raw === "") return []
|
||||
try {
|
||||
const data = JSON.parse(raw) as unknown
|
||||
if (!Array.isArray(data)) return []
|
||||
const out: WanUplinkRead[] = []
|
||||
for (const row of data) {
|
||||
if (typeof row !== "object" || row === null) continue
|
||||
const r = row as Record<string, unknown>
|
||||
out.push({
|
||||
id: typeof r.id === "string" ? r.id : "",
|
||||
name: typeof r.name === "string" ? r.name : "",
|
||||
isp: typeof r.isp === "string" ? r.isp : "",
|
||||
iface: typeof r.iface === "string" ? r.iface : "",
|
||||
ip: typeof r.ip === "string" ? r.ip : "",
|
||||
maxDl: typeof r.maxDl === "number" && Number.isFinite(r.maxDl) ? r.maxDl : 0,
|
||||
maxUl: typeof r.maxUl === "number" && Number.isFinite(r.maxUl) ? r.maxUl : 0,
|
||||
})
|
||||
}
|
||||
return out
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/** Merge a server row with its latest snapshot into the frontend-compatible shape */
|
||||
function toServerRead(server: ServerRow, snap: SnapshotRow | undefined): ServerRead {
|
||||
return {
|
||||
id: server.id,
|
||||
name: server.name || server.host,
|
||||
host: server.host,
|
||||
port: server.port,
|
||||
useSsl: server.useSsl,
|
||||
verifySsl: server.verifySsl,
|
||||
username: server.username,
|
||||
password: server.password,
|
||||
type: server.type,
|
||||
site: server.site,
|
||||
country: server.country,
|
||||
asn: server.asn,
|
||||
comment: server.comment,
|
||||
enabled: server.enabled,
|
||||
lanSubnet: server.lanSubnet ?? "",
|
||||
wanUplinks: parseWanUplinksJson(server.wanUplinks),
|
||||
createdAt: server.createdAt,
|
||||
updatedAt: server.updatedAt,
|
||||
// snapshot fields (null if never polled)
|
||||
status: snap ? snap.status : null,
|
||||
latency: snap?.latencyMs ?? null,
|
||||
os: snap?.rosVersion ?? null,
|
||||
model: snap?.boardName ?? null,
|
||||
uptime: snap?.uptime ?? null,
|
||||
cpuLoad: snap?.cpuLoad ?? null,
|
||||
freeMemory: snap?.freeMemory ?? null,
|
||||
totalMemory: snap?.totalMemory ?? null,
|
||||
identityName: snap?.identityName ?? null,
|
||||
sessions: 0,
|
||||
polledAt: snap?.polledAt ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
/** Get latest snapshot for a single server */
|
||||
function getLatestSnapshot(serverId: number): SnapshotRow | undefined {
|
||||
return db
|
||||
.select()
|
||||
.from(serverSnapshots)
|
||||
.where(eq(serverSnapshots.serverId, serverId))
|
||||
.orderBy(desc(serverSnapshots.polledAt))
|
||||
.limit(1)
|
||||
.all()[0]
|
||||
}
|
||||
import {
|
||||
createServer,
|
||||
deleteServer,
|
||||
getServerReadById,
|
||||
listServerSnapshots,
|
||||
listServersRead,
|
||||
updateServer,
|
||||
} from "../modules/servers/service/servers-service.js"
|
||||
import { getServerRowById } from "../modules/servers/repository/servers-repository.js"
|
||||
|
||||
// ── plugin ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -97,7 +30,7 @@ const serversRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
|
||||
// POST /api/servers/test-connection — check credentials before saving
|
||||
app.post("/test-connection", { schema: { body: TestConnectionSchema } }, async (req, reply) => {
|
||||
const { host, port, useSsl, verifySsl, apiPath, username, password } = req.body
|
||||
const { host, port, useSsl, verifySsl, apiPath, username, password } = req.body as TestConnectionRequest
|
||||
const client = new MikrotikClient({ host, port, useSsl, verifySsl, apiPath, username, password })
|
||||
|
||||
try {
|
||||
@@ -144,17 +77,13 @@ const serversRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
|
||||
// GET /api/servers
|
||||
app.get("/", async (_req, reply) => {
|
||||
const all = db.select().from(servers).all()
|
||||
const result: ServerRead[] = all.map(s => toServerRead(s, getLatestSnapshot(s.id)))
|
||||
return reply.send(result)
|
||||
return reply.send(listServersRead())
|
||||
})
|
||||
|
||||
// GET /api/servers/:id/ros-src-address — IPv4 для src-address в RouterOS (не FQDN)
|
||||
app.get("/:id/ros-src-address", { schema: { params: ServerIdParamSchema } }, async (req, reply) => {
|
||||
const server = db
|
||||
.select().from(servers)
|
||||
.where(eq(servers.id, req.params.id))
|
||||
.limit(1).all()[0]
|
||||
const params = req.params as ServerIdParams
|
||||
const server = getServerRowById(params.id)
|
||||
|
||||
if (!server) return reply.status(404).send({ error: "Server not found" })
|
||||
const ipv4 = await resolveRosSrcIpv4(server.host)
|
||||
@@ -163,30 +92,15 @@ const serversRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
|
||||
// POST /api/servers
|
||||
app.post("/", { schema: { body: ServerCreateSchema } }, async (req, reply) => {
|
||||
const now = new Date().toISOString()
|
||||
const { wanUplinks, ...rest } = req.body
|
||||
const [inserted] = db
|
||||
.insert(servers)
|
||||
.values({
|
||||
...rest,
|
||||
wanUplinks: JSON.stringify(wanUplinks ?? []),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
})
|
||||
.returning()
|
||||
.all()
|
||||
return reply.status(201).send(toServerRead(inserted, undefined))
|
||||
return reply.status(201).send(createServer(req.body as ServerCreateRequest))
|
||||
})
|
||||
|
||||
// GET /api/servers/:id
|
||||
app.get("/:id", { schema: { params: ServerIdParamSchema } }, async (req, reply) => {
|
||||
const server = db
|
||||
.select().from(servers)
|
||||
.where(eq(servers.id, req.params.id))
|
||||
.limit(1).all()[0]
|
||||
|
||||
const params = req.params as ServerIdParams
|
||||
const server = getServerReadById(params.id)
|
||||
if (!server) return reply.status(404).send({ error: "Server not found" })
|
||||
return reply.send(toServerRead(server, getLatestSnapshot(server.id)))
|
||||
return reply.send(server)
|
||||
})
|
||||
|
||||
// PUT /api/servers/:id
|
||||
@@ -194,58 +108,30 @@ const serversRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
"/:id",
|
||||
{ schema: { params: ServerIdParamSchema, body: ServerUpdateSchema } },
|
||||
async (req, reply) => {
|
||||
const existing = db
|
||||
.select().from(servers)
|
||||
.where(eq(servers.id, req.params.id))
|
||||
.limit(1).all()[0]
|
||||
|
||||
const params = req.params as ServerIdParams
|
||||
const existing = getServerReadById(params.id)
|
||||
if (!existing) return reply.status(404).send({ error: "Server not found" })
|
||||
|
||||
const body = req.body
|
||||
const { wanUplinks, ...rest } = body
|
||||
const setPayload: Record<string, unknown> = { updatedAt: new Date().toISOString() }
|
||||
for (const [k, v] of Object.entries(rest)) {
|
||||
if (v !== undefined) setPayload[k] = v
|
||||
}
|
||||
if (wanUplinks !== undefined) {
|
||||
setPayload.wanUplinks = JSON.stringify(wanUplinks)
|
||||
}
|
||||
|
||||
const [updated] = db
|
||||
.update(servers)
|
||||
.set(setPayload as Partial<ServerRow>)
|
||||
.where(eq(servers.id, req.params.id))
|
||||
.returning()
|
||||
.all()
|
||||
|
||||
return reply.send(toServerRead(updated, getLatestSnapshot(updated.id)))
|
||||
return reply.send(updateServer(params.id, req.body as ServerUpdateRequest))
|
||||
},
|
||||
)
|
||||
|
||||
// DELETE /api/servers/:id
|
||||
app.delete("/:id", { schema: { params: ServerIdParamSchema } }, async (req, reply) => {
|
||||
const existing = db
|
||||
.select().from(servers)
|
||||
.where(eq(servers.id, req.params.id))
|
||||
.limit(1).all()[0]
|
||||
|
||||
const params = req.params as ServerIdParams
|
||||
const existing = getServerReadById(params.id)
|
||||
if (!existing) return reply.status(404).send({ error: "Server not found" })
|
||||
|
||||
db.delete(servers).where(eq(servers.id, req.params.id)).run()
|
||||
deleteServer(params.id)
|
||||
return reply.status(204).send()
|
||||
})
|
||||
|
||||
// POST /api/servers/:id/poll
|
||||
app.post("/:id/poll", { schema: { params: ServerIdParamSchema } }, async (req, reply) => {
|
||||
const existing = db
|
||||
.select().from(servers)
|
||||
.where(eq(servers.id, req.params.id))
|
||||
.limit(1).all()[0]
|
||||
|
||||
const params = req.params as ServerIdParams
|
||||
const existing = getServerReadById(params.id)
|
||||
if (!existing) return reply.status(404).send({ error: "Server not found" })
|
||||
|
||||
try {
|
||||
const snap = await pollServer(req.params.id)
|
||||
const snap = await pollServer(params.id)
|
||||
return reply.send(snap)
|
||||
} catch (err) {
|
||||
return reply.status(500).send({ error: (err as Error).message })
|
||||
@@ -257,22 +143,11 @@ const serversRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
"/:id/snapshots",
|
||||
{ schema: { params: ServerIdParamSchema, querystring: SnapshotsQuerySchema } },
|
||||
async (req, reply) => {
|
||||
const existing = db
|
||||
.select().from(servers)
|
||||
.where(eq(servers.id, req.params.id))
|
||||
.limit(1).all()[0]
|
||||
|
||||
const params = req.params as ServerIdParams
|
||||
const query = req.query as SnapshotsQuery
|
||||
const existing = getServerReadById(params.id)
|
||||
if (!existing) return reply.status(404).send({ error: "Server not found" })
|
||||
|
||||
const snaps = db
|
||||
.select()
|
||||
.from(serverSnapshots)
|
||||
.where(eq(serverSnapshots.serverId, req.params.id))
|
||||
.orderBy(desc(serverSnapshots.polledAt))
|
||||
.limit(req.query.limit)
|
||||
.all()
|
||||
|
||||
return reply.send(snaps.map(toSnapshotRead))
|
||||
return reply.send(listServerSnapshots(params.id, query.limit))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user