Openstatus www.openstatus.dev

🎯 working on my github stats

+451 -456
+4 -5
apps/screenshot-service/src/index.ts
··· 27 27 const app = new Hono(); 28 28 29 29 app.get("/ping", (c) => 30 - c.json({ ping: "pong", region: process.env.FLY_REGION }, 200) 30 + c.json({ ping: "pong", region: process.env.FLY_REGION }, 200), 31 31 ); 32 32 33 33 app.post( ··· 38 38 url: z.string().url(), 39 39 incidentId: z.number(), 40 40 kind: z.enum(["incident", "recovery"]), 41 - }) 41 + }), 42 42 ), 43 43 async (c) => { 44 44 const signature = c.req.header("Upstash-Signature"); ··· 53 53 body: JSON.stringify(data), 54 54 }); 55 55 if (!isValid) { 56 - 57 56 console.error("Unauthorized"); 58 57 return c.text("Unauthorized", 401); 59 58 } ··· 75 74 Bucket: "incident-screenshot", 76 75 Key: id, 77 76 ContentType: "image/png", 78 - }) 77 + }), 79 78 ); 80 79 81 80 if (data.kind === "incident") { ··· 109 108 } 110 109 111 110 return c.text("Screenshot saved"); 112 - } 111 + }, 113 112 ); 114 113 115 114 export default app;
+9 -9
apps/server/src/checker/index.ts
··· 1 + import { Client } from "@upstash/qstash"; 1 2 import { Hono } from "hono"; 2 3 import { z } from "zod"; 3 - import { Client } from "@upstash/qstash"; 4 4 5 5 import { and, db, eq, isNull, schema } from "@openstatus/db"; 6 6 import { incidentTable } from "@openstatus/db/src/schema"; ··· 59 59 and( 60 60 eq(incidentTable.monitorId, Number(monitorId)), 61 61 isNull(incidentTable.resolvedAt), 62 - isNull(incidentTable.acknowledgedAt) 63 - ) 62 + isNull(incidentTable.acknowledgedAt), 63 + ), 64 64 ) 65 65 .get(); 66 66 ··· 100 100 const numberOfRegions = monitor.regions.length; 101 101 102 102 console.log( 103 - `🤓 MonitorID ${monitorId} incident current affected ${nbAffectedRegion} total region ${numberOfRegions}` 103 + `🤓 MonitorID ${monitorId} incident current affected ${nbAffectedRegion} total region ${numberOfRegions}`, 104 104 ); 105 105 // If the number of affected regions is greater than half of the total region, we trigger the alerting 106 106 // 4 of 6 monitor need to fail to trigger an alerting ··· 114 114 eq(incidentTable.monitorId, Number(monitorId)), 115 115 isNull(incidentTable.resolvedAt), 116 116 isNull(incidentTable.acknowledgedAt), 117 - eq(incidentTable.startedAt, new Date(cronTimestamp)) 118 - ) 117 + eq(incidentTable.startedAt, new Date(cronTimestamp)), 118 + ), 119 119 ) 120 120 .get(); 121 121 ··· 192 192 const numberOfRegions = monitor.regions.length; 193 193 194 194 console.log( 195 - `🤓 MonitorId ${monitorId} recovering incident current ${nbAffectedRegion} total region ${numberOfRegions}` 195 + `🤓 MonitorId ${monitorId} recovering incident current ${nbAffectedRegion} total region ${numberOfRegions}`, 196 196 ); 197 197 // // If the number of affected regions is greater than half of the total region, we trigger the alerting 198 198 // // 4 of 6 monitor need to fail to trigger an alerting ··· 204 204 and( 205 205 eq(incidentTable.monitorId, Number(monitorId)), 206 206 isNull(incidentTable.resolvedAt), 207 - isNull(incidentTable.acknowledgedAt) 208 - ) 207 + isNull(incidentTable.acknowledgedAt), 208 + ), 209 209 ) 210 210 .get(); 211 211 if (incident) {
+1 -1
apps/server/src/index.ts
··· 5 5 6 6 import { checkerRoute } from "./checker"; 7 7 import { env } from "./env"; 8 + import { handleError } from "./libs/errors"; 8 9 import { publicRoute } from "./public"; 9 10 import { api } from "./v1"; 10 - import { handleError } from "./libs/errors"; 11 11 12 12 const app = new Hono({ strict: false }); 13 13 app.use("*", sentry({ dsn: process.env.SENTRY_DSN }));
+1 -1
apps/server/src/libs/errors/openapi-error-responses.ts
··· 51 51 content: { 52 52 "application/json": { 53 53 schema: createErrorSchema("INTERNAL_SERVER_ERROR").openapi( 54 - "ErrInternalServerError" 54 + "ErrInternalServerError", 55 55 ), 56 56 }, 57 57 },
+5 -5
apps/server/src/libs/errors/utils.ts
··· 19 19 message: error.message, 20 20 docs: "https://docs.openstatus.dev/api-references/errors/code/BAD_REQUEST", 21 21 }, 22 - { status: 400 } 22 + { status: 400 }, 23 23 ); 24 24 } 25 25 if (err instanceof HTTPException) { ··· 30 30 message: err.message, 31 31 docs: `https://docs.openstatus.dev/api-references/errors/code/${code}`, 32 32 }, 33 - { status: err.status } 33 + { status: err.status }, 34 34 ); 35 35 } 36 36 return c.json<ErrorSchema>( ··· 40 40 docs: "https://docs.openstatus.dev/api-references/errors/code/INTERNAL_SERVER_ERROR", 41 41 }, 42 42 43 - { status: 500 } 43 + { status: 500 }, 44 44 ); 45 45 } 46 46 ··· 54 54 success: false; 55 55 error: ZodError; 56 56 }, 57 - c: Context 57 + c: Context, 58 58 ) { 59 59 if (!result.success) { 60 60 const error = SchemaError.fromZod(result.error, c); ··· 64 64 docs: "https://docs.openstatus.dev/api-references/errors/code/BAD_REQUEST", 65 65 message: error.message, 66 66 }, 67 - { status: 400 } 67 + { status: 400 }, 68 68 ); 69 69 } 70 70 }
+9 -9
apps/server/src/public/status.ts
··· 84 84 and( 85 85 eq(monitorsToPages.monitorId, monitor.id), 86 86 eq(monitor.active, true), 87 - eq(monitorsToPages.pageId, pageId) 88 - ) 87 + eq(monitorsToPages.pageId, pageId), 88 + ), 89 89 ) 90 90 91 91 .all(); ··· 105 105 .from(monitorsToStatusReport) 106 106 .innerJoin( 107 107 statusReport, 108 - eq(monitorsToStatusReport.statusReportId, statusReport.id) 108 + eq(monitorsToStatusReport.statusReportId, statusReport.id), 109 109 ) 110 110 .where(inArray(monitorsToStatusReport.monitorId, monitorIds)) 111 111 .all(); ··· 117 117 statusReport, 118 118 and( 119 119 eq(pagesToStatusReports.statusReportId, statusReport.id), 120 - eq(pagesToStatusReports.pageId, pageId) 121 - ) 120 + eq(pagesToStatusReports.pageId, pageId), 121 + ), 122 122 ) 123 123 .all(); 124 124 ··· 128 128 .where( 129 129 and( 130 130 isNull(incidentTable.resolvedAt), 131 - inArray(incidentTable.monitorId, monitorIds) 132 - ) 131 + inArray(incidentTable.monitorId, monitorIds), 132 + ), 133 133 ) 134 134 .all(); 135 135 ··· 140 140 and( 141 141 eq(maintenance.pageId, pageId), 142 142 lte(maintenance.from, new Date()), 143 - gte(maintenance.to, new Date()) 144 - ) 143 + gte(maintenance.to, new Date()), 144 + ), 145 145 ); 146 146 147 147 const [
+2 -2
apps/server/src/v1/incidents/get.ts
··· 3 3 import { and, db, eq } from "@openstatus/db"; 4 4 import { incidentTable } from "@openstatus/db/src/schema/incidents"; 5 5 6 - import { IncidentSchema, ParamsSchema } from "./schema"; 6 + import { HTTPException } from "hono/http-exception"; 7 7 import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 8 8 import type { incidentsApi } from "./index"; 9 - import { HTTPException } from "hono/http-exception"; 9 + import { IncidentSchema, ParamsSchema } from "./schema"; 10 10 11 11 const getRoute = createRoute({ 12 12 method: "get",
+3 -3
apps/server/src/v1/incidents/get_all.ts
··· 3 3 import { db, eq } from "@openstatus/db"; 4 4 import { incidentTable } from "@openstatus/db/src/schema/incidents"; 5 5 6 - import { IncidentSchema } from "./schema"; 7 - import type { incidentsApi } from "./index"; 8 - import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 9 6 import { HTTPException } from "hono/http-exception"; 7 + import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 8 + import type { incidentsApi } from "./index"; 9 + import { IncidentSchema } from "./schema"; 10 10 11 11 const getAllRoute = createRoute({ 12 12 method: "get",
+2 -2
apps/server/src/v1/incidents/index.ts
··· 1 1 import { OpenAPIHono } from "@hono/zod-openapi"; 2 2 3 + import { handleZodError } from "../../libs/errors"; 3 4 import type { Variables } from "../index"; 4 - import { registerGetAllIncidents } from "./get_all"; 5 5 import { registerGetIncident } from "./get"; 6 + import { registerGetAllIncidents } from "./get_all"; 6 7 import { registerPutIncident } from "./put"; 7 - import { handleZodError } from "../../libs/errors"; 8 8 9 9 const incidentsApi = new OpenAPIHono<{ Variables: Variables }>({ 10 10 defaultHook: handleZodError,
+4 -4
apps/server/src/v1/incidents/put.ts
··· 3 3 import { and, db, eq } from "@openstatus/db"; 4 4 import { incidentTable } from "@openstatus/db/src/schema/incidents"; 5 5 6 - import { IncidentSchema, ParamsSchema } from "./schema"; 6 + import { HTTPException } from "hono/http-exception"; 7 7 import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 8 8 import type { incidentsApi } from "./index"; 9 - import { HTTPException } from "hono/http-exception"; 9 + import { IncidentSchema, ParamsSchema } from "./schema"; 10 10 11 11 const putRoute = createRoute({ 12 12 method: "put", ··· 59 59 .where( 60 60 and( 61 61 eq(incidentTable.id, Number(id)), 62 - eq(incidentTable.workspaceId, Number(workspaceId)) 63 - ) 62 + eq(incidentTable.workspaceId, Number(workspaceId)), 63 + ), 64 64 ) 65 65 .get(); 66 66
+3 -3
apps/server/src/v1/index.ts
··· 4 4 5 5 import type { Limits } from "@openstatus/plans/src/types"; 6 6 7 + import { handleError, handleZodError } from "../libs/errors"; 7 8 import { incidentsApi } from "./incidents"; 8 9 import { middleware } from "./middleware"; 9 10 import { monitorsApi } from "./monitors"; 10 11 import { notificationsApi } from "./notifications"; 11 - import { pagesApi } from "./pages"; 12 12 import { pageSubscribersApi } from "./pageSubscribers"; 13 - import { statusReportsApi } from "./statusReports"; 13 + import { pagesApi } from "./pages"; 14 14 import { statusReportUpdatesApi } from "./statusReportUpdates"; 15 - import { handleError, handleZodError } from "../libs/errors"; 15 + import { statusReportsApi } from "./statusReports"; 16 16 17 17 export type Variables = { 18 18 workspaceId: string;
+3 -3
apps/server/src/v1/middleware.ts
··· 1 1 import { verifyKey } from "@unkey/api"; 2 2 import type { Context, Next } from "hono"; 3 3 4 - import type { Variables } from "./index"; 4 + import { db, eq } from "@openstatus/db"; 5 5 import { workspace } from "@openstatus/db/src/schema"; 6 - import { db, eq } from "@openstatus/db"; 7 6 import { getPlanConfig } from "@openstatus/plans"; 7 + import type { Variables } from "./index"; 8 8 9 9 export async function middleware( 10 10 c: Context<{ Variables: Variables }, "/*">, 11 - next: Next 11 + next: Next, 12 12 ) { 13 13 const key = c.req.header("x-openstatus-key"); 14 14 if (!key) return c.text("Unauthorized", 401);
+2 -2
apps/server/src/v1/monitors/delete.ts
··· 3 3 import { db, eq } from "@openstatus/db"; 4 4 import { monitor } from "@openstatus/db/src/schema"; 5 5 6 - import { ParamsSchema } from "./schema"; 6 + import { HTTPException } from "hono/http-exception"; 7 7 import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 8 8 import type { monitorsApi } from "./index"; 9 - import { HTTPException } from "hono/http-exception"; 9 + import { ParamsSchema } from "./schema"; 10 10 11 11 const deleteRoute = createRoute({ 12 12 method: "delete",
+4 -4
apps/server/src/v1/monitors/get.ts
··· 3 3 import { and, db, eq, isNull } from "@openstatus/db"; 4 4 import { monitor } from "@openstatus/db/src/schema"; 5 5 6 + import { HTTPException } from "hono/http-exception"; 7 + import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 6 8 import type { monitorsApi } from "./index"; 7 9 import { MonitorSchema, ParamsSchema } from "./schema"; 8 - import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 9 - import { HTTPException } from "hono/http-exception"; 10 10 11 11 const getRoute = createRoute({ 12 12 method: "get", ··· 41 41 and( 42 42 eq(monitor.id, Number(id)), 43 43 eq(monitor.workspaceId, Number(workspaceId)), 44 - isNull(monitor.deletedAt) 45 - ) 44 + isNull(monitor.deletedAt), 45 + ), 46 46 ) 47 47 .get(); 48 48
+4 -4
apps/server/src/v1/monitors/get_all.ts
··· 3 3 import { and, db, eq, isNull } from "@openstatus/db"; 4 4 import { monitor } from "@openstatus/db/src/schema"; 5 5 6 + import { HTTPException } from "hono/http-exception"; 7 + import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 6 8 import type { monitorsApi } from "./index"; 7 9 import { MonitorSchema } from "./schema"; 8 - import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 9 - import { HTTPException } from "hono/http-exception"; 10 10 11 11 const getAllRoute = createRoute({ 12 12 method: "get", ··· 37 37 .where( 38 38 and( 39 39 eq(monitor.workspaceId, Number(workspaceId)), 40 - isNull(monitor.deletedAt) 41 - ) 40 + isNull(monitor.deletedAt), 41 + ), 42 42 ) 43 43 .all(); 44 44
+4 -4
apps/server/src/v1/monitors/index.ts
··· 1 1 import { OpenAPIHono } from "@hono/zod-openapi"; 2 2 3 + import { handleZodError } from "../../libs/errors"; 3 4 import type { Variables } from "../index"; 4 - import { registerGetAllMonitors } from "./get_all"; 5 + import { registerDeleteMonitor } from "./delete"; 5 6 import { registerGetMonitor } from "./get"; 7 + import { registerGetAllMonitors } from "./get_all"; 8 + import { registerPostMonitor } from "./post"; 6 9 import { registerPutMonitor } from "./put"; 7 - import { registerDeleteMonitor } from "./delete"; 8 10 import { registerGetMonitorSummary } from "./summary/get"; 9 - import { registerPostMonitor } from "./post"; 10 - import { handleZodError } from "../../libs/errors"; 11 11 12 12 const monitorsApi = new OpenAPIHono<{ Variables: Variables }>({ 13 13 defaultHook: handleZodError,
+5 -5
apps/server/src/v1/monitors/post.ts
··· 4 4 import { and, db, eq, isNull, sql } from "@openstatus/db"; 5 5 import { monitor } from "@openstatus/db/src/schema"; 6 6 7 - import type { monitorsApi } from "./index"; 7 + import { HTTPException } from "hono/http-exception"; 8 + import { env } from "../../env"; 8 9 import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 10 + import type { monitorsApi } from "./index"; 9 11 import { MonitorSchema } from "./schema"; 10 - import { env } from "../../env"; 11 - import { HTTPException } from "hono/http-exception"; 12 12 13 13 const postRoute = createRoute({ 14 14 method: "post", ··· 51 51 .where( 52 52 and( 53 53 eq(monitor.workspaceId, Number(workspaceId)), 54 - isNull(monitor.deletedAt) 55 - ) 54 + isNull(monitor.deletedAt), 55 + ), 56 56 ) 57 57 .all() 58 58 )[0].count;
+2 -2
apps/server/src/v1/monitors/put.ts
··· 3 3 import { db, eq } from "@openstatus/db"; 4 4 import { monitor } from "@openstatus/db/src/schema"; 5 5 6 - import { MonitorSchema, ParamsSchema } from "./schema"; 6 + import { HTTPException } from "hono/http-exception"; 7 7 import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 8 8 import type { monitorsApi } from "./index"; 9 - import { HTTPException } from "hono/http-exception"; 9 + import { MonitorSchema, ParamsSchema } from "./schema"; 10 10 11 11 const putRoute = createRoute({ 12 12 method: "put",
+38 -32
apps/server/src/v1/monitors/schema.ts
··· 36 36 description: "The url to monitor", 37 37 }), 38 38 regions: z 39 - .preprocess((val) => { 40 - try { 41 - if (Array.isArray(val)) return val; 42 - if (String(val).length > 0) { 43 - return String(val).split(","); 39 + .preprocess( 40 + (val) => { 41 + try { 42 + if (Array.isArray(val)) return val; 43 + if (String(val).length > 0) { 44 + return String(val).split(","); 45 + } 46 + return []; 47 + } catch (e) { 48 + throw new ZodError([ 49 + { 50 + code: "custom", 51 + path: ["headers"], 52 + message: e instanceof Error ? e.message : "Invalid value", 53 + }, 54 + ]); 44 55 } 45 - return []; 46 - } catch (e) { 47 - throw new ZodError([ 48 - { 49 - code: "custom", 50 - path: ["headers"], 51 - message: e instanceof Error ? e.message : "Invalid value", 52 - }, 53 - ]); 54 - } 55 - }, z.array(z.enum(flyRegions))) 56 + }, 57 + z.array(z.enum(flyRegions)), 58 + ) 56 59 .default([]) 57 60 .openapi({ 58 61 example: ["ams"], ··· 81 84 description: "The body", 82 85 }), 83 86 headers: z 84 - .preprocess((val) => { 85 - try { 86 - if (Array.isArray(val)) return val; 87 - if (String(val).length > 0) { 88 - return JSON.parse(String(val)); 87 + .preprocess( 88 + (val) => { 89 + try { 90 + if (Array.isArray(val)) return val; 91 + if (String(val).length > 0) { 92 + return JSON.parse(String(val)); 93 + } 94 + return []; 95 + } catch (e) { 96 + throw new ZodError([ 97 + { 98 + code: "custom", 99 + path: ["headers"], 100 + message: e instanceof Error ? e.message : "Invalid value", 101 + }, 102 + ]); 89 103 } 90 - return []; 91 - } catch (e) { 92 - throw new ZodError([ 93 - { 94 - code: "custom", 95 - path: ["headers"], 96 - message: e instanceof Error ? e.message : "Invalid value", 97 - }, 98 - ]); 99 - } 100 - }, z.array(z.object({ key: z.string(), value: z.string() })).default([])) 104 + }, 105 + z.array(z.object({ key: z.string(), value: z.string() })).default([]), 106 + ) 101 107 .nullish() 102 108 .openapi({ 103 109 description: "The headers of your request",
+6 -6
apps/server/src/v1/monitors/summary/get.ts
··· 5 5 import { OSTinybird } from "@openstatus/tinybird"; 6 6 import { Redis } from "@openstatus/upstash"; 7 7 8 - import { isoDate } from "../../utils"; 8 + import { HTTPException } from "hono/http-exception"; 9 9 import { env } from "../../../env"; 10 - import { ParamsSchema } from "../schema"; 11 10 import { openApiErrorResponses } from "../../../libs/errors/openapi-error-responses"; 11 + import { isoDate } from "../../utils"; 12 12 import type { monitorsApi } from "../index"; 13 - import { HTTPException } from "hono/http-exception"; 13 + import { ParamsSchema } from "../schema"; 14 14 15 15 const tb = new OSTinybird({ token: env.TINY_BIRD_API_KEY }); 16 16 const redis = Redis.fromEnv(); ··· 65 65 and( 66 66 eq(monitor.id, Number(id)), 67 67 eq(monitor.workspaceId, Number(workspaceId)), 68 - isNull(monitor.deletedAt) 69 - ) 68 + isNull(monitor.deletedAt), 69 + ), 70 70 ) 71 71 .get(); 72 72 ··· 75 75 } 76 76 77 77 const cache = await redis.get<z.infer<typeof dailyStatsSchemaArray>>( 78 - `${id}-daily-stats` 78 + `${id}-daily-stats`, 79 79 ); 80 80 if (cache) { 81 81 console.log("fetching from cache");
+2 -2
apps/server/src/v1/notifications/get.ts
··· 6 6 notificationsToMonitors, 7 7 page, 8 8 } from "@openstatus/db/src/schema"; 9 - import { NotificationSchema, ParamsSchema } from "./schema"; 9 + import { HTTPException } from "hono/http-exception"; 10 10 import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 11 11 import type { notificationsApi } from "./index"; 12 - import { HTTPException } from "hono/http-exception"; 12 + import { NotificationSchema, ParamsSchema } from "./schema"; 13 13 14 14 const getRoute = createRoute({ 15 15 method: "get",
+2 -2
apps/server/src/v1/notifications/get_all.ts
··· 6 6 notificationsToMonitors, 7 7 page, 8 8 } from "@openstatus/db/src/schema"; 9 - import { NotificationSchema } from "./schema"; 9 + import { HTTPException } from "hono/http-exception"; 10 10 import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 11 11 import type { notificationsApi } from "./index"; 12 - import { HTTPException } from "hono/http-exception"; 12 + import { NotificationSchema } from "./schema"; 13 13 14 14 const getAllRoute = createRoute({ 15 15 method: "get",
+2 -2
apps/server/src/v1/notifications/index.ts
··· 1 1 import { OpenAPIHono } from "@hono/zod-openapi"; 2 2 3 + import { handleZodError } from "../../libs/errors"; 3 4 import type { Variables } from "../index"; 4 - import { registerGetAllNotifications } from "./get_all"; 5 5 import { registerGetNotification } from "./get"; 6 + import { registerGetAllNotifications } from "./get_all"; 6 7 import { registerPostNotification } from "./post"; 7 - import { handleZodError } from "../../libs/errors"; 8 8 9 9 export const notificationsApi = new OpenAPIHono<{ Variables: Variables }>({ 10 10 defaultHook: handleZodError,
+4 -4
apps/server/src/v1/notifications/post.ts
··· 8 8 notificationsToMonitors, 9 9 selectNotificationSchema, 10 10 } from "@openstatus/db/src/schema"; 11 + import { HTTPException } from "hono/http-exception"; 11 12 import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 12 - import { NotificationSchema } from "./schema"; 13 13 import type { notificationsApi } from "./index"; 14 - import { HTTPException } from "hono/http-exception"; 14 + import { NotificationSchema } from "./schema"; 15 15 16 16 const postRoute = createRoute({ 17 17 method: "post", ··· 75 75 and( 76 76 inArray(monitor.id, monitors), 77 77 eq(monitor.workspaceId, Number(workspaceId)), 78 - isNull(monitor.deletedAt) 79 - ) 78 + isNull(monitor.deletedAt), 79 + ), 80 80 ) 81 81 .all(); 82 82
+1 -1
apps/server/src/v1/pageSubscribers/index.ts
··· 1 1 import { OpenAPIHono } from "@hono/zod-openapi"; 2 2 3 + import { handleZodError } from "../../libs/errors"; 3 4 import type { Variables } from "../index"; 4 5 import { registerPostPageSubscriber } from "./post"; 5 - import { handleZodError } from "../../libs/errors"; 6 6 7 7 export const pageSubscribersApi = new OpenAPIHono<{ Variables: Variables }>({ 8 8 defaultHook: handleZodError,
+5 -5
apps/server/src/v1/pageSubscribers/post.ts
··· 5 5 import { page, pageSubscriber } from "@openstatus/db/src/schema"; 6 6 import { SubscribeEmail } from "@openstatus/emails"; 7 7 import { sendEmail } from "@openstatus/emails/emails/send"; 8 - import { PageSubscriberSchema, ParamsSchema } from "./schema"; 8 + import { HTTPException } from "hono/http-exception"; 9 9 import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 10 10 import type { pageSubscribersApi } from "./index"; 11 - import { HTTPException } from "hono/http-exception"; 11 + import { PageSubscriberSchema, ParamsSchema } from "./schema"; 12 12 13 13 const postRouteSubscriber = createRoute({ 14 14 method: "post", ··· 49 49 .select() 50 50 .from(page) 51 51 .where( 52 - and(eq(page.id, Number(id)), eq(page.workspaceId, Number(workspaceId))) 52 + and(eq(page.id, Number(id)), eq(page.workspaceId, Number(workspaceId))), 53 53 ) 54 54 .get(); 55 55 ··· 63 63 .where( 64 64 and( 65 65 eq(pageSubscriber.email, input.email), 66 - eq(pageSubscriber.pageId, Number(id)) 67 - ) 66 + eq(pageSubscriber.pageId, Number(id)), 67 + ), 68 68 ) 69 69 .get(); 70 70
+2 -2
apps/server/src/v1/pages/get.ts
··· 3 3 import { and, eq } from "@openstatus/db"; 4 4 import { db } from "@openstatus/db/src/db"; 5 5 import { page } from "@openstatus/db/src/schema"; 6 - import { PageSchema, ParamsSchema } from "./schema"; 6 + import { HTTPException } from "hono/http-exception"; 7 7 import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 8 8 import type { pagesApi } from "./index"; 9 - import { HTTPException } from "hono/http-exception"; 9 + import { PageSchema, ParamsSchema } from "./schema"; 10 10 11 11 const getRoute = createRoute({ 12 12 method: "get",
+3 -3
apps/server/src/v1/pages/get_all.ts
··· 1 1 import { createRoute, z } from "@hono/zod-openapi"; 2 2 3 - import type { pagesApi } from "./index"; 4 - import { PageSchema } from "./schema"; 5 - import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 6 3 import { db, eq } from "@openstatus/db"; 7 4 import { page } from "@openstatus/db/src/schema"; 8 5 import { HTTPException } from "hono/http-exception"; 6 + import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 7 + import type { pagesApi } from "./index"; 8 + import { PageSchema } from "./schema"; 9 9 10 10 const getAllRoute = createRoute({ 11 11 method: "get",
+2 -2
apps/server/src/v1/pages/index.ts
··· 1 1 import { OpenAPIHono } from "@hono/zod-openapi"; 2 2 3 + import { handleZodError } from "../../libs/errors"; 3 4 import type { Variables } from "../index"; 4 5 import { registerGetPage } from "./get"; 5 6 import { registerGetAllPages } from "./get_all"; 6 - import { registerPutPage } from "./put"; 7 7 import { registerPostPage } from "./post"; 8 - import { handleZodError } from "../../libs/errors"; 8 + import { registerPutPage } from "./put"; 9 9 10 10 export const pagesApi = new OpenAPIHono<{ Variables: Variables }>({ 11 11 defaultHook: handleZodError,
+5 -5
apps/server/src/v1/pages/post.ts
··· 4 4 import { db } from "@openstatus/db/src/db"; 5 5 import { monitor, monitorsToPages, page } from "@openstatus/db/src/schema"; 6 6 7 - import type { pagesApi } from "./index"; 8 - import { isNumberArray } from "../utils"; 7 + import { HTTPException } from "hono/http-exception"; 9 8 import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 9 + import { isNumberArray } from "../utils"; 10 + import type { pagesApi } from "./index"; 10 11 import { PageSchema } from "./schema"; 11 - import { HTTPException } from "hono/http-exception"; 12 12 13 13 const postRoute = createRoute({ 14 14 method: "post", ··· 95 95 and( 96 96 inArray(monitor.id, monitorIds), 97 97 eq(monitor.workspaceId, Number(workspaceId)), 98 - isNull(monitor.deletedAt) 99 - ) 98 + isNull(monitor.deletedAt), 99 + ), 100 100 ) 101 101 .all(); 102 102
+8 -8
apps/server/src/v1/pages/put.ts
··· 3 3 import { and, eq, inArray, isNull, sql } from "@openstatus/db"; 4 4 import { db } from "@openstatus/db/src/db"; 5 5 import { monitor, monitorsToPages, page } from "@openstatus/db/src/schema"; 6 - import { PageSchema, ParamsSchema } from "./schema"; 6 + import { HTTPException } from "hono/http-exception"; 7 7 import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 8 - import type { pagesApi } from "./index"; 9 8 import { isNumberArray } from "../utils"; 10 - import { HTTPException } from "hono/http-exception"; 9 + import type { pagesApi } from "./index"; 10 + import { PageSchema, ParamsSchema } from "./schema"; 11 11 12 12 const putRoute = createRoute({ 13 13 method: "put", ··· 59 59 .select() 60 60 .from(page) 61 61 .where( 62 - and(eq(page.id, Number(id)), eq(page.workspaceId, Number(workspaceId))) 62 + and(eq(page.id, Number(id)), eq(page.workspaceId, Number(workspaceId))), 63 63 ) 64 64 .get(); 65 65 ··· 98 98 and( 99 99 inArray(monitor.id, monitorIds), 100 100 eq(monitor.workspaceId, Number(workspaceId)), 101 - isNull(monitor.deletedAt) 102 - ) 101 + isNull(monitor.deletedAt), 102 + ), 103 103 ) 104 104 .all(); 105 105 ··· 132 132 .where( 133 133 and( 134 134 inArray(monitorsToPages.monitorId, removedMonitors), 135 - eq(monitorsToPages.pageId, newPage.id) 136 - ) 135 + eq(monitorsToPages.pageId, newPage.id), 136 + ), 137 137 ); 138 138 } 139 139
+1 -1
apps/server/src/v1/pages/schema.ts
··· 81 81 { monitorId: 1, order: 0 }, 82 82 { monitorId: 2, order: 1 }, 83 83 ], 84 - }) 84 + }), 85 85 ) 86 86 .optional(), 87 87 });
+6 -6
apps/server/src/v1/statusReportUpdates/get.ts
··· 3 3 import { and, db, eq } from "@openstatus/db"; 4 4 import { statusReport, statusReportUpdate } from "@openstatus/db/src/schema"; 5 5 6 + import { HTTPException } from "hono/http-exception"; 7 + import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 6 8 import type { statusReportUpdatesApi } from "./index"; 7 9 import { ParamsSchema, StatusReportUpdateSchema } from "./schema"; 8 - import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 9 - import { HTTPException } from "hono/http-exception"; 10 10 11 11 const getRoute = createRoute({ 12 12 method: "get", ··· 30 30 }); 31 31 32 32 export function registerGetStatusReportUpdate( 33 - api: typeof statusReportUpdatesApi 33 + api: typeof statusReportUpdatesApi, 34 34 ) { 35 35 return api.openapi(getRoute, async (c) => { 36 36 const workspaceId = c.get("workspaceId"); ··· 43 43 statusReport, 44 44 and( 45 45 eq(statusReport.id, statusReportUpdate.statusReportId), 46 - eq(statusReport.workspaceId, Number(workspaceId)) 47 - ) 46 + eq(statusReport.workspaceId, Number(workspaceId)), 47 + ), 48 48 ) 49 49 .where(eq(statusReportUpdate.id, Number(id))) 50 50 .get(); ··· 54 54 } 55 55 56 56 const data = StatusReportUpdateSchema.parse( 57 - _statusReportJoin.status_report_update 57 + _statusReportJoin.status_report_update, 58 58 ); 59 59 60 60 return c.json(data);
+1 -1
apps/server/src/v1/statusReportUpdates/index.ts
··· 1 1 import { OpenAPIHono } from "@hono/zod-openapi"; 2 2 3 + import { handleZodError } from "../../libs/errors"; 3 4 import type { Variables } from "../index"; 4 5 import { registerGetStatusReportUpdate } from "./get"; 5 6 import { registerPostStatusReportUpdate } from "./post"; 6 - import { handleZodError } from "../../libs/errors"; 7 7 8 8 export const statusReportUpdatesApi = new OpenAPIHono<{ 9 9 Variables: Variables;
+8 -8
apps/server/src/v1/statusReportUpdates/post.ts
··· 10 10 } from "@openstatus/db/src/schema"; 11 11 import { sendEmailHtml } from "@openstatus/emails"; 12 12 13 + import { HTTPException } from "hono/http-exception"; 14 + import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 13 15 import type { statusReportUpdatesApi } from "./index"; 14 16 import { StatusReportUpdateSchema } from "./schema"; 15 - import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 16 - import { HTTPException } from "hono/http-exception"; 17 17 18 18 const createStatusUpdate = createRoute({ 19 19 method: "post", ··· 44 44 }); 45 45 46 46 export function registerPostStatusReportUpdate( 47 - api: typeof statusReportUpdatesApi 47 + api: typeof statusReportUpdatesApi, 48 48 ) { 49 49 return api.openapi(createStatusUpdate, async (c) => { 50 50 const workspaceId = c.get("workspaceId"); ··· 57 57 .where( 58 58 and( 59 59 eq(statusReport.id, input.statusReportId), 60 - eq(statusReport.workspaceId, Number(workspaceId)) 61 - ) 60 + eq(statusReport.workspaceId, Number(workspaceId)), 61 + ), 62 62 ) 63 63 .get(); 64 64 ··· 95 95 .where( 96 96 and( 97 97 eq(pageSubscriber.pageId, currentPage.pageId), 98 - isNotNull(pageSubscriber.acceptedAt) 99 - ) 98 + isNotNull(pageSubscriber.acceptedAt), 99 + ), 100 100 ) 101 101 .all(); 102 102 ··· 107 107 .get(); 108 108 if (!pageInfo) continue; 109 109 const subscribersEmails = subscribers.map( 110 - (subscriber) => subscriber.email 110 + (subscriber) => subscriber.email, 111 111 ); 112 112 113 113 // TODO: verify if we leak any email data here
+4 -4
apps/server/src/v1/statusReports/delete.ts
··· 3 3 import { and, db, eq } from "@openstatus/db"; 4 4 import { statusReport } from "@openstatus/db/src/schema"; 5 5 6 + import { HTTPException } from "hono/http-exception"; 7 + import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 6 8 import type { statusReportsApi } from "./index"; 7 9 import { ParamsSchema } from "./schema"; 8 - import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 9 - import { HTTPException } from "hono/http-exception"; 10 10 11 11 const deleteRoute = createRoute({ 12 12 method: "delete", ··· 40 40 .where( 41 41 and( 42 42 eq(statusReport.id, Number(id)), 43 - eq(statusReport.workspaceId, Number(workspaceId)) 44 - ) 43 + eq(statusReport.workspaceId, Number(workspaceId)), 44 + ), 45 45 ) 46 46 .get(); 47 47
+3 -3
apps/server/src/v1/statusReports/get.ts
··· 3 3 import { and, db, eq } from "@openstatus/db"; 4 4 import { statusReport } from "@openstatus/db/src/schema"; 5 5 6 + import { HTTPException } from "hono/http-exception"; 7 + import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 6 8 import type { statusReportsApi } from "./index"; 7 9 import { ParamsSchema, StatusReportSchema } from "./schema"; 8 - import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 9 - import { HTTPException } from "hono/http-exception"; 10 10 11 11 const getRoute = createRoute({ 12 12 method: "get", ··· 42 42 }, 43 43 where: and( 44 44 eq(statusReport.workspaceId, Number(workspaceId)), 45 - eq(statusReport.id, Number(id)) 45 + eq(statusReport.id, Number(id)), 46 46 ), 47 47 }); 48 48
+3 -3
apps/server/src/v1/statusReports/get_all.ts
··· 3 3 import { db, eq } from "@openstatus/db"; 4 4 import { statusReport } from "@openstatus/db/src/schema"; 5 5 6 + import { HTTPException } from "hono/http-exception"; 7 + import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 6 8 import type { statusReportsApi } from "./index"; 7 9 import { StatusReportSchema } from "./schema"; 8 - import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 9 - import { HTTPException } from "hono/http-exception"; 10 10 11 11 const getAllRoute = createRoute({ 12 12 method: "get", ··· 50 50 statusReportUpdateIds: r.statusReportUpdates.map((u) => u.id), 51 51 pageIds: r.pagesToStatusReports.map((p) => p.pageId), 52 52 monitorIds: r.monitorsToStatusReports.map((m) => m.monitorId), 53 - })) 53 + })), 54 54 ); 55 55 56 56 return c.json(data);
+2 -2
apps/server/src/v1/statusReports/index.ts
··· 1 1 import { OpenAPIHono } from "@hono/zod-openapi"; 2 2 3 + import { handleZodError } from "../../libs/errors"; 3 4 import type { Variables } from "../index"; 4 - import { registerGetAllStatusReports } from "./get_all"; 5 5 import { registerDeleteStatusReport } from "./delete"; 6 6 import { regsiterGetStatusReport } from "./get"; 7 + import { registerGetAllStatusReports } from "./get_all"; 7 8 import { registerPostStatusReport } from "./post"; 8 - import { handleZodError } from "../../libs/errors"; 9 9 import { registerStatusReportUpdateRoutes } from "./update/post"; 10 10 11 11 export const statusReportsApi = new OpenAPIHono<{ Variables: Variables }>({
+14 -14
apps/server/src/v1/statusReports/post.ts
··· 11 11 statusReportUpdate, 12 12 } from "@openstatus/db/src/schema"; 13 13 14 - import type { statusReportsApi } from "./index"; 15 - import { StatusReportSchema } from "./schema"; 16 - import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 14 + import { sendEmailHtml } from "@openstatus/emails"; 17 15 import { HTTPException } from "hono/http-exception"; 16 + import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 18 17 import { isoDate } from "../utils"; 19 - import { sendEmailHtml } from "@openstatus/emails"; 18 + import type { statusReportsApi } from "./index"; 19 + import { StatusReportSchema } from "./schema"; 20 20 21 21 const postRoute = createRoute({ 22 22 method: "post", ··· 72 72 and( 73 73 eq(monitor.workspaceId, Number(workspaceId)), 74 74 inArray(monitor.id, monitorIds), 75 - isNull(monitor.deletedAt) 76 - ) 75 + isNull(monitor.deletedAt), 76 + ), 77 77 ) 78 78 .all(); 79 79 ··· 89 89 .where( 90 90 and( 91 91 eq(page.workspaceId, Number(workspaceId)), 92 - inArray(page.id, pageIds) 93 - ) 92 + inArray(page.id, pageIds), 93 + ), 94 94 ) 95 95 .all(); 96 96 ··· 127 127 pageId: id, 128 128 statusReportId: _newStatusReport.id, 129 129 }; 130 - }) 130 + }), 131 131 ) 132 132 .returning(); 133 133 } ··· 141 141 monitorId: id, 142 142 statusReportId: _newStatusReport.id, 143 143 }; 144 - }) 144 + }), 145 145 ) 146 146 .returning(); 147 147 } ··· 151 151 .select() 152 152 .from(pagesToStatusReports) 153 153 .where( 154 - eq(pagesToStatusReports.statusReportId, Number(_newStatusReport.id)) 154 + eq(pagesToStatusReports.statusReportId, Number(_newStatusReport.id)), 155 155 ) 156 156 .all(); 157 157 for (const currentPage of allPages) { ··· 161 161 .where( 162 162 and( 163 163 eq(pageSubscriber.pageId, currentPage.pageId), 164 - isNotNull(pageSubscriber.acceptedAt) 165 - ) 164 + isNotNull(pageSubscriber.acceptedAt), 165 + ), 166 166 ) 167 167 .all(); 168 168 const pageInfo = await db ··· 172 172 .get(); 173 173 if (!pageInfo) continue; 174 174 const subscribersEmails = subscribers.map( 175 - (subscriber) => subscriber.email 175 + (subscriber) => subscriber.email, 176 176 ); 177 177 await sendEmailHtml({ 178 178 to: subscribersEmails,
+10 -10
apps/server/src/v1/statusReports/update/post.ts
··· 1 1 import { createRoute } from "@hono/zod-openapi"; 2 - import { ParamsSchema, StatusReportSchema } from "../schema"; 3 - import { StatusReportUpdateSchema } from "../../statusReportUpdates/schema"; 4 - import { openApiErrorResponses } from "../../../libs/errors/openapi-error-responses"; 5 - import type { statusReportsApi } from "../index"; 2 + import { and, db, eq, isNotNull } from "@openstatus/db"; 6 3 import { 7 4 page, 8 5 pageSubscriber, ··· 10 7 statusReport, 11 8 statusReportUpdate, 12 9 } from "@openstatus/db/src/schema"; 13 - import { and, db, eq, isNotNull } from "@openstatus/db"; 14 10 import { sendEmailHtml } from "@openstatus/emails"; 15 11 import { HTTPException } from "hono/http-exception"; 12 + import { openApiErrorResponses } from "../../../libs/errors/openapi-error-responses"; 13 + import { StatusReportUpdateSchema } from "../../statusReportUpdates/schema"; 14 + import type { statusReportsApi } from "../index"; 15 + import { ParamsSchema, StatusReportSchema } from "../schema"; 16 16 17 17 const postRouteUpdate = createRoute({ 18 18 method: "post", ··· 57 57 .where( 58 58 and( 59 59 eq(statusReport.id, Number(id)), 60 - eq(statusReport.workspaceId, Number(workspaceId)) 61 - ) 60 + eq(statusReport.workspaceId, Number(workspaceId)), 61 + ), 62 62 ) 63 63 .returning() 64 64 .get(); ··· 90 90 .where( 91 91 and( 92 92 eq(pageSubscriber.pageId, currentPage.pageId), 93 - isNotNull(pageSubscriber.acceptedAt) 94 - ) 93 + isNotNull(pageSubscriber.acceptedAt), 94 + ), 95 95 ) 96 96 .all(); 97 97 const pageInfo = await db ··· 101 101 .get(); 102 102 if (!pageInfo) continue; 103 103 const subscribersEmails = subscribers.map( 104 - (subscriber) => subscriber.email 104 + (subscriber) => subscriber.email, 105 105 ); 106 106 await sendEmailHtml({ 107 107 to: subscribersEmails,
+1 -1
apps/server/src/v1/utils.ts
··· 8 8 }, z.string()); 9 9 10 10 export function isNumberArray<T>( 11 - monitors: number[] | T[] 11 + monitors: number[] | T[], 12 12 ): monitors is number[] { 13 13 return ( 14 14 Array.isArray(monitors) &&
+6 -6
apps/web/src/app/api/checker/cron/_cron.ts
··· 45 45 const parent = client.queuePath( 46 46 env.GCP_PROJECT_ID, 47 47 env.GCP_LOCATION, 48 - periodicity 48 + periodicity, 49 49 ); 50 50 51 51 const timestamp = Date.now(); ··· 54 54 .select({ id: maintenance.id }) 55 55 .from(maintenance) 56 56 .where( 57 - and(lte(maintenance.from, new Date()), gte(maintenance.to, new Date())) 57 + and(lte(maintenance.from, new Date()), gte(maintenance.to, new Date())), 58 58 ) 59 59 .as("currentMaintenance"); 60 60 ··· 63 63 .from(maintenancesToMonitors) 64 64 .innerJoin( 65 65 currentMaintenance, 66 - eq(maintenancesToMonitors.maintenanceId, currentMaintenance.id) 66 + eq(maintenancesToMonitors.maintenanceId, currentMaintenance.id), 67 67 ); 68 68 69 69 const result = await db ··· 73 73 and( 74 74 eq(monitor.periodicity, periodicity), 75 75 eq(monitor.active, true), 76 - notInArray(monitor.id, currentMaintenanceMonitors) 77 - ) 76 + notInArray(monitor.id, currentMaintenanceMonitors), 77 + ), 78 78 ) 79 79 .all(); 80 80 ··· 127 127 const failed = allRequests.filter((r) => r.status === "rejected").length; 128 128 129 129 console.log( 130 - `End cron for ${periodicity} with ${allResult.length} jobs with ${success} success and ${failed} failed` 130 + `End cron for ${periodicity} with ${allResult.length} jobs with ${success} success and ${failed} failed`, 131 131 ); 132 132 }; 133 133 // timestamp needs to be in ms
+5 -7
apps/web/src/app/api/og/page/route.tsx
··· 12 12 13 13 export async function GET(req: Request) { 14 14 const [interRegularData, interLightData, calSemiBoldData] = await Promise.all( 15 - [interRegular, interLight, calSemiBold] 15 + [interRegular, interLight, calSemiBold], 16 16 ); 17 17 18 18 const { searchParams } = new URL(req.url); ··· 34 34 }); 35 35 36 36 return new ImageResponse( 37 - ( 38 - <BasicLayout title={title} description={description} tw="py-24 px-24"> 39 - <StatusCheck tracker={tracker} /> 40 - </BasicLayout> 41 - ), 37 + <BasicLayout title={title} description={description} tw="py-24 px-24"> 38 + <StatusCheck tracker={tracker} /> 39 + </BasicLayout>, 42 40 { 43 41 ...SIZE, 44 42 fonts: [ ··· 61 59 weight: 600, 62 60 }, 63 61 ], 64 - } 62 + }, 65 63 ); 66 64 }
+8 -8
apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/(overview)/page.tsx
··· 32 32 if (v === "true") return true; 33 33 if (v === "false") return false; 34 34 return undefined; 35 - }) 35 + }), 36 36 ) 37 37 .optional(), 38 38 }); ··· 74 74 { 75 75 monitorId: String(monitor.id), 76 76 }, 77 - { cache: "no-store", revalidate: 0 } 77 + { cache: "no-store", revalidate: 0 }, 78 78 ); 79 79 80 80 const data = await tb.endpointStatusPeriod("7d")( 81 81 { 82 82 monitorId: String(monitor.id), 83 83 }, 84 - { cache: "no-store", revalidate: 0 } 84 + { cache: "no-store", revalidate: 0 }, 85 85 ); 86 86 87 87 const [current] = metrics?.sort((a, b) => 88 - (a.lastTimestamp || 0) - (b.lastTimestamp || 0) < 0 ? 1 : -1 88 + (a.lastTimestamp || 0) - (b.lastTimestamp || 0) < 0 ? 1 : -1, 89 89 ) || [undefined]; 90 90 91 91 const incidents = _incidents.filter( 92 - (incident) => incident.monitorId === monitor.id 92 + (incident) => incident.monitorId === monitor.id, 93 93 ); 94 94 95 95 const tags = monitor.monitorTagsToMonitors.map( 96 - ({ monitorTag }) => monitorTag 96 + ({ monitorTag }) => monitorTag, 97 97 ); 98 98 99 99 const maintenances = _maintenances.filter((maintenance) => 100 - maintenance.monitors.includes(monitor.id) 100 + maintenance.monitors.includes(monitor.id), 101 101 ); 102 102 103 103 return { monitor, metrics: current, data, incidents, maintenances, tags }; 104 - }) 104 + }), 105 105 ); 106 106 107 107 return (
+1 -1
apps/web/src/app/app/[workspaceSlug]/(dashboard)/monitors/[id]/layout.tsx
··· 45 45 <span className="text-muted-foreground/50 text-xs">•</span> 46 46 <TagBadgeWithTooltip 47 47 tags={monitor.monitorTagsToMonitors.map( 48 - ({ monitorTag }) => monitorTag 48 + ({ monitorTag }) => monitorTag, 49 49 )} 50 50 /> 51 51 </>
+1 -1
apps/web/src/app/app/[workspaceSlug]/(dashboard)/rum/_components/rum-card.tsx
··· 1 - import { Card } from "@tremor/react"; 2 1 import { getColorByType, webVitalsConfig } from "@openstatus/rum"; 3 2 import type { WebVitalEvents, WebVitalsValues } from "@openstatus/rum"; 3 + import { Card } from "@tremor/react"; 4 4 import { CategoryBar } from "./category-bar"; 5 5 6 6 export function prepareWebVitalValues(values: WebVitalsValues) {
+1 -1
apps/web/src/app/app/[workspaceSlug]/(dashboard)/rum/overview/_components/session-table.tsx
··· 1 1 import { api } from "@/trpc/server"; 2 - import { DataTableWrapper } from "./data-table-wrapper"; 3 2 import { useSearchParams } from "next/navigation"; 4 3 import { use } from "react"; 4 + import { DataTableWrapper } from "./data-table-wrapper"; 5 5 6 6 const SessionTable = async ({ dsn, path }: { dsn: string; path: string }) => { 7 7 const data = await api.tinybird.sessionRumMetricsForPath.query({
+2 -2
apps/web/src/app/app/[workspaceSlug]/(dashboard)/rum/overview/page.tsx
··· 1 1 import * as React from "react"; 2 2 3 - import { PathCard } from "./_components/path-card"; 4 3 import { api } from "@/trpc/server"; 5 - import { SessionTable } from "./_components/session-table"; 6 4 import { z } from "zod"; 5 + import { PathCard } from "./_components/path-card"; 6 + import { SessionTable } from "./_components/session-table"; 7 7 8 8 const searchParamsSchema = z.object({ 9 9 path: z.string(),
+4 -4
apps/web/src/app/app/[workspaceSlug]/(dashboard)/rum/page.tsx
··· 1 1 import * as React from "react"; 2 2 3 3 import { EmptyState } from "@/components/dashboard/empty-state"; 4 + import { auth } from "@/lib/auth"; 4 5 import { api } from "@/trpc/server"; 5 - import { RouteTable } from "./_components/route-table"; 6 - import { RUMMetricCards } from "./_components/rum-metric-card"; 7 6 import { Redis } from "@upstash/redis"; 8 - import { auth } from "@/lib/auth"; 9 7 import { RequestButton } from "./_components/request-button/request-button"; 8 + import { RouteTable } from "./_components/route-table"; 9 + import { RUMMetricCards } from "./_components/rum-metric-card"; 10 10 11 11 export const dynamic = "force-dynamic"; 12 12 ··· 20 20 21 21 const accessRequested = await redis.sismember( 22 22 "rum_access_requested", 23 - session.user.email 23 + session.user.email, 24 24 ); 25 25 26 26 if (applications.length === 0) {
+2 -2
apps/web/src/app/app/[workspaceSlug]/(dashboard)/settings/team/_components/info-banner.tsx
··· 1 1 "use client"; 2 2 3 + import { Info } from "lucide-react"; 3 4 import Link from "next/link"; 4 5 import { useParams } from "next/navigation"; 5 - import { Info } from "lucide-react"; 6 6 7 7 import { Alert, AlertDescription, AlertTitle } from "@openstatus/ui"; 8 8 ··· 16 16 To inform your team about the workspace name, please set it in the{" "} 17 17 <Link 18 18 href={`/app/${params.workspaceSlug}/settings/general`} 19 - className="text-foreground inline-flex items-center font-medium underline underline-offset-4 hover:no-underline" 19 + className="inline-flex items-center font-medium text-foreground underline underline-offset-4 hover:no-underline" 20 20 > 21 21 general 22 22 </Link>{" "}
+2 -2
apps/web/src/app/app/invite/page.tsx
··· 30 30 if (!data) { 31 31 return ( 32 32 <div className="mx-auto flex h-full max-w-xl flex-1 flex-col items-center justify-center gap-4"> 33 - <h1 className="text-2xl font-semibold">Invitation</h1> 33 + <h1 className="font-semibold text-2xl">Invitation</h1> 34 34 <Alert variant="destructive"> 35 35 <AlertTriangle className="h-4 w-4" /> 36 36 <AlertTitle>Something went wrong</AlertTitle> ··· 45 45 46 46 return ( 47 47 <div className="mx-auto flex h-full max-w-xl flex-1 flex-col items-center justify-center gap-4"> 48 - <h1 className="text-2xl font-semibold">Invitation</h1> 48 + <h1 className="font-semibold text-2xl">Invitation</h1> 49 49 <Alert> 50 50 <Icons.check className="h-4 w-4" /> 51 51 <AlertTitle>Ready to go</AlertTitle>
+1 -1
apps/web/src/app/layout.tsx
··· 4 4 import { Inter } from "next/font/google"; 5 5 import LocalFont from "next/font/local"; 6 6 7 - import { OpenStatusProvider } from "@openstatus/next-monitoring"; 8 7 import { Toaster } from "@/components/ui/sonner"; 8 + import { OpenStatusProvider } from "@openstatus/next-monitoring"; 9 9 10 10 import { 11 11 defaultMetadata,
+9 -11
apps/web/src/app/status-page/[domain]/badge/route.tsx
··· 45 45 }; 46 46 export async function GET( 47 47 req: NextRequest, 48 - { params }: { params: { domain: string } } 48 + { params }: { params: { domain: string } }, 49 49 ) { 50 50 const { status } = await getStatus(params.domain); 51 51 const theme = req.nextUrl.searchParams.get("theme"); ··· 61 61 const dark = "border-gray-800 text-gray-300 bg-gray-900"; 62 62 63 63 return new ImageResponse( 64 - ( 65 - <div 66 - tw={`flex items-center justify-center rounded-md border px-3 py-1 64 + <div 65 + tw={`flex items-center justify-center rounded-md border px-3 py-1 67 66 ${size === "sm" && "text-sm"}${size === "md" && "text-md"} ${ 68 67 size === "lg" && "text-lg" 69 68 } ${size === "xl" && "text-xl"} ${!size && "text-sm"} ${ 70 69 theme === "dark" ? dark : light 71 70 }`} 72 - style={{ ...s }} 73 - > 74 - {label} 75 - <div tw={`flex h-2 w-2 rounded-full ml-2 ${color}`} /> 76 - </div> 77 - ), 78 - { ...s } 71 + style={{ ...s }} 72 + > 73 + {label} 74 + <div tw={`flex h-2 w-2 rounded-full ml-2 ${color}`} /> 75 + </div>, 76 + { ...s }, 79 77 ); 80 78 }
+1 -1
apps/web/src/app/status-page/[domain]/incidents/[id]/page.tsx
··· 21 21 if (!report) return notFound(); 22 22 23 23 const affectedMonitors = report.monitorsToStatusReports.map( 24 - ({ monitor }) => monitor 24 + ({ monitor }) => monitor, 25 25 ); 26 26 27 27 return (
+2 -2
apps/web/src/app/status-page/[domain]/page.tsx
··· 4 4 import { Separator } from "@openstatus/ui"; 5 5 6 6 import { Header } from "@/components/dashboard/header"; 7 + import { MaintenanceBanner } from "@/components/status-page/maintenance-banner"; 7 8 import { MonitorList } from "@/components/status-page/monitor-list"; 8 9 import { StatusCheck } from "@/components/status-page/status-check"; 9 10 import { StatusReportList } from "@/components/status-page/status-report-list"; 10 11 import { api } from "@/trpc/server"; 11 - import { MaintenanceBanner } from "@/components/status-page/maintenance-banner"; 12 12 13 13 type Props = { 14 14 params: { domain: string }; ··· 25 25 const currentMaintenances = page.maintenances.filter( 26 26 (maintenance) => 27 27 maintenance.to.getTime() > Date.now() && 28 - maintenance.from.getTime() < Date.now() 28 + maintenance.from.getTime() < Date.now(), 29 29 ); 30 30 31 31 return (
+4 -4
apps/web/src/components/data-table/data-table-faceted-filter.tsx
··· 41 41 42 42 const facets = column?.getFacetedUniqueValues(); 43 43 const selectedValues = new Set( 44 - column?.getFilterValue() as (string | number | boolean)[] 44 + column?.getFilterValue() as (string | number | boolean)[], 45 45 ); 46 46 47 47 const updatePageSearchParams = ( 48 - values: Record<string, number | string | null> 48 + values: Record<string, number | string | null>, 49 49 ) => { 50 50 const newSearchParams = updateSearchParams(values); 51 51 router.replace(`?${newSearchParams}`, { scroll: false }); ··· 111 111 } 112 112 const filterValues = Array.from(selectedValues); 113 113 column?.setFilterValue( 114 - filterValues.length ? filterValues : undefined 114 + filterValues.length ? filterValues : undefined, 115 115 ); 116 116 117 117 // update search params ··· 128 128 "mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary", 129 129 isSelected 130 130 ? "bg-primary text-primary-foreground" 131 - : "opacity-50 [&_svg]:invisible" 131 + : "opacity-50 [&_svg]:invisible", 132 132 )} 133 133 > 134 134 <Check className={cn("h-4 w-4")} />
+1 -1
apps/web/src/components/data-table/maintenance/columns.tsx
··· 4 4 5 5 import type { Maintenance } from "@openstatus/db/src/schema"; 6 6 7 - import { DataTableRowActions } from "./data-table-row-actions"; 8 7 import { formatDateTime } from "@/lib/utils"; 9 8 import { format } from "date-fns"; 9 + import { DataTableRowActions } from "./data-table-row-actions"; 10 10 11 11 export const columns: ColumnDef<Maintenance>[] = [ 12 12 {
+1 -1
apps/web/src/components/data-table/monitor/columns.tsx
··· 84 84 // REMINDER: if one value is found, return true 85 85 // we could consider restricting it to all the values have to be found 86 86 return value.some((item) => 87 - row.original.tags?.some((tag) => tag.name === item) 87 + row.original.tags?.some((tag) => tag.name === item), 88 88 ); 89 89 }, 90 90 },
+1 -1
apps/web/src/components/data-table/rum/data-table.tsx
··· 45 45 ? null 46 46 : flexRender( 47 47 header.column.columnDef.header, 48 - header.getContext() 48 + header.getContext(), 49 49 )} 50 50 </TableHead> 51 51 );
+1 -1
apps/web/src/components/data-table/session/data-table.tsx
··· 45 45 ? null 46 46 : flexRender( 47 47 header.column.columnDef.header, 48 - header.getContext() 48 + header.getContext(), 49 49 )} 50 50 </TableHead> 51 51 );
+1 -1
apps/web/src/components/data-table/status-page/columns.tsx
··· 14 14 TooltipTrigger, 15 15 } from "@openstatus/ui"; 16 16 17 - import { DataTableRowActions } from "./data-table-row-actions"; 18 17 import { Check } from "lucide-react"; 18 + import { DataTableRowActions } from "./data-table-row-actions"; 19 19 20 20 export const columns: ColumnDef< 21 21 Page & {
+1 -1
apps/web/src/components/dev-mode-container.tsx
··· 11 11 <div 12 12 className={cn( 13 13 "-m-2 relative rounded-lg border-2 border-destructive/80 p-2", 14 - className 14 + className, 15 15 )} 16 16 > 17 17 <p className="-top-2 absolute left-3 bg-background px-1 font-medium text-destructive text-xs uppercase">
+1 -1
apps/web/src/components/forms/maintenance/general.tsx
··· 16 16 Textarea, 17 17 } from "@openstatus/ui"; 18 18 19 - import { SectionHeader } from "../shared/section-header"; 20 19 import { format } from "date-fns"; 20 + import { SectionHeader } from "../shared/section-header"; 21 21 22 22 interface Props { 23 23 form: UseFormReturn<InsertMaintenance>;
+2 -2
apps/web/src/components/forms/maintenance/section-connect.tsx
··· 63 63 ]) 64 64 : field.onChange( 65 65 field.value?.filter( 66 - (value) => value !== item.id 67 - ) 66 + (value) => value !== item.id, 67 + ), 68 68 ); 69 69 }} 70 70 >
+2 -2
apps/web/src/components/forms/monitor/section-notifications.tsx
··· 86 86 ]) 87 87 : field.onChange( 88 88 field.value?.filter( 89 - (value) => value !== item.id 90 - ) 89 + (value) => value !== item.id, 90 + ), 91 91 ); 92 92 }} 93 93 >
+2 -2
apps/web/src/components/forms/monitor/section-scheduling.tsx
··· 120 120 ]) 121 121 : field.onChange( 122 122 field.value?.filter( 123 - (value) => value !== item 124 - ) 123 + (value) => value !== item, 124 + ), 125 125 ); 126 126 }} 127 127 >
+2 -2
apps/web/src/components/forms/monitor/section-status-page.tsx
··· 89 89 ]) 90 90 : field.onChange( 91 91 field.value?.filter( 92 - (value) => value !== item.id 93 - ) 92 + (value) => value !== item.id, 93 + ), 94 94 ); 95 95 }} 96 96 >
+3 -3
apps/web/src/components/forms/notification/form.tsx
··· 15 15 import { LoadingAnimation } from "@/components/loading-animation"; 16 16 import { toast, toastAction } from "@/lib/toast"; 17 17 import { api } from "@/trpc/client"; 18 + import { SchemaError } from "@openstatus/error"; 19 + import { TRPCClientError } from "@trpc/client"; 20 + import { ZodError, type ZodIssue } from "zod"; 18 21 import { SaveButton } from "../shared/save-button"; 19 22 import { 20 23 getDefaultProviderData, ··· 22 25 setProviderData, 23 26 } from "./config"; 24 27 import { General } from "./general"; 25 - import { TRPCClientError } from "@trpc/client"; 26 - import { ZodError, type ZodIssue } from "zod"; 27 - import { SchemaError } from "@openstatus/error"; 28 28 29 29 interface Props { 30 30 defaultValues?: InsertNotification;
+1 -1
apps/web/src/components/forms/shared/checkbox-label.tsx
··· 34 34 htmlFor={`${name}-${id}`} 35 35 className={cn( 36 36 "flex h-full items-center gap-1 rounded-md border border-border bg-popover p-4 pr-10 [&:has([data-state=checked])]:border-primary peer-data-[state=checked]:border-primary hover:bg-accent hover:text-accent-foreground", 37 - className 37 + className, 38 38 )} 39 39 > 40 40 {children}
+1 -1
apps/web/src/components/layout/app-sidebar.tsx
··· 8 8 9 9 function replacePlaceholders( 10 10 template: string, 11 - values: { [key: string]: string } 11 + values: { [key: string]: string }, 12 12 ): string { 13 13 return template.replace(/\[([^\]]+)\]/g, (_, key) => { 14 14 return values[key] || `[${key}]`;
+1 -1
apps/web/src/components/marketing/alert/timeline.tsx
··· 29 29 <div 30 30 className={cn( 31 31 "rounded-full border bg-background p-2", 32 - icon.borderColor 32 + icon.borderColor, 33 33 )} 34 34 > 35 35 <Icon className={cn("h-4 w-4", icon.textColor)} />
+1 -1
apps/web/src/components/marketing/hero.tsx
··· 27 27 <h1 28 28 className={cn( 29 29 "font-cal text-4xl text-foreground md:text-6xl", 30 - "bg-gradient-to-tl from-0% from-[hsl(var(--muted))] to-40% to-[hsl(var(--foreground))] bg-clip-text text-transparent" 30 + "bg-gradient-to-tl from-0% from-[hsl(var(--muted))] to-40% to-[hsl(var(--foreground))] bg-clip-text text-transparent", 31 31 )} 32 32 > 33 33 A better way to monitor your services.
+1 -1
apps/web/src/components/marketing/pricing/pricing-wrapper.tsx
··· 4 4 5 5 import type { WorkspacePlan } from "@openstatus/plans"; 6 6 7 + import { Suspense } from "react"; 7 8 import { PricingPlanRadio } from "./pricing-plan-radio"; 8 9 import { PricingTable } from "./pricing-table"; 9 - import { Suspense } from "react"; 10 10 11 11 export function PricingWrapper() { 12 12 const searchParams = useSearchParams();
+1 -1
apps/web/src/components/monitor-dashboard/metrics.tsx
··· 27 27 if (!metrics || metrics.length === 0) return null; 28 28 29 29 const [current, last] = metrics.sort((a, b) => 30 - (a.lastTimestamp || 0) - (b.lastTimestamp || 0) < 0 ? 1 : -1 30 + (a.lastTimestamp || 0) - (b.lastTimestamp || 0) < 0 ? 1 : -1, 31 31 ); 32 32 33 33 const isEmpty = current.count === 0;
+1 -1
apps/web/src/components/status-page/maintenance-banner.tsx
··· 1 - import { isSameDay, format } from "date-fns"; 1 + import { format, isSameDay } from "date-fns"; 2 2 import { Hammer } from "lucide-react"; 3 3 4 4 import type { Maintenance } from "@openstatus/db/src/schema";
+4 -4
apps/web/src/components/status-page/monitor-list.tsx
··· 26 26 {monitors.map((monitor, _index) => { 27 27 const monitorStatusReport = statusReports.filter((statusReport) => 28 28 statusReport.monitorsToStatusReports.some( 29 - (i) => i.monitor.id === monitor.id 30 - ) 29 + (i) => i.monitor.id === monitor.id, 30 + ), 31 31 ); 32 32 const monitorIncidents = incidents.filter( 33 - (incident) => incident.monitorId === monitor.id 33 + (incident) => incident.monitorId === monitor.id, 34 34 ); 35 35 const monitorMaintenances = maintenances.filter((maintenance) => 36 - maintenance.monitors?.includes(monitor.id) 36 + maintenance.monitors?.includes(monitor.id), 37 37 ); 38 38 return ( 39 39 <Monitor
+1 -1
apps/web/src/components/status-page/status-report-list.tsx
··· 23 23 return statusReports.filter((report) => { 24 24 if (filter.open && report.status !== "resolved") return true; 25 25 return report.statusReportUpdates.some( 26 - (update) => update.date.getTime() > filter?.date?.getTime() 26 + (update) => update.date.getTime() > filter?.date?.getTime(), 27 27 ); 28 28 }); 29 29 }
+1 -1
apps/web/src/components/status-page/status-report.tsx
··· 13 13 import { Badge, Button } from "@openstatus/ui"; 14 14 15 15 import { setPrefixUrl } from "@/app/status-page/[domain]/utils"; 16 + import { cn } from "@/lib/utils"; 16 17 import { StatusBadge } from "../status-update/status-badge"; 17 18 import { ProcessMessage } from "./process-message"; 18 - import { cn } from "@/lib/utils"; 19 19 20 20 function StatusReport({ 21 21 report,
+2 -2
apps/web/src/components/support/bubble.tsx
··· 1 1 "use client"; 2 2 3 - import { useState } from "react"; 4 3 import { MessageCircle } from "lucide-react"; 4 + import { useState } from "react"; 5 5 6 6 import { 7 7 Button, ··· 10 10 PopoverTrigger, 11 11 } from "@openstatus/ui"; 12 12 13 - import { ContactForm } from "./contact-form"; 14 13 import { useSession } from "next-auth/react"; 14 + import { ContactForm } from "./contact-form"; 15 15 16 16 export function Bubble() { 17 17 const [open, setOpen] = useState(false);
+3 -3
apps/web/src/components/support/contact-form.tsx
··· 1 1 "use client"; 2 2 3 - import { useTransition } from "react"; 4 3 import { zodResolver } from "@hookform/resolvers/zod"; 4 + import { useTransition } from "react"; 5 5 import { useForm } from "react-hook-form"; 6 6 import { z } from "zod"; 7 7 ··· 23 23 Textarea, 24 24 } from "@openstatus/ui"; 25 25 26 + import { toast } from "@/lib/toast"; 26 27 import { LoadingAnimation } from "../loading-animation"; 27 28 import { handlePlainSupport } from "./action"; 28 - import { toast } from "@/lib/toast"; 29 29 30 30 export const types = [ 31 31 { ··· 84 84 } else { 85 85 handleSubmit?.(); 86 86 toast.success( 87 - "Your message has been sent! We will get back to you soon." 87 + "Your message has been sent! We will get back to you soon.", 88 88 ); 89 89 } 90 90 });
+2 -2
apps/web/src/components/tracker/tracker.tsx
··· 150 150 <div 151 151 className={cn( 152 152 rootClassName, 153 - "h-auto w-1 flex-none rounded-full" 153 + "h-auto w-1 flex-none rounded-full", 154 154 )} 155 155 /> 156 156 <div className="grid flex-1 gap-1"> ··· 245 245 Down for{" "} 246 246 {formatDuration( 247 247 { minutes, hours, days }, 248 - { format: ["days", "hours", "minutes", "seconds"], zero: false } 248 + { format: ["days", "hours", "minutes", "seconds"], zero: false }, 249 249 )} 250 250 </p> 251 251 );
+1 -1
apps/web/src/config/pages.ts
··· 247 247 248 248 export function getPageBySegment( 249 249 segment: string | string[], 250 - currentPage: readonly Page[] = pagesConfig 250 + currentPage: readonly Page[] = pagesConfig, 251 251 ): Page | undefined { 252 252 if (typeof segment === "string") { 253 253 const page = currentPage.find((page) => page.segment === segment);
+1 -1
apps/web/src/lib/auth/index.ts
··· 6 6 import { user } from "@openstatus/db/src/schema"; 7 7 import { WelcomeEmail, sendEmail } from "@openstatus/emails"; 8 8 9 + import { identifyUser } from "@/providers/posthog"; 9 10 import { adapter } from "./adapter"; 10 11 import { GitHubProvider, GoogleProvider, ResendProvider } from "./providers"; 11 - import { identifyUser } from "@/providers/posthog"; 12 12 13 13 export type { DefaultSession }; 14 14
+1 -1
apps/web/src/lib/timezone.ts
··· 94 94 } 95 95 return prev; 96 96 }, 97 - { timezone: "UTC", minDifference: Number.POSITIVE_INFINITY } 97 + { timezone: "UTC", minDifference: Number.POSITIVE_INFINITY }, 98 98 ); 99 99 100 100 return closestTimezone.timezone as keyof typeof timeDifferences;
+2 -2
apps/web/src/lib/utils.ts
··· 40 40 } 41 41 42 42 export function notEmpty<TValue>( 43 - value: TValue | null | undefined 43 + value: TValue | null | undefined, 44 44 ): value is TValue { 45 45 return value !== null && value !== undefined; 46 46 } ··· 69 69 date?: { 70 70 from: Date | undefined; 71 71 to?: Date | undefined; 72 - } | null 72 + } | null, 73 73 ) { 74 74 const isToDateMidnight = String(date?.to?.getTime()).endsWith("00000"); 75 75
+2 -2
apps/web/src/providers/posthog.tsx
··· 1 1 "use client"; 2 2 3 - import { useEffect } from "react"; 3 + import type { User } from "next-auth"; 4 4 import { usePathname, useSearchParams } from "next/navigation"; 5 - import type { User } from "next-auth"; 6 5 import type { CaptureOptions, Properties } from "posthog-js"; 7 6 import posthog from "posthog-js"; 8 7 import { PostHogProvider } from "posthog-js/react"; 8 + import { useEffect } from "react"; 9 9 10 10 import { env } from "@/env"; 11 11
+7 -7
packages/api/src/router/invitation.ts
··· 34 34 where: and( 35 35 eq(invitation.workspaceId, opts.ctx.workspace.id), 36 36 gte(invitation.expiresAt, new Date()), 37 - isNull(invitation.acceptedAt) 37 + isNull(invitation.acceptedAt), 38 38 ), 39 39 }) 40 40 ).length; ··· 60 60 61 61 if (process.env.NODE_ENV === "development") { 62 62 console.log( 63 - `>>>> Invitation token: http://localhost:3000/app/invite?token=${token} <<<< ` 63 + `>>>> Invitation token: http://localhost:3000/app/invite?token=${token} <<<< `, 64 64 ); 65 65 } else { 66 66 await fetch("https://api.resend.com/emails", { ··· 104 104 .where( 105 105 and( 106 106 eq(invitation.id, opts.input.id), 107 - eq(invitation.workspaceId, opts.ctx.workspace.id) 108 - ) 107 + eq(invitation.workspaceId, opts.ctx.workspace.id), 108 + ), 109 109 ) 110 110 .run(); 111 111 }), ··· 115 115 where: and( 116 116 eq(invitation.workspaceId, opts.ctx.workspace.id), 117 117 gte(invitation.expiresAt, new Date()), 118 - isNull(invitation.acceptedAt) 118 + isNull(invitation.acceptedAt), 119 119 ), 120 120 }); 121 121 return _invitations; ··· 144 144 z.object({ 145 145 message: z.string(), 146 146 data: selectWorkspaceSchema.optional(), 147 - }) 147 + }), 148 148 ) 149 149 .mutation(async (opts) => { 150 150 const _invitation = await opts.ctx.db.query.invitation.findFirst({ 151 151 where: and( 152 152 eq(invitation.token, opts.input.token), 153 - isNull(invitation.acceptedAt) 153 + isNull(invitation.acceptedAt), 154 154 ), 155 155 with: { 156 156 workspace: true,
+19 -19
packages/api/src/router/maintenance.ts
··· 8 8 selectMaintenanceSchema, 9 9 } from "@openstatus/db/src/schema"; 10 10 11 - import { createTRPCRouter, protectedProcedure } from "../trpc"; 12 11 import { TRPCError } from "@trpc/server"; 12 + import { createTRPCRouter, protectedProcedure } from "../trpc"; 13 13 14 14 export const maintenanceRouter = createTRPCRouter({ 15 15 create: protectedProcedure ··· 28 28 opts.input.monitors.map((monitorId) => ({ 29 29 maintenanceId: _maintenance.id, 30 30 monitorId, 31 - })) 31 + })), 32 32 ) 33 33 .returning() 34 34 .get(); ··· 45 45 .where( 46 46 and( 47 47 eq(maintenance.id, opts.input.id), 48 - eq(maintenance.workspaceId, opts.ctx.workspace.id) 49 - ) 48 + eq(maintenance.workspaceId, opts.ctx.workspace.id), 49 + ), 50 50 ) 51 51 .get(); 52 52 ··· 71 71 const _maintenances = await opts.ctx.db.query.maintenance.findMany({ 72 72 where: and( 73 73 eq(maintenance.workspaceId, opts.ctx.workspace.id), 74 - gte(maintenance.from, new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)) 74 + gte(maintenance.from, new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)), 75 75 ), 76 76 with: { maintenancesToMonitors: true }, 77 77 }); ··· 89 89 .where( 90 90 and( 91 91 eq(maintenance.pageId, opts.input.id), 92 - eq(maintenance.workspaceId, opts.ctx.workspace.id) 93 - ) 92 + eq(maintenance.workspaceId, opts.ctx.workspace.id), 93 + ), 94 94 ) 95 95 .all(); 96 96 // TODO: ··· 104 104 and( 105 105 eq(maintenance.workspaceId, opts.ctx.workspace.id), 106 106 gte(maintenance.to, new Date()), 107 - lte(maintenance.from, new Date()) 108 - ) 107 + lte(maintenance.from, new Date()), 108 + ), 109 109 ) 110 110 .all(); 111 111 return _maintenances; ··· 123 123 .where( 124 124 and( 125 125 eq(maintenance.id, opts.input.id), 126 - eq(maintenance.workspaceId, opts.ctx.workspace.id) 127 - ) 126 + eq(maintenance.workspaceId, opts.ctx.workspace.id), 127 + ), 128 128 ) 129 129 .returning() 130 130 .get(); ··· 136 136 .all(); 137 137 138 138 const _monitorsIds = _maintenancesToMonitors.map( 139 - ({ monitorId }) => monitorId 139 + ({ monitorId }) => monitorId, 140 140 ); 141 141 142 142 const added = opts.input.monitors?.filter( 143 - (monitor) => !_monitorsIds.includes(monitor) 143 + (monitor) => !_monitorsIds.includes(monitor), 144 144 ); 145 145 146 146 if (added?.length) { ··· 150 150 added.map((monitorId) => ({ 151 151 maintenanceId: _maintenance.id, 152 152 monitorId, 153 - })) 153 + })), 154 154 ) 155 155 .returning() 156 156 .get(); 157 157 } 158 158 159 159 const removed = _monitorsIds.filter( 160 - (monitor) => !opts.input.monitors?.includes(monitor) 160 + (monitor) => !opts.input.monitors?.includes(monitor), 161 161 ); 162 162 163 163 if (removed?.length) { ··· 166 166 .where( 167 167 and( 168 168 eq(maintenancesToMonitors.maintenanceId, _maintenance.id), 169 - inArray(maintenancesToMonitors.monitorId, removed) 170 - ) 169 + inArray(maintenancesToMonitors.monitorId, removed), 170 + ), 171 171 ) 172 172 .run(); 173 173 } ··· 182 182 .where( 183 183 and( 184 184 eq(maintenance.id, opts.input.id), 185 - eq(maintenance.workspaceId, opts.ctx.workspace.id) 186 - ) 185 + eq(maintenance.workspaceId, opts.ctx.workspace.id), 186 + ), 187 187 ) 188 188 .returning(); 189 189 }),
+36 -36
packages/api/src/router/monitor.ts
··· 43 43 await opts.ctx.db.query.monitor.findMany({ 44 44 where: and( 45 45 eq(monitor.workspaceId, opts.ctx.workspace.id), 46 - isNull(monitor.deletedAt) 46 + isNull(monitor.deletedAt), 47 47 ), 48 48 }) 49 49 ).length; ··· 106 106 const allNotifications = await opts.ctx.db.query.notification.findMany({ 107 107 where: and( 108 108 eq(notification.workspaceId, opts.ctx.workspace.id), 109 - inArray(notification.id, notifications) 109 + inArray(notification.id, notifications), 110 110 ), 111 111 }); 112 112 ··· 122 122 const allTags = await opts.ctx.db.query.monitorTag.findMany({ 123 123 where: and( 124 124 eq(monitorTag.workspaceId, opts.ctx.workspace.id), 125 - inArray(monitorTag.id, tags) 125 + inArray(monitorTag.id, tags), 126 126 ), 127 127 }); 128 128 ··· 138 138 const allPages = await opts.ctx.db.query.page.findMany({ 139 139 where: and( 140 140 eq(page.workspaceId, opts.ctx.workspace.id), 141 - inArray(page.id, pages) 141 + inArray(page.id, pages), 142 142 ), 143 143 }); 144 144 ··· 165 165 where: and( 166 166 eq(monitor.id, opts.input.id), 167 167 eq(monitor.workspaceId, opts.ctx.workspace.id), 168 - isNull(monitor.deletedAt) 168 + isNull(monitor.deletedAt), 169 169 ), 170 170 with: { 171 171 monitorTagsToMonitors: { with: { monitorTag: true } }, ··· 190 190 maintenance: _monitor?.maintenancesToMonitors.some( 191 191 (item) => 192 192 item.maintenance.from.getTime() <= Date.now() && 193 - item.maintenance.to.getTime() >= Date.now() 193 + item.maintenance.to.getTime() >= Date.now(), 194 194 ), 195 195 }); 196 196 ··· 212 212 where: and( 213 213 eq(monitor.id, opts.input.id), 214 214 isNull(monitor.deletedAt), 215 - eq(monitor.public, true) 215 + eq(monitor.public, true), 216 216 ), 217 217 }); 218 218 if (!_monitor) return undefined; ··· 224 224 }); 225 225 226 226 const hasPageRelation = _page?.monitorsToPages.find( 227 - ({ monitorId }) => _monitor.id === monitorId 227 + ({ monitorId }) => _monitor.id === monitorId, 228 228 ); 229 229 230 230 if (!hasPageRelation) return undefined; ··· 284 284 and( 285 285 eq(monitor.id, opts.input.id), 286 286 eq(monitor.workspaceId, opts.ctx.workspace.id), 287 - isNull(monitor.deletedAt) 288 - ) 287 + isNull(monitor.deletedAt), 288 + ), 289 289 ) 290 290 .returning() 291 291 .get(); ··· 300 300 (x) => 301 301 !currentMonitorNotifications 302 302 .map(({ notificationId }) => notificationId) 303 - ?.includes(x) 303 + ?.includes(x), 304 304 ); 305 305 306 306 if (addedNotifications.length > 0) { ··· 324 324 eq(notificationsToMonitors.monitorId, currentMonitor.id), 325 325 inArray( 326 326 notificationsToMonitors.notificationId, 327 - removedNotifications 328 - ) 329 - ) 327 + removedNotifications, 328 + ), 329 + ), 330 330 ) 331 331 .run(); 332 332 } ··· 341 341 (x) => 342 342 !currentMonitorTags 343 343 .map(({ monitorTagId }) => monitorTagId) 344 - ?.includes(x) 344 + ?.includes(x), 345 345 ); 346 346 347 347 if (addedTags.length > 0) { ··· 363 363 .where( 364 364 and( 365 365 eq(monitorTagsToMonitors.monitorId, currentMonitor.id), 366 - inArray(monitorTagsToMonitors.monitorTagId, removedTags) 367 - ) 366 + inArray(monitorTagsToMonitors.monitorTagId, removedTags), 367 + ), 368 368 ) 369 369 .run(); 370 370 } ··· 376 376 .all(); 377 377 378 378 const addedPages = pages.filter( 379 - (x) => !currentMonitorPages.map(({ pageId }) => pageId)?.includes(x) 379 + (x) => !currentMonitorPages.map(({ pageId }) => pageId)?.includes(x), 380 380 ); 381 381 382 382 if (addedPages.length > 0) { ··· 398 398 .where( 399 399 and( 400 400 eq(monitorsToPages.monitorId, currentMonitor.id), 401 - inArray(monitorsToPages.pageId, removedPages) 402 - ) 401 + inArray(monitorsToPages.pageId, removedPages), 402 + ), 403 403 ) 404 404 .run(); 405 405 } ··· 414 414 .where( 415 415 and( 416 416 eq(monitor.id, opts.input.id), 417 - eq(monitor.workspaceId, opts.ctx.workspace.id) 418 - ) 417 + eq(monitor.workspaceId, opts.ctx.workspace.id), 418 + ), 419 419 ) 420 420 .get(); 421 421 if (!monitorToDelete) return; ··· 446 446 const monitors = await opts.ctx.db.query.monitor.findMany({ 447 447 where: and( 448 448 eq(monitor.workspaceId, opts.ctx.workspace.id), 449 - isNull(monitor.deletedAt) 449 + isNull(monitor.deletedAt), 450 450 ), 451 451 with: { 452 452 monitorTagsToMonitors: { with: { monitorTag: true } }, ··· 459 459 monitorTagsToMonitors: z 460 460 .array(z.object({ monitorTag: selectMonitorTagSchema })) 461 461 .default([]), 462 - }) 462 + }), 463 463 ) 464 464 .parse(monitors); 465 465 }), ··· 470 470 const _page = await opts.ctx.db.query.page.findFirst({ 471 471 where: and( 472 472 eq(page.id, opts.input.id), 473 - eq(page.workspaceId, opts.ctx.workspace.id) 473 + eq(page.workspaceId, opts.ctx.workspace.id), 474 474 ), 475 475 }); 476 476 ··· 479 479 const monitors = await opts.ctx.db.query.monitor.findMany({ 480 480 where: and( 481 481 eq(monitor.workspaceId, opts.ctx.workspace.id), 482 - isNull(monitor.deletedAt) 482 + isNull(monitor.deletedAt), 483 483 ), 484 484 with: { 485 485 monitorTagsToMonitors: { with: { monitorTag: true } }, ··· 495 495 monitorTagsToMonitors: z 496 496 .array(z.object({ monitorTag: selectMonitorTagSchema })) 497 497 .default([]), 498 - }) 498 + }), 499 499 ) 500 500 .parse( 501 501 monitors.filter((monitor) => 502 502 monitor.monitorsToPages 503 503 .map(({ pageId }) => pageId) 504 - .includes(_page.id) 505 - ) 504 + .includes(_page.id), 505 + ), 506 506 ); 507 507 }), 508 508 ··· 516 516 and( 517 517 eq(monitor.id, opts.input.id), 518 518 eq(monitor.workspaceId, opts.ctx.workspace.id), 519 - isNull(monitor.deletedAt) 520 - ) 519 + isNull(monitor.deletedAt), 520 + ), 521 521 ) 522 522 .get(); 523 523 ··· 536 536 .where( 537 537 and( 538 538 eq(monitor.id, opts.input.id), 539 - eq(monitor.workspaceId, opts.ctx.workspace.id) 540 - ) 539 + eq(monitor.workspaceId, opts.ctx.workspace.id), 540 + ), 541 541 ) 542 542 .run(); 543 543 }), ··· 565 565 notification, 566 566 and( 567 567 eq(notificationsToMonitors.notificationId, notification.id), 568 - eq(notification.workspaceId, opts.ctx.workspace.id) 569 - ) 568 + eq(notification.workspaceId, opts.ctx.workspace.id), 569 + ), 570 570 ) 571 571 .where(eq(notificationsToMonitors.monitorId, opts.input.id)) 572 572 .all(); ··· 579 579 await opts.ctx.db.query.monitor.findMany({ 580 580 where: and( 581 581 eq(monitor.workspaceId, opts.ctx.workspace.id), 582 - isNull(monitor.deletedAt) 582 + isNull(monitor.deletedAt), 583 583 ), 584 584 }) 585 585 ).length;
+9 -9
packages/api/src/router/notification.ts
··· 10 10 } from "@openstatus/db/src/schema"; 11 11 import { getLimit } from "@openstatus/plans"; 12 12 13 + import { SchemaError } from "@openstatus/error"; 13 14 import { trackNewNotification } from "../analytics"; 14 15 import { createTRPCRouter, protectedProcedure } from "../trpc"; 15 - import { SchemaError } from "@openstatus/error"; 16 16 17 17 export const notificationRouter = createTRPCRouter({ 18 18 create: protectedProcedure ··· 22 22 23 23 const notificationLimit = getLimit( 24 24 opts.ctx.workspace.plan, 25 - "notification-channels" 25 + "notification-channels", 26 26 ); 27 27 28 28 const notificationNumber = ( ··· 83 83 .where( 84 84 and( 85 85 eq(notification.id, opts.input.id), 86 - eq(notification.workspaceId, opts.ctx.workspace.id) 87 - ) 86 + eq(notification.workspaceId, opts.ctx.workspace.id), 87 + ), 88 88 ) 89 89 .returning() 90 90 .get(); ··· 98 98 .where( 99 99 and( 100 100 eq(notification.id, opts.input.id), 101 - eq(notification.id, opts.input.id) 102 - ) 101 + eq(notification.id, opts.input.id), 102 + ), 103 103 ) 104 104 .run(); 105 105 }), ··· 114 114 and( 115 115 eq(notification.id, opts.input.id), 116 116 eq(notification.id, opts.input.id), 117 - eq(notification.workspaceId, opts.ctx.workspace.id) 118 - ) 117 + eq(notification.workspaceId, opts.ctx.workspace.id), 118 + ), 119 119 ) 120 120 .get(); 121 121 ··· 135 135 isNotificationLimitReached: protectedProcedure.query(async (opts) => { 136 136 const notificationLimit = getLimit( 137 137 opts.ctx.workspace.plan, 138 - "notification-channels" 138 + "notification-channels", 139 139 ); 140 140 const notificationNumbers = ( 141 141 await opts.ctx.db.query.notification.findMany({
+21 -21
packages/api/src/router/page.ts
··· 66 66 where: and( 67 67 inArray(monitor.id, monitorIds), 68 68 eq(monitor.workspaceId, opts.ctx.workspace.id), 69 - isNull(monitor.deletedAt) 69 + isNull(monitor.deletedAt), 70 70 ), 71 71 }); 72 72 ··· 96 96 const firstPage = await opts.ctx.db.query.page.findFirst({ 97 97 where: and( 98 98 eq(page.id, opts.input.id), 99 - eq(page.workspaceId, opts.ctx.workspace.id) 99 + eq(page.workspaceId, opts.ctx.workspace.id), 100 100 ), 101 101 with: { 102 102 monitorsToPages: { ··· 133 133 .where( 134 134 and( 135 135 eq(page.id, pageInput.id), 136 - eq(page.workspaceId, opts.ctx.workspace.id) 137 - ) 136 + eq(page.workspaceId, opts.ctx.workspace.id), 137 + ), 138 138 ) 139 139 .returning() 140 140 .get(); ··· 145 145 where: and( 146 146 inArray(monitor.id, monitorIds), 147 147 eq(monitor.workspaceId, opts.ctx.workspace.id), 148 - isNull(monitor.deletedAt) 148 + isNull(monitor.deletedAt), 149 149 ), 150 150 }); 151 151 ··· 174 174 .where( 175 175 and( 176 176 inArray(monitorsToPages.monitorId, removedMonitors), 177 - eq(monitorsToPages.pageId, currentPage.id) 178 - ) 177 + eq(monitorsToPages.pageId, currentPage.id), 178 + ), 179 179 ); 180 180 } 181 181 ··· 203 203 .where( 204 204 and( 205 205 eq(page.id, opts.input.id), 206 - eq(page.workspaceId, opts.ctx.workspace.id) 207 - ) 206 + eq(page.workspaceId, opts.ctx.workspace.id), 207 + ), 208 208 ) 209 209 .run(); 210 210 }), ··· 216 216 maintenancesToPages: { 217 217 where: and( 218 218 lte(maintenance.from, new Date()), 219 - gte(maintenance.to, new Date()) 219 + gte(maintenance.to, new Date()), 220 220 ), 221 221 }, 222 222 }, ··· 252 252 .leftJoin(monitor, eq(monitorsToPages.monitorId, monitor.id)) 253 253 .where( 254 254 // make sur only active monitors are returned! 255 - and(eq(monitorsToPages.pageId, result.id), eq(monitor.active, true)) 255 + and(eq(monitorsToPages.pageId, result.id), eq(monitor.active, true)), 256 256 ) 257 257 .all(); 258 258 259 259 const monitorsId = monitorsToPagesResult.map( 260 - ({ monitors_to_pages }) => monitors_to_pages.monitorId 260 + ({ monitors_to_pages }) => monitors_to_pages.monitorId, 261 261 ); 262 262 263 263 const monitorsToStatusReportResult = ··· 276 276 .all(); 277 277 278 278 const monitorStatusReportIds = monitorsToStatusReportResult.map( 279 - ({ statusReportId }) => statusReportId 279 + ({ statusReportId }) => statusReportId, 280 280 ); 281 281 282 282 const pageStatusReportIds = statusReportsToPagesResult.map( 283 - ({ statusReportId }) => statusReportId 283 + ({ statusReportId }) => statusReportId, 284 284 ); 285 285 286 286 const statusReportIds = Array.from( 287 - new Set([...monitorStatusReportIds, ...pageStatusReportIds]) 287 + new Set([...monitorStatusReportIds, ...pageStatusReportIds]), 288 288 ); 289 289 290 290 const statusReports = ··· 311 311 and( 312 312 inArray(monitor.id, monitorsId), 313 313 eq(monitor.active, true), 314 - isNull(monitor.deletedAt) 315 - ) // REMINDER: this is hardcoded 314 + isNull(monitor.deletedAt), 315 + ), // REMINDER: this is hardcoded 316 316 ) 317 317 .all() 318 318 : []; ··· 325 325 .where( 326 326 inArray( 327 327 incidentTable.monitorId, 328 - monitors.map((m) => m.id) 329 - ) 328 + monitors.map((m) => m.id), 329 + ), 330 330 ) 331 331 .all() 332 332 : []; ··· 365 365 // had filter on some words we want to keep for us 366 366 if ( 367 367 ["api", "app", "www", "docs", "checker", "time", "help"].includes( 368 - opts.input.slug 368 + opts.input.slug, 369 369 ) 370 370 ) { 371 371 return false; ··· 378 378 379 379 addCustomDomain: protectedProcedure 380 380 .input( 381 - z.object({ customDomain: z.string().toLowerCase(), pageId: z.number() }) 381 + z.object({ customDomain: z.string().toLowerCase(), pageId: z.number() }), 382 382 ) 383 383 .mutation(async (opts) => { 384 384 // TODO Add some check ?
+4 -4
packages/api/src/router/rum/index.ts
··· 4 4 5 5 const event = z.enum(["CLS", "FCP", "FID", "INP", "LCP", "TTFB"]); 6 6 7 - const RouteData = z.object({ 7 + const _RouteData = z.object({ 8 8 href: z.string(), 9 9 total_event: z.coerce.number(), 10 10 clsValue: z.number().optional(), ··· 19 19 .input( 20 20 z.object({ 21 21 event: event, 22 - }) 22 + }), 23 23 ) 24 - .query((opts) => { 24 + .query((_opts) => { 25 25 // FIXME: Use tb pipe instead 26 26 return null; 27 27 }), 28 28 29 - GetAggregatedPerPage: protectedProcedure.query((opts) => { 29 + GetAggregatedPerPage: protectedProcedure.query((_opts) => { 30 30 // FIXME: Use tb pipe instead 31 31 return null; 32 32 }),
+3 -3
packages/api/src/router/tinybird/index.ts
··· 29 29 url: z.string().url().optional(), 30 30 region: z.enum(flyRegions).optional(), 31 31 cronTimestamp: z.number().int().optional(), 32 - }) 32 + }), 33 33 ) 34 34 .query(async (opts) => { 35 35 return await tb.endpointResponseDetails("7d")(opts.input); ··· 52 52 dsn: z.string(), 53 53 path: z.string(), 54 54 period: z.enum(["24h", "7d", "30d"]), 55 - }) 55 + }), 56 56 ) 57 57 .query(async (opts) => { 58 58 return await tb.applicationRUMMetricsForPath()(opts.input); ··· 63 63 dsn: z.string(), 64 64 path: z.string(), 65 65 period: z.enum(["24h", "7d", "30d"]), 66 - }) 66 + }), 67 67 ) 68 68 .query(async (opts) => { 69 69 return await tb.applicationSessionMetricsPerPath()(opts.input);
+4 -4
packages/api/src/router/workspace.ts
··· 124 124 await opts.ctx.db.query.usersToWorkspaces.findFirst({ 125 125 where: and( 126 126 eq(usersToWorkspaces.userId, opts.ctx.user.id), 127 - eq(usersToWorkspaces.workspaceId, opts.ctx.workspace.id) 127 + eq(usersToWorkspaces.workspaceId, opts.ctx.workspace.id), 128 128 ), 129 129 }); 130 130 ··· 141 141 .where( 142 142 and( 143 143 eq(usersToWorkspaces.userId, opts.input.id), 144 - eq(usersToWorkspaces.workspaceId, opts.ctx.workspace.id) 145 - ) 144 + eq(usersToWorkspaces.workspaceId, opts.ctx.workspace.id), 145 + ), 146 146 ) 147 147 .run(); 148 148 }), ··· 154 154 await opts.ctx.db.query.usersToWorkspaces.findFirst({ 155 155 where: and( 156 156 eq(usersToWorkspaces.userId, opts.ctx.user.id), 157 - eq(usersToWorkspaces.workspaceId, opts.ctx.workspace.id) 157 + eq(usersToWorkspaces.workspaceId, opts.ctx.workspace.id), 158 158 ), 159 159 }); 160 160
+2 -2
packages/db/src/schema/applications/application.ts
··· 10 10 workspaceId: integer("workspace_id").references(() => workspace.id), 11 11 12 12 createdAt: integer("created_at", { mode: "timestamp" }).default( 13 - sql`(strftime('%s', 'now'))` 13 + sql`(strftime('%s', 'now'))`, 14 14 ), 15 15 updatedAt: integer("updated_at", { mode: "timestamp" }).default( 16 - sql`(strftime('%s', 'now'))` 16 + sql`(strftime('%s', 'now'))`, 17 17 ), 18 18 });
+6 -6
packages/db/src/schema/maintenances/maintenance.ts
··· 6 6 text, 7 7 } from "drizzle-orm/sqlite-core"; 8 8 9 + import { monitor } from "../monitors"; 9 10 import { page } from "../pages"; 10 11 import { workspace } from "../workspaces"; 11 - import { monitor } from "../monitors"; 12 12 13 13 export const maintenance = sqliteTable("maintenance", { 14 14 id: integer("id").primaryKey(), ··· 22 22 pageId: integer("page_id").references(() => page.id), 23 23 24 24 createdAt: integer("created_at", { mode: "timestamp" }).default( 25 - sql`(strftime('%s', 'now'))` 25 + sql`(strftime('%s', 'now'))`, 26 26 ), 27 27 updatedAt: integer("updated_at", { mode: "timestamp" }).default( 28 - sql`(strftime('%s', 'now'))` 28 + sql`(strftime('%s', 'now'))`, 29 29 ), 30 30 }); 31 31 ··· 39 39 .notNull() 40 40 .references(() => maintenance.id, { onDelete: "cascade" }), 41 41 createdAt: integer("created_at", { mode: "timestamp" }).default( 42 - sql`(strftime('%s', 'now'))` 42 + sql`(strftime('%s', 'now'))`, 43 43 ), 44 44 }, 45 45 (t) => ({ 46 46 pk: primaryKey(t.monitorId, t.maintenanceId), 47 - }) 47 + }), 48 48 ); 49 49 50 50 export const maintenancesToMonitorsRelations = relations( ··· 58 58 fields: [maintenancesToMonitors.maintenanceId], 59 59 references: [maintenance.id], 60 60 }), 61 - }) 61 + }), 62 62 ); 63 63 64 64 export const maintenanceRelations = relations(maintenance, ({ one, many }) => ({
+1 -1
packages/db/src/schema/maintenances/validation.ts
··· 1 1 import { createInsertSchema, createSelectSchema } from "drizzle-zod"; 2 - import { maintenance } from "./maintenance"; 3 2 import { z } from "zod"; 3 + import { maintenance } from "./maintenance"; 4 4 5 5 export const insertMaintenanceSchema = createInsertSchema(maintenance) 6 6 .extend({
+6 -6
packages/db/src/schema/monitors/monitor.ts
··· 6 6 text, 7 7 } from "drizzle-orm/sqlite-core"; 8 8 9 + import { maintenancesToMonitors } from "../maintenances"; 9 10 import { monitorTagsToMonitors } from "../monitor_tags"; 10 11 import { notificationsToMonitors } from "../notifications"; 11 12 import { page } from "../pages"; ··· 17 18 monitorPeriodicity, 18 19 monitorStatus, 19 20 } from "./constants"; 20 - import { maintenancesToMonitors } from "../maintenances"; 21 21 22 22 export const monitor = sqliteTable("monitor", { 23 23 id: integer("id").primaryKey(), ··· 47 47 public: integer("public", { mode: "boolean" }).default(false), 48 48 49 49 createdAt: integer("created_at", { mode: "timestamp" }).default( 50 - sql`(strftime('%s', 'now'))` 50 + sql`(strftime('%s', 'now'))`, 51 51 ), 52 52 updatedAt: integer("updated_at", { mode: "timestamp" }).default( 53 - sql`(strftime('%s', 'now'))` 53 + sql`(strftime('%s', 'now'))`, 54 54 ), 55 55 56 56 deletedAt: integer("deleted_at", { mode: "timestamp" }), ··· 78 78 .notNull() 79 79 .references(() => page.id, { onDelete: "cascade" }), 80 80 createdAt: integer("created_at", { mode: "timestamp" }).default( 81 - sql`(strftime('%s', 'now'))` 81 + sql`(strftime('%s', 'now'))`, 82 82 ), 83 83 order: integer("order").default(0), 84 84 }, 85 85 (t) => ({ 86 86 pk: primaryKey(t.monitorId, t.pageId), 87 - }) 87 + }), 88 88 ); 89 89 90 90 export const monitorsToPagesRelation = relations( ··· 98 98 fields: [monitorsToPages.pageId], 99 99 references: [page.id], 100 100 }), 101 - }) 101 + }), 102 102 );
+4 -4
packages/db/src/schema/pages/page.ts
··· 1 1 import { relations, sql } from "drizzle-orm"; 2 2 import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; 3 3 4 + import { maintenance } from "../maintenances"; 4 5 import { monitorsToPages } from "../monitors"; 5 6 import { pagesToStatusReports } from "../status_reports"; 6 7 import { workspace } from "../workspaces"; 7 - import { maintenance } from "../maintenances"; 8 8 9 9 export const page = sqliteTable("page", { 10 10 id: integer("id").primaryKey(), ··· 23 23 // Password protecting the status page - no specific restriction on password 24 24 password: text("password", { length: 256 }), 25 25 passwordProtected: integer("password_protected", { mode: "boolean" }).default( 26 - false 26 + false, 27 27 ), 28 28 29 29 createdAt: integer("created_at", { mode: "timestamp" }).default( 30 - sql`(strftime('%s', 'now'))` 30 + sql`(strftime('%s', 'now'))`, 31 31 ), 32 32 updatedAt: integer("updated_at", { mode: "timestamp" }).default( 33 - sql`(strftime('%s', 'now'))` 33 + sql`(strftime('%s', 'now'))`, 34 34 ), 35 35 }); 36 36
+8 -8
packages/db/src/schema/shared.ts
··· 1 1 import { z } from "zod"; 2 2 3 3 import { selectIncidentSchema } from "./incidents/validation"; 4 + import { 5 + maintenancesToMonitors, 6 + selectMaintenanceSchema, 7 + } from "./maintenances"; 4 8 import { selectMonitorSchema } from "./monitors"; 5 9 import { selectPageSchema } from "./pages"; 6 10 import { ··· 8 12 selectStatusReportUpdateSchema, 9 13 } from "./status_reports"; 10 14 import { workspacePlanSchema } from "./workspaces"; 11 - import { 12 - maintenancesToMonitors, 13 - selectMaintenanceSchema, 14 - } from "./maintenances"; 15 15 16 16 // TODO: create a 'public-status' schema with all the different types and validations 17 17 ··· 29 29 monitorId: z.number(), 30 30 statusReportId: z.number(), 31 31 monitor: selectPublicMonitorSchema, 32 - }) 32 + }), 33 33 ) 34 34 .default([]), 35 35 }); ··· 40 40 z.object({ 41 41 monitorId: z.number(), 42 42 maintenanceId: z.number(), 43 - }) 43 + }), 44 44 ) 45 45 .default([]), 46 46 }); ··· 59 59 pageId: z.number(), 60 60 order: z.number().default(0).optional(), 61 61 monitor: selectMonitorSchema, 62 - }) 62 + }), 63 63 ), 64 64 maintenancesToPages: selectMaintenanceSchema.array().default([]), 65 65 }); ··· 88 88 monitorId: z.number(), 89 89 statusReportId: z.number(), 90 90 monitor: selectPublicMonitorSchema, 91 - }) 91 + }), 92 92 ) 93 93 .default([]), 94 94 statusReportUpdates: z.array(selectStatusReportUpdateSchema),
+3 -3
packages/db/src/schema/workspaces/workspace.ts
··· 19 19 paidUntil: integer("paid_until", { mode: "timestamp" }), 20 20 21 21 createdAt: integer("created_at", { mode: "timestamp" }).default( 22 - sql`(strftime('%s', 'now'))` 22 + sql`(strftime('%s', 'now'))`, 23 23 ), 24 24 updatedAt: integer("updated_at", { mode: "timestamp" }).default( 25 - sql`(strftime('%s', 'now'))` 25 + sql`(strftime('%s', 'now'))`, 26 26 ), 27 27 28 28 dsn: text("dsn"), // should be removed soon 29 29 }, 30 30 (t) => ({ 31 31 unique: unique().on(t.id, t.dsn), 32 - }) 32 + }), 33 33 ); 34 34 35 35 export const workspaceRelations = relations(workspace, ({ many }) => ({
+1 -2
packages/emails/emails/followup.tsx
··· 26 26 <br /> 27 27 <br />⭐ Star us on{" "} 28 28 <Link href="https://github.com/openstatushq/openstatus">GitHub</Link> 29 - <br /> 30 - 🚀 Visit our website{" "} 29 + <br />🚀 Visit our website{" "} 31 30 <Link href="https://www.openstatus.dev">OpenStatus.dev</Link> 32 31 </Body> 33 32 </Head>
+2 -5
packages/emails/emails/welcome.tsx
··· 26 26 </a>{" "} 27 27 to manage your monitors 28 28 <br />- Integrate your status within your application with our{" "} 29 - <a href="https://docs.openstatus.dev/packages/status-widget"> 30 - API 31 - </a>{" "} 29 + <a href="https://docs.openstatus.dev/packages/status-widget">API</a>{" "} 32 30 and{" "} 33 31 <a href="https://docs.openstatus.dev/packages/react">React Widget</a> 34 32 <br />- Build your own status page with our{" "} ··· 50 48 <br /> 51 49 <br />⭐ Star us on{" "} 52 50 <Link href="https://github.com/openstatushq/openstatus">GitHub</Link> 53 - <br /> 54 - 🚀 Visit our website{" "} 51 + <br />🚀 Visit our website{" "} 55 52 <Link href="https://www.openstatus.dev">OpenStatus.dev</Link> 56 53 </Body> 57 54 </Head>
+2 -2
packages/error/src/base-error.ts
··· 3 3 type ErrorContext = Record<string, unknown>; 4 4 5 5 export abstract class BaseError< 6 - TContext extends ErrorContext = ErrorContext 6 + TContext extends ErrorContext = ErrorContext, 7 7 > extends Error { 8 8 public abstract readonly name: string; 9 9 /** ··· 35 35 return `${this.name}(${this.code}): ${ 36 36 this.message 37 37 } - caused by ${this.cause?.toString()} - with context ${JSON.stringify( 38 - this.context 38 + this.context, 39 39 )}`; 40 40 } 41 41
+2 -2
packages/error/src/utils.ts
··· 55 55 i.code === "invalid_union" 56 56 ? i.unionErrors.map((ue) => parseZodErrorIssues(ue.issues)).join("; ") 57 57 : i.code === "unrecognized_keys" 58 - ? i.message 59 - : `${i.path.length ? `${i.code} in '${i.path}': ` : ""}${i.message}` 58 + ? i.message 59 + : `${i.path.length ? `${i.code} in '${i.path}': ` : ""}${i.message}`, 60 60 ) 61 61 .join("; "); 62 62 }
+1 -1
packages/plans/src/utils.ts
··· 6 6 // TODO: use getLimit utils function 7 7 export function getLimit<T extends keyof Limits>( 8 8 plan: WorkspacePlan, 9 - limit: T 9 + limit: T, 10 10 ) { 11 11 return allPlans[plan].limits[limit]; 12 12 }
+2 -4
packages/react/src/widget.tsx
··· 45 45 <span className="relative flex h-2 w-2"> 46 46 {status === "operational" ? ( 47 47 <span 48 - className={`absolute inline-flex h-full w-full animate-ping rounded-full ${color} opacity-75 duration-1000`} 48 + className={`absolute inline-flex h-full w-full animate-ping rounded-full${color}opacity-75 duration-1000`} 49 49 /> 50 50 ) : null} 51 - <span 52 - className={`relative inline-flex h-2 w-2 rounded-full ${color}`} 53 - /> 51 + <span className={`relative inline-flex h-2 w-2 rounded-full${color}`} /> 54 52 </span> 55 53 </a> 56 54 );
+4 -4
packages/tinybird/src/os-client.ts
··· 16 16 17 17 const MIN_CACHE = isProd ? 60 : DEV_CACHE; // 60s 18 18 const DEFAULT_CACHE = isProd ? 120 : DEV_CACHE; // 2min 19 - const MAX_CACHE = 86_400; // 1d 19 + const _MAX_CACHE = 86_400; // 1d 20 20 21 21 const VERSION = "v1"; 22 22 ··· 118 118 opts?: { 119 119 cache?: RequestCache | undefined; 120 120 revalidate: number | undefined; 121 - } // RETHINK: not the best way to handle it 121 + }, // RETHINK: not the best way to handle it 122 122 ) => { 123 123 try { 124 124 const res = await this.tb.buildPipe({ ··· 178 178 179 179 endpointStatusPeriod( 180 180 period: "7d" | "45d", 181 - timezone: "UTC" = "UTC" // "EST" | "PST" | "CET" 181 + timezone: "UTC" = "UTC", // "EST" | "PST" | "CET" 182 182 ) { 183 183 const parameters = z.object({ monitorId: z.string() }); 184 184 ··· 187 187 opts?: { 188 188 cache?: RequestCache | undefined; 189 189 revalidate: number | undefined; 190 - } // RETHINK: not the best way to handle it 190 + }, // RETHINK: not the best way to handle it 191 191 ) => { 192 192 try { 193 193 const res = await this.tb.buildPipe({
+7 -7
packages/tracker/src/tracker.ts
··· 56 56 prev.count += curr.count; 57 57 return prev; 58 58 }, 59 - { count: 0, ok: 0 } 59 + { count: 0, ok: 0 }, 60 60 ); 61 61 } 62 62 ··· 80 80 private isOngoingReport() { 81 81 const resolved: StatusReport["status"][] = ["monitoring", "resolved"]; 82 82 return this.statusReports.some( 83 - (report) => !resolved.includes(report.status) 83 + (report) => !resolved.includes(report.status), 84 84 ); 85 85 } 86 86 ··· 152 152 private getStatusReportsByDay(props: Monitor): StatusReports { 153 153 const statusReports = this.statusReports?.filter((report) => { 154 154 const firstStatusReportUpdate = report?.statusReportUpdates?.sort( 155 - (a, b) => a.date.getTime() - b.date.getTime() 155 + (a, b) => a.date.getTime() - b.date.getTime(), 156 156 )?.[0]; 157 157 158 158 if (!firstStatusReportUpdate) return false; ··· 191 191 const status = maintenances.length 192 192 ? Status.UnderMaintenance 193 193 : incidents.length 194 - ? Status.Incident 195 - : isMissingData 196 - ? Status.Unknown 197 - : this.calculateUptimeStatus([props]); 194 + ? Status.Incident 195 + : isMissingData 196 + ? Status.Unknown 197 + : this.calculateUptimeStatus([props]); 198 198 199 199 const variant = statusDetails[status].variant; 200 200 const label = statusDetails[status].short;