Openstatus www.openstatus.dev

🔥 api improvement (#539)

authored by

Thibault Le Ouay and committed by
GitHub
f07e881f 2b1bbeba

+122 -3
+1 -1
apps/server/src/index.ts
··· 7 7 import { publicRoute } from "./public"; 8 8 import { api } from "./v1"; 9 9 10 - const app = new Hono(); 10 + const app = new Hono({ strict: false }); 11 11 app.use("*", sentry({ dsn: process.env.SENTRY_DSN })); 12 12 13 13 /**
+99
apps/server/src/v1/monitor.ts
··· 7 7 monitorMethods, 8 8 monitorPeriodicity, 9 9 } from "@openstatus/db/src/schema"; 10 + import { getMonitorList, Tinybird } from "@openstatus/tinybird"; 11 + import { Redis } from "@openstatus/upstash"; 10 12 13 + import { env } from "../env"; 11 14 import type { Variables } from "./index"; 12 15 import { ErrorSchema } from "./shared"; 16 + 17 + const tb = new Tinybird({ token: env.TINY_BIRD_API_KEY }); 18 + const redis = Redis.fromEnv(); 13 19 14 20 const ParamsSchema = z.object({ 15 21 id: z ··· 462 468 463 469 await db.delete(monitor).where(eq(monitor.id, monitorId)).run(); 464 470 return c.jsonT({ message: "Deleted" }); 471 + }); 472 + 473 + const dailyStatsSchema = z.object({ 474 + ok: z.number().int().openapi({ 475 + description: "The number of ok responses", 476 + }), 477 + count: z 478 + .number() 479 + .int() 480 + .openapi({ description: "The total number of request" }), 481 + avgLatency: z.number().int().openapi({ description: "The average latency" }), 482 + day: z.string().openapi({ description: "the date of the event" }), 483 + }); 484 + 485 + const dailyStatsSchemaArray = z 486 + .array(dailyStatsSchema) 487 + .openapi({ description: "The daily stats" }); 488 + 489 + const getMonitorStats = createRoute({ 490 + method: "get", 491 + tags: ["monitor"], 492 + description: "Get monitor daily summary", 493 + path: "/:id/summary", 494 + request: { 495 + params: ParamsSchema, 496 + }, 497 + responses: { 498 + 200: { 499 + content: { 500 + "application/json": { 501 + schema: z.object({ 502 + data: dailyStatsSchemaArray, 503 + }), 504 + }, 505 + }, 506 + description: "All the historical metrics", 507 + }, 508 + 404: { 509 + content: { 510 + "application/json": { 511 + schema: ErrorSchema, 512 + }, 513 + }, 514 + description: "Not found", 515 + }, 516 + 401: { 517 + content: { 518 + "application/json": { 519 + schema: ErrorSchema, 520 + }, 521 + }, 522 + description: "Returns an error", 523 + }, 524 + }, 525 + }); 526 + monitorApi.openapi(getMonitorStats, async (c) => { 527 + const workspaceId = Number(c.get("workspaceId")); 528 + const { id } = c.req.valid("param"); 529 + 530 + const monitorId = Number(id); 531 + const _monitor = await db 532 + .select() 533 + .from(monitor) 534 + .where(eq(monitor.id, monitorId)) 535 + .get(); 536 + 537 + if (!_monitor) return c.jsonT({ code: 404, message: "Not Found" }); 538 + 539 + if (workspaceId !== _monitor.workspaceId) 540 + return c.jsonT({ code: 401, message: "Unauthorized" }); 541 + 542 + const cache = await redis.get<z.infer<typeof dailyStatsSchemaArray>>( 543 + `${monitorId}-daily-stats`, 544 + ); 545 + 546 + if (cache) { 547 + console.log("fetching from cache"); 548 + return c.jsonT({ 549 + data: cache, 550 + }); 551 + } 552 + 553 + console.log("fetching from tinybird"); 554 + const res = await getMonitorList(tb)({ 555 + monitorId: String(monitorId), 556 + limit: 30, 557 + // return data in utc 558 + timezone: "Etc/UTC", 559 + }); 560 + 561 + await redis.set(`${monitorId}-daily-stats`, res.data, { ex: 600 }); 562 + 563 + return c.jsonT({ data: res.data }); 465 564 }); 466 565 467 566 export { monitorApi };
+3 -1
apps/web/src/lib/tracker.ts
··· 80 80 for (const date of dateSequence) { 81 81 const timestamp = date.getTime(); 82 82 const cronTimestamp = 83 - dataIndex < data.length ? data[dataIndex].day.getTime() : undefined; 83 + dataIndex < data.length 84 + ? new Date(data[dataIndex].day).getTime() 85 + : undefined; 84 86 85 87 if ( 86 88 cronTimestamp &&
+4 -1
packages/tinybird/src/validation.ts
··· 60 60 count: z.number().int(), 61 61 ok: z.number().int(), 62 62 avgLatency: z.number().int(), 63 - day: z.coerce.date(), 63 + day: z.string().transform((val) => { 64 + // That's a hack because clickhouse return the date in UTC but in shitty format (2021-09-01 00:00:00) 65 + return new Date(`${val} GMT`).toISOString(); 66 + }), 64 67 }); 65 68 66 69 /**
+15
utils/api-bruno/Monitor Summary.bru
··· 1 + meta { 2 + name: Monitor Summary 3 + type: http 4 + seq: 3 5 + } 6 + 7 + get { 8 + url: {{url}}/v1/monitor/1/summary 9 + body: none 10 + auth: none 11 + } 12 + 13 + headers { 14 + x-openstatus-key: 1 15 + }