Openstatus www.openstatus.dev

🔥 Add run api (#870)

* 🚧 checker api

* 🚧 single check

* 🚧 single check

* 🧪 fix test

* 🧪 fix tests

* 🧪 fix tests

* 🧪

* 🧪 fix tests

* 🧪 fix tests

* 🧪 add tests

* 🛂 pr

* 📝 format

authored by

Thibault Le Ouay and committed by
GitHub
5d867bb2 82ec6bf9

+3026 -60
+9 -21
apps/checker/cmd/main.go
··· 19 19 "github.com/openstatushq/openstatus/apps/checker/request" 20 20 "github.com/rs/zerolog/log" 21 21 22 - unkey "github.com/WilfredAlmeida/unkey-go/features" 23 22 backoff "github.com/cenkalti/backoff/v4" 24 23 ) 25 24 ··· 61 60 router := gin.New() 62 61 router.POST("/checker", func(c *gin.Context) { 63 62 ctx := c.Request.Context() 64 - 63 + dataSourceName := "ping_response__v8" 65 64 if c.GetHeader("Authorization") != fmt.Sprintf("Basic %s", cronSecret) { 66 65 c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) 67 66 return ··· 112 111 if err != nil { 113 112 // handle error 114 113 return fmt.Errorf("unable to unmarshal assertion: %w", err) 115 - 116 114 } 117 115 switch assert.AssertionType { 118 116 case request.AssertionHeader: ··· 183 181 }) 184 182 } 185 183 186 - if err := tinybirdClient.SendEvent(ctx, res); err != nil { 184 + if err := tinybirdClient.SendEvent(ctx, res, dataSourceName); err != nil { 187 185 log.Ctx(ctx).Error().Err(err).Msg("failed to send event to tinybird") 188 186 } 189 187 ··· 202 200 Error: 1, 203 201 Assertions: assertionAsString, 204 202 Body: "", 205 - }); err != nil { 203 + }, dataSourceName); err != nil { 206 204 log.Ctx(ctx).Error().Err(err).Msg("failed to send event to tinybird") 207 205 } 208 206 ··· 226 224 }) 227 225 228 226 router.POST("/ping/:region", func(c *gin.Context) { 227 + dataSourceName := "check_response__v1" 229 228 region := c.Param("region") 230 229 if region == "" { 231 230 c.String(http.StatusBadRequest, "region is required") ··· 233 232 } 234 233 fmt.Printf("Start of /ping/%s\n", region) 235 234 236 - apiKey := c.GetHeader("x-openstatus-key") 237 - 238 - if c.GetHeader("Authorization") != fmt.Sprintf("Basic %s", cronSecret) && apiKey == "" { 235 + if c.GetHeader("Authorization") != fmt.Sprintf("Basic %s", cronSecret) { 239 236 c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) 240 237 return 241 238 } 242 - if apiKey != "" { 243 - response, err := unkey.KeyVerify(apiKey) 244 - if err != nil { 245 - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) 246 - return 247 - } 248 - 249 - if !response.Valid { 250 - fmt.Println("Key is not valid valid") 251 - c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) 252 - 253 - } 254 - } 255 239 256 240 if region != flyRegion { 257 241 c.Header("fly-replay", fmt.Sprintf("region=%s", region)) ··· 275 259 r, err := checker.SinglePing(c.Request.Context(), requestClient, req) 276 260 if err != nil { 277 261 return fmt.Errorf("unable to ping: %w", err) 262 + } 263 + r.Region = flyRegion 264 + if err := tinybirdClient.SendEvent(ctx, res, dataSourceName); err != nil { 265 + log.Ctx(ctx).Error().Err(err).Msg("failed to send event to tinybird") 278 266 } 279 267 res = r 280 268 return nil
+1
apps/checker/ping.go
··· 57 57 Timing Timing `json:"timing"` 58 58 Error string `json:"error,omitempty"` 59 59 Tags []string `json:"tags,omitempty"` 60 + Region string `json:"region"` 60 61 } 61 62 62 63 func Ping(ctx context.Context, client *http.Client, inputData request.CheckerRequest) (PingData, error) {
+3 -3
apps/checker/pkg/tinybird/client.go
··· 14 14 const baseURL = "https://api.tinybird.co/v0/events" 15 15 16 16 type Client interface { 17 - SendEvent(ctx context.Context, event any) error 17 + SendEvent(ctx context.Context, event any, dataSourceName string) error 18 18 } 19 19 20 20 type client struct { ··· 29 29 } 30 30 } 31 31 32 - func (c client) SendEvent(ctx context.Context, event any) error { 32 + func (c client) SendEvent(ctx context.Context, event any, dataSourceName string) error { 33 33 requestURL, err := url.Parse(baseURL) 34 34 if err != nil { 35 35 log.Ctx(ctx).Error().Err(err).Msg("unable to parse url") ··· 37 37 } 38 38 39 39 q := requestURL.Query() 40 - q.Add("name", "ping_response__v8") 40 + q.Add("name", dataSourceName) 41 41 requestURL.RawQuery = q.Encode() 42 42 43 43 var payload bytes.Buffer
+4 -4
apps/checker/pkg/tinybird/client_test.go
··· 39 39 40 40 client := tinybird.NewClient(interceptor.GetHTTPClient(), "apiKey") 41 41 42 - err := client.SendEvent(ctx, "event") 42 + err := client.SendEvent(ctx, "event", "test") 43 43 require.Error(t, err) 44 44 }) 45 45 ··· 54 54 55 55 client := tinybird.NewClient(interceptor.GetHTTPClient(), "apiKey") 56 56 57 - err := client.SendEvent(ctx, "event") 57 + err := client.SendEvent(ctx, "event", "test") 58 58 require.Error(t, err) 59 59 }) 60 60 ··· 71 71 72 72 client := tinybird.NewClient(interceptor.GetHTTPClient(), "apiKey") 73 73 74 - err := client.SendEvent(ctx, "event") 74 + err := client.SendEvent(ctx, "event", "test") 75 75 require.NoError(t, err) 76 - require.Equal(t, "https://api.tinybird.co/v0/events?name=ping_response__v8", url) 76 + require.Equal(t, "https://api.tinybird.co/v0/events?name=test", url) 77 77 }) 78 78 }
+1
apps/checker/request/request.go
··· 59 59 } 60 60 61 61 type PingRequest struct { 62 + ID string `json:"id"` 62 63 URL string `json:"url"` 63 64 Method string `json:"method"` 64 65 Body string `json:"body"`
+4 -3
apps/server/package.json
··· 11 11 }, 12 12 "dependencies": { 13 13 "@hono/sentry": "1.1.0", 14 - "@hono/zod-openapi": "0.13.0", 15 - "@hono/zod-validator": "0.2.1", 14 + "@hono/zod-openapi": "0.14.2", 15 + "@hono/zod-validator": "0.2.2", 16 16 "@openstatus/analytics": "workspace:^", 17 17 "@openstatus/db": "workspace:*", 18 18 "@openstatus/emails": "workspace:*", ··· 29 29 "@t3-oss/env-core": "0.7.1", 30 30 "@unkey/api": "0.16.0", 31 31 "@upstash/qstash": "2.1.8", 32 - "hono": "4.0.0", 32 + "hono": "4.4.5", 33 33 "nanoid": "5.0.2", 34 + "percentile": "^1.6.0", 34 35 "validator": "13.11.0", 35 36 "zod": "3.22.4" 36 37 },
+14
apps/server/src/v1/check/index.ts
··· 1 + import { OpenAPIHono } from "@hono/zod-openapi"; 2 + 3 + import type { Variables } from "../index"; 4 + 5 + import { handleZodError } from "../../libs/errors"; 6 + import { registerPostCheck } from "./post"; 7 + 8 + const checkAPI = new OpenAPIHono<{ Variables: Variables }>({ 9 + defaultHook: handleZodError, 10 + }); 11 + 12 + registerPostCheck(checkAPI); 13 + 14 + export { checkAPI };
+152
apps/server/src/v1/check/post.test.ts
··· 1 + import { expect, test } from "bun:test"; 2 + 3 + import { api } from "../index"; 4 + 5 + import { afterEach, mock } from "bun:test"; 6 + 7 + const mockFetch = mock(); 8 + 9 + global.fetch = mockFetch; 10 + mock.module("node-fetch", () => mockFetch); 11 + 12 + afterEach(() => { 13 + mockFetch.mockReset(); 14 + }); 15 + 16 + test("Create a single check ", async () => { 17 + const data = { 18 + url: "https://www.openstatus.dev", 19 + regions: ["ams"], 20 + method: "POST", 21 + body: '{"hello":"world"}', 22 + headers: [{ key: "key", value: "value" }], 23 + }; 24 + mockFetch.mockReturnValue( 25 + Promise.resolve( 26 + new Response( 27 + '{"status":200,"latency":100,"body":"Hello World","headers":{"Content-Type":"application/json"},"timestamp":1234567890,"timing":{"dnsStart":1,"dnsDone":2,"connectStart":3,"connectDone":4,"tlsHandshakeStart":5,"tlsHandshakeDone":6,"firstByteStart":7,"firstByteDone":8,"transferStart":9,"transferDone":10},"region":"ams"}', 28 + { status: 200, headers: { "content-type": "application/json" } }, 29 + ), 30 + ), 31 + ); 32 + 33 + const res = await api.request("/check", { 34 + method: "POST", 35 + headers: { 36 + "x-openstatus-key": "1", 37 + "content-type": "application/json", 38 + }, 39 + body: JSON.stringify(data), 40 + }); 41 + 42 + expect(res.status).toBe(200); 43 + 44 + expect(await res.json()).toMatchObject({ 45 + id: expect.any(Number), 46 + raw: [ 47 + { 48 + connectDone: 4, 49 + connectStart: 3, 50 + dnsDone: 2, 51 + dnsStart: 1, 52 + firstByteDone: 8, 53 + firstByteStart: 7, 54 + tlsHandshakeDone: 6, 55 + tlsHandshakeStart: 5, 56 + transferDone: 10, 57 + transferStart: 9, 58 + }, 59 + ], 60 + response: { 61 + body: "Hello World", 62 + headers: { 63 + "Content-Type": "application/json", 64 + }, 65 + latency: 100, 66 + region: "ams", 67 + status: 200, 68 + timestamp: 1234567890, 69 + timing: { 70 + connectDone: 4, 71 + connectStart: 3, 72 + dnsDone: 2, 73 + dnsStart: 1, 74 + firstByteDone: 8, 75 + firstByteStart: 7, 76 + tlsHandshakeDone: 6, 77 + tlsHandshakeStart: 5, 78 + transferDone: 10, 79 + transferStart: 9, 80 + }, 81 + }, 82 + }); 83 + }); 84 + 85 + test.todo("Create a multiple check ", async () => { 86 + const data = { 87 + url: "https://www.openstatus.dev", 88 + regions: ["ams", "gru"], 89 + method: "POST", 90 + body: '{"hello":"world"}', 91 + headers: [{ key: "key", value: "value" }], 92 + }; 93 + mockFetch.mockReturnValue( 94 + Promise.resolve( 95 + new Response( 96 + '{"status":200,"latency":100,"body":"Hello World","headers":{"Content-Type":"application/json"},"timestamp":1234567890,"timing":{"dnsStart":1,"dnsDone":2,"connectStart":3,"connectDone":4,"tlsHandshakeStart":5,"tlsHandshakeDone":6,"firstByteStart":7,"firstByteDone":8,"transferStart":9,"transferDone":10},"region":"ams"}', 97 + { status: 200, headers: { "content-type": "application/json" } }, 98 + ), 99 + ), 100 + ); 101 + 102 + const res = await api.request("/check", { 103 + method: "POST", 104 + headers: { 105 + "x-openstatus-key": "1", 106 + "content-type": "application/json", 107 + }, 108 + body: JSON.stringify(data), 109 + }); 110 + 111 + expect(res.status).toBe(200); 112 + 113 + expect(await res.json()).toMatchObject({ 114 + id: expect.any(Number), 115 + raw: [ 116 + { 117 + connectDone: 4, 118 + connectStart: 3, 119 + dnsDone: 2, 120 + dnsStart: 1, 121 + firstByteDone: 8, 122 + firstByteStart: 7, 123 + tlsHandshakeDone: 6, 124 + tlsHandshakeStart: 5, 125 + transferDone: 10, 126 + transferStart: 9, 127 + }, 128 + ], 129 + response: { 130 + body: "Hello World", 131 + headers: { 132 + "Content-Type": "application/json", 133 + }, 134 + latency: 100, 135 + region: "ams", 136 + status: 200, 137 + timestamp: 1234567890, 138 + timing: { 139 + connectDone: 4, 140 + connectStart: 3, 141 + dnsDone: 2, 142 + dnsStart: 1, 143 + firstByteDone: 8, 144 + firstByteStart: 7, 145 + tlsHandshakeDone: 6, 146 + tlsHandshakeStart: 5, 147 + transferDone: 10, 148 + transferStart: 9, 149 + }, 150 + }, 151 + }); 152 + });
+226
apps/server/src/v1/check/post.ts
··· 1 + import { createRoute, z } from "@hono/zod-openapi"; 2 + 3 + import { db } from "@openstatus/db"; 4 + import { check } from "@openstatus/db/src/schema/check"; 5 + import percentile from "percentile"; 6 + import { env } from "../../env"; 7 + import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 8 + import type { checkAPI } from "./index"; 9 + import { 10 + AggregatedResponseSchema, 11 + CheckPostResponseSchema, 12 + CheckSchema, 13 + ResponseSchema, 14 + } from "./schema"; 15 + 16 + const postRoute = createRoute({ 17 + method: "post", 18 + tags: ["page"], 19 + description: "Run a single check", 20 + path: "/", 21 + request: { 22 + body: { 23 + description: "The run request to create", 24 + content: { 25 + "application/json": { 26 + schema: CheckSchema, 27 + }, 28 + }, 29 + }, 30 + }, 31 + responses: { 32 + 200: { 33 + content: { 34 + "application/json": { 35 + schema: CheckPostResponseSchema, 36 + }, 37 + }, 38 + description: "Return a run result", 39 + }, 40 + ...openApiErrorResponses, 41 + }, 42 + }); 43 + 44 + export function registerPostCheck(api: typeof checkAPI) { 45 + return api.openapi(postRoute, async (c) => { 46 + const data = c.req.valid("json"); 47 + const workspaceId = c.get("workspaceId"); 48 + const input = c.req.valid("json"); 49 + 50 + const { headers, regions, runCount, aggregated, ...rest } = data; 51 + 52 + const newCheck = await db 53 + .insert(check) 54 + .values({ 55 + workspaceId: Number(workspaceId), 56 + regions: regions.join(","), 57 + countRequests: runCount, 58 + ...rest, 59 + }) 60 + .returning() 61 + .get(); 62 + const result = []; 63 + for (let count = 0; count < input.runCount; count++) { 64 + const currentFetch = []; 65 + for (const region of input.regions) { 66 + const r = fetch(`https://checker.openstatus.dev/ping/${region}`, { 67 + headers: { 68 + Authorization: `Basic ${env.CRON_SECRET}`, 69 + "Content-Type": "application/json", 70 + "fly-prefer-region": region, 71 + }, 72 + method: "POST", 73 + body: JSON.stringify({ 74 + checkId: newCheck.id, 75 + workspaceId: workspaceId, 76 + url: input.url, 77 + method: input.method, 78 + headers: input.headers?.reduce((acc, { key, value }) => { 79 + if (!key) return acc; // key === "" is an invalid header 80 + 81 + return { 82 + // biome-ignore lint/performance/noAccumulatingSpread: <explanation> 83 + ...acc, 84 + [key]: value, 85 + }; 86 + }, {}), 87 + body: input.body ? input.body : undefined, 88 + }), 89 + }); 90 + currentFetch.push(r); 91 + } 92 + 93 + const allResults = await Promise.allSettled(currentFetch); 94 + result.push(...allResults); 95 + } 96 + console.log(result); 97 + const filteredResult = result.filter((r) => r.status === "fulfilled"); 98 + const fulfilledRequest = []; 99 + for await (const r of filteredResult) { 100 + if (r.status !== "fulfilled") throw new Error("No value"); 101 + 102 + const json = await r.value.json(); 103 + console.log(ResponseSchema.safeParse(json)); 104 + fulfilledRequest.push(ResponseSchema.parse(json)); 105 + } 106 + 107 + let aggregatedResponse = null; 108 + if (aggregated) { 109 + // This is ugly 110 + const dnsArray = fulfilledRequest.map( 111 + (r) => r.timing.dnsDone - r.timing.dnsStart, 112 + ); 113 + const connectArray = fulfilledRequest.map( 114 + (r) => r.timing.connectDone - r.timing.connectStart, 115 + ); 116 + const tlsArray = fulfilledRequest.map( 117 + (r) => r.timing.tlsHandshakeDone - r.timing.tlsHandshakeStart, 118 + ); 119 + const firstArray = fulfilledRequest.map( 120 + (r) => r.timing.firstByteDone - r.timing.firstByteStart, 121 + ); 122 + const transferArray = fulfilledRequest.map( 123 + (r) => r.timing.transferDone - r.timing.transferStart, 124 + ); 125 + const latencyArray = fulfilledRequest.map((r) => r.latency); 126 + 127 + const dnsPercentile = percentile([50, 75, 95, 99], dnsArray) as number[]; 128 + const connectPercentile = percentile( 129 + [50, 75, 95, 99], 130 + connectArray, 131 + ) as number[]; 132 + const tlsPercentile = percentile([50, 75, 95, 99], tlsArray) as number[]; 133 + const firstPercentile = percentile( 134 + [50, 75, 95, 99], 135 + firstArray, 136 + ) as number[]; 137 + 138 + const transferPercentile = percentile( 139 + [50, 75, 95, 99], 140 + transferArray, 141 + ) as number[]; 142 + const latencyPercentile = percentile( 143 + [50, 75, 95, 99], 144 + latencyArray, 145 + ) as number[]; 146 + 147 + const aggregate = z.object({ 148 + dms: AggregatedResponseSchema, 149 + connect: AggregatedResponseSchema, 150 + tls: AggregatedResponseSchema, 151 + firstByte: AggregatedResponseSchema, 152 + transfert: AggregatedResponseSchema, 153 + latency: AggregatedResponseSchema, 154 + }); 155 + const aggregatedDNS = AggregatedResponseSchema.parse({ 156 + p50: dnsPercentile[0], 157 + p75: dnsPercentile[1], 158 + p95: dnsPercentile[2], 159 + p99: dnsPercentile[3], 160 + min: Math.min(...dnsArray), 161 + max: Math.max(...dnsArray), 162 + }); 163 + const aggregatedConnect = AggregatedResponseSchema.parse({ 164 + p50: connectPercentile[0], 165 + p75: connectPercentile[1], 166 + p95: connectPercentile[2], 167 + p99: connectPercentile[3], 168 + min: Math.min(...connectArray), 169 + max: Math.max(...connectArray), 170 + }); 171 + const aggregatedTls = AggregatedResponseSchema.parse({ 172 + p50: tlsPercentile[0], 173 + p75: tlsPercentile[1], 174 + p95: tlsPercentile[2], 175 + p99: tlsPercentile[3], 176 + min: Math.min(...tlsArray), 177 + max: Math.max(...tlsArray), 178 + }); 179 + const aggregatedFirst = AggregatedResponseSchema.parse({ 180 + p50: firstPercentile[0], 181 + p75: firstPercentile[1], 182 + p95: firstPercentile[2], 183 + p99: firstPercentile[3], 184 + min: Math.min(...firstArray), 185 + max: Math.max(...firstArray), 186 + }); 187 + const aggregatedTransfer = AggregatedResponseSchema.parse({ 188 + p50: transferPercentile[0], 189 + p75: transferPercentile[1], 190 + p95: transferPercentile[2], 191 + p99: transferPercentile[3], 192 + min: Math.min(...transferArray), 193 + max: Math.max(...transferArray), 194 + }); 195 + 196 + const aggregatedLatency = AggregatedResponseSchema.parse({ 197 + p50: latencyPercentile[0], 198 + p75: latencyPercentile[1], 199 + p95: latencyPercentile[2], 200 + p99: latencyPercentile[3], 201 + min: Math.min(...latencyArray), 202 + max: Math.max(...latencyArray), 203 + }); 204 + 205 + aggregatedResponse = aggregate.parse({ 206 + dns: aggregatedDNS, 207 + connection: aggregatedConnect, 208 + tls: aggregatedTls, 209 + firstByte: aggregatedFirst, 210 + transfer: aggregatedTransfer, 211 + latency: aggregatedLatency, 212 + }); 213 + } 214 + const allTimings = fulfilledRequest.map((r) => r.timing); 215 + 216 + const lastResponse = fulfilledRequest[fulfilledRequest.length - 1]; 217 + const responseResult = CheckPostResponseSchema.parse({ 218 + id: newCheck.id, 219 + raw: allTimings, 220 + response: lastResponse, 221 + aggregated: aggregatedResponse, 222 + }); 223 + 224 + return c.json(responseResult); 225 + }); 226 + }
+131
apps/server/src/v1/check/schema.ts
··· 1 + import { z } from "zod"; 2 + import { MonitorSchema } from "../monitors/schema"; 3 + 4 + export const CheckSchema = MonitorSchema.pick({ 5 + url: true, 6 + body: true, 7 + headers: true, 8 + method: true, 9 + regions: true, 10 + }) 11 + .extend({ 12 + runCount: z 13 + .number() 14 + .optional() 15 + .default(1) 16 + .openapi({ description: "The number of times to run the check" }), 17 + aggregated: z 18 + .boolean() 19 + .optional() 20 + .openapi({ description: "Whether to aggregate the results or not" }), 21 + // webhook: z 22 + // .string() 23 + // .optional() 24 + // .openapi({ description: "The webhook to send the result to" }), 25 + }) 26 + .openapi({ 27 + description: "The check request", 28 + }); 29 + 30 + export const TimingSchema = z.object({ 31 + dnsStart: z 32 + .number() 33 + .openapi({ description: "DNS timestamp start time in UTC " }), 34 + dnsDone: z 35 + .number() 36 + .openapi({ description: "DNS timestamp end time in UTC " }), 37 + connectStart: z 38 + .number() 39 + .openapi({ description: "Connect timestamp start time in UTC " }), 40 + connectDone: z 41 + .number() 42 + .openapi({ description: "Connect timestamp end time in UTC " }), 43 + tlsHandshakeStart: z 44 + .number() 45 + .openapi({ description: "TLS handshake timestamp start time in UTC " }), 46 + tlsHandshakeDone: z 47 + .number() 48 + .openapi({ description: "TLS handshake timestamp end time in UTC " }), 49 + firstByteStart: z 50 + .number() 51 + .openapi({ description: "First byte timestamp start time in UTC " }), 52 + firstByteDone: z 53 + .number() 54 + .openapi({ description: "First byte timestamp end time in UTC " }), 55 + transferStart: z 56 + .number() 57 + .openapi({ description: "Transfer timestamp start time in UTC " }), 58 + transferDone: z 59 + .number() 60 + .openapi({ description: "Transfer timestamp end time in UTC " }), 61 + }); 62 + 63 + export const AggregatedResponseSchema = z 64 + .object({ 65 + p50: z.number().openapi({ description: "The 50th percentile" }), 66 + p75: z.number().openapi({ description: "The 75th percentile" }), 67 + p95: z.number().openapi({ description: "The 95th percentile" }), 68 + p99: z.number().openapi({ description: "The 99th percentile" }), 69 + min: z.number().openapi({ description: "The minimum value" }), 70 + max: z.number().openapi({ description: "The maximum value" }), 71 + }) 72 + .openapi({ 73 + description: "The aggregated data of the check", 74 + }); 75 + 76 + export const ResponseSchema = z.object({ 77 + timestamp: z 78 + .number() 79 + .openapi({ description: "The timestamp of the response in UTC" }), 80 + status: z 81 + .number() 82 + .openapi({ description: "The status code of the response" }), 83 + latency: z.number().openapi({ description: "The latency of the response" }), 84 + body: z 85 + .string() 86 + .optional() 87 + .openapi({ description: "The body of the response" }), 88 + headers: z 89 + .record(z.string()) 90 + .optional() 91 + .openapi({ description: "The headers of the response" }), 92 + timing: TimingSchema.openapi({ 93 + description: "The timing metrics of the response", 94 + }), 95 + aggregated: z 96 + .object({ 97 + dns: AggregatedResponseSchema.openapi({ 98 + description: "The aggregated DNS timing of the check", 99 + }), 100 + connection: AggregatedResponseSchema.openapi({ 101 + description: "The aggregated connection timing of the check", 102 + }), 103 + tls: AggregatedResponseSchema.openapi({ 104 + description: "The aggregated tls timing of the check", 105 + }), 106 + firstByte: AggregatedResponseSchema.openapi({ 107 + description: "The aggregated first byte timing of the check", 108 + }), 109 + transfer: AggregatedResponseSchema.openapi({ 110 + description: "The aggregated transfer timing of the check", 111 + }), 112 + latency: AggregatedResponseSchema.openapi({ 113 + description: "The aggregated latency timing of the check", 114 + }), 115 + }) 116 + .optional() 117 + .openapi({ 118 + description: "The aggregated data dns timing of the check", 119 + }), 120 + region: z.string().openapi({ description: "The region where the check ran" }), 121 + }); 122 + 123 + export const CheckPostResponseSchema = z.object({ 124 + id: z.number().int().openapi({ description: "The id of the check" }), 125 + raw: z.array(TimingSchema).openapi({ 126 + description: "The raw data of the check", 127 + }), 128 + response: ResponseSchema.openapi({ 129 + description: "The last response of the check", 130 + }), 131 + });
+2
apps/server/src/v1/index.ts
··· 5 5 import type { Limits } from "@openstatus/plans/src/types"; 6 6 7 7 import { handleError, handleZodError } from "../libs/errors"; 8 + import { checkAPI } from "./check"; 8 9 import { incidentsApi } from "./incidents"; 9 10 import { middleware } from "./middleware"; 10 11 import { monitorsApi } from "./monitors"; ··· 56 57 api.route("/page_subscriber", pageSubscribersApi); 57 58 api.route("/status_report", statusReportsApi); 58 59 api.route("/status_report_update", statusReportUpdatesApi); 60 + api.route("/check", checkAPI);
+47
apps/server/src/v1/monitors/monitors.test.ts
··· 24 24 headers: [{ key: "key", value: "value" }], 25 25 active: true, 26 26 public: false, 27 + assertions: null, 27 28 }); 28 29 }); 29 30 ··· 63 64 headers: [{ key: "key", value: "value" }], 64 65 active: true, 65 66 public: true, 67 + assertions: [ 68 + { 69 + type: "status", 70 + compare: "eq", 71 + target: 200, 72 + }, 73 + { type: "header", compare: "not_eq", key: "key", target: "value" }, 74 + ], 66 75 }; 67 76 77 + const res = await api.request("/monitor", { 78 + method: "POST", 79 + headers: { 80 + "x-openstatus-key": "1", 81 + "content-type": "application/json", 82 + }, 83 + body: JSON.stringify(data), 84 + }); 85 + 86 + expect(res.status).toBe(200); 87 + 88 + expect(await res.json()).toMatchObject({ 89 + id: expect.any(Number), 90 + ...data, 91 + }); 92 + }); 93 + 94 + test("Create a monitor with Assertion ", async () => { 95 + const data = { 96 + periodicity: "10m", 97 + url: "https://www.openstatus.dev", 98 + name: "OpenStatus", 99 + description: "OpenStatus website", 100 + regions: ["ams", "gru", "iad"], 101 + method: "POST", 102 + body: '{"hello":"world"}', 103 + headers: [{ key: "key", value: "value" }], 104 + active: true, 105 + public: true, 106 + assertions: [ 107 + { 108 + type: "status", 109 + compare: "eq", 110 + target: 200, 111 + }, 112 + { type: "header", compare: "not_eq", key: "key", target: "value" }, 113 + ], 114 + }; 68 115 const res = await api.request("/monitor", { 69 116 method: "POST", 70 117 headers: {
+9 -3
apps/server/src/v1/monitors/post.ts
··· 5 5 import { monitor } from "@openstatus/db/src/schema"; 6 6 7 7 import { HTTPException } from "hono/http-exception"; 8 + import { serialize } from "../../../../../packages/assertions/src"; 9 + 8 10 import { env } from "../../env"; 9 11 import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 10 12 import type { monitorsApi } from "./index"; 11 13 import { MonitorSchema } from "./schema"; 14 + import { getAssertions } from "./utils"; 12 15 13 16 const postRoute = createRoute({ 14 17 method: "post", ··· 42 45 return api.openapi(postRoute, async (c) => { 43 46 const workspaceId = c.get("workspaceId"); 44 47 const workspacePlan = c.get("workspacePlan"); 45 - const input = c.req.valid("json"); 46 48 49 + const input = c.req.valid("json"); 47 50 const count = ( 48 51 await db 49 52 .select({ count: sql<number>`count(*)` }) ··· 67 70 throw new HTTPException(403, { message: "Forbidden" }); 68 71 } 69 72 70 - const { headers, regions, ...rest } = input; 73 + const { headers, regions, assertions, ...rest } = input; 74 + 75 + const assert = assertions ? getAssertions(assertions) : []; 76 + 71 77 const _newMonitor = await db 72 78 .insert(monitor) 73 79 .values({ ··· 75 81 workspaceId: Number(workspaceId), 76 82 regions: regions ? regions.join(",") : undefined, 77 83 headers: input.headers ? JSON.stringify(input.headers) : undefined, 84 + assertions: assert.length > 0 ? serialize(assert) : undefined, 78 85 }) 79 86 .returning() 80 87 .get(); 81 - 82 88 if (env.JITSU_WRITE_KEY) { 83 89 trackAnalytics({ 84 90 event: "Monitor Created",
+6 -1
apps/server/src/v1/monitors/put.ts
··· 4 4 import { monitor } from "@openstatus/db/src/schema"; 5 5 6 6 import { HTTPException } from "hono/http-exception"; 7 + import { serialize } from "../../../../../packages/assertions/src/serializing"; 7 8 import { openApiErrorResponses } from "../../libs/errors/openapi-error-responses"; 8 9 import type { monitorsApi } from "./index"; 9 10 import { MonitorSchema, ParamsSchema } from "./schema"; 11 + import { getAssertions } from "./utils"; 10 12 11 13 const putRoute = createRoute({ 12 14 method: "put", ··· 62 64 throw new HTTPException(401, { message: "Unauthorized" }); 63 65 } 64 66 65 - const { headers, regions, ...rest } = input; 67 + const { headers, regions, assertions, ...rest } = input; 68 + 69 + const assert = assertions ? getAssertions(assertions) : []; 66 70 67 71 const _newMonitor = await db 68 72 .update(monitor) ··· 70 74 ...rest, 71 75 regions: regions ? regions.join(",") : undefined, 72 76 headers: input.headers ? JSON.stringify(input.headers) : undefined, 77 + assertions: assert.length > 0 ? serialize(assert) : undefined, 73 78 }) 74 79 .returning() 75 80 .get();
+82
apps/server/src/v1/monitors/schema.ts
··· 6 6 monitorPeriodicitySchema, 7 7 } from "@openstatus/db/src/schema"; 8 8 import { ZodError } from "zod"; 9 + import { 10 + numberCompare, 11 + stringCompare, 12 + } from "../../../../../packages/assertions/src/v1"; 13 + 14 + const statusAssertion = z 15 + .object({ 16 + type: z.literal("status"), 17 + compare: numberCompare.openapi({ 18 + description: "The comparison to run", 19 + example: "eq", 20 + }), 21 + target: z 22 + .number() 23 + .int() 24 + .positive() 25 + .openapi({ description: "The target value" }), 26 + }) 27 + .openapi({ 28 + description: "The status assertion", 29 + }); 30 + 31 + const headerAssertion = z 32 + .object({ 33 + type: z.literal("header"), 34 + compare: stringCompare, 35 + key: z.string().openapi({ 36 + description: "The key of the header", 37 + }), 38 + target: z.string().openapi({ 39 + description: "the header value", 40 + }), 41 + }) 42 + .openapi({ description: "The header assertion" }); 43 + 44 + const textBodyAssertion = z 45 + .object({ 46 + type: z.literal("textBody"), 47 + compare: stringCompare, 48 + target: z.string().openapi({ 49 + description: "The target value", 50 + }), 51 + }) 52 + .openapi({ description: "The text body assertion" }); 53 + 54 + // Not used yet 55 + const _jsonBodyAssertion = z.object({ 56 + type: z.literal("jsonBody"), 57 + path: z.string(), // https://www.npmjs.com/package/jsonpath-plus 58 + compare: stringCompare, 59 + target: z.string(), 60 + }); 61 + 62 + export const assertion = z.discriminatedUnion("type", [ 63 + statusAssertion, 64 + headerAssertion, 65 + textBodyAssertion, 66 + // jsonBodyAssertion, 67 + ]); 9 68 10 69 export const ParamsSchema = z.object({ 11 70 id: z ··· 108 167 .openapi({ 109 168 description: "The headers of your request", 110 169 example: [{ key: "x-apikey", value: "supersecrettoken" }], 170 + }), 171 + assertions: z 172 + .preprocess((val) => { 173 + try { 174 + if (Array.isArray(val)) return val; 175 + if (String(val).length > 0) { 176 + return JSON.parse(String(val)); 177 + } 178 + return []; 179 + } catch (e) { 180 + throw new ZodError([ 181 + { 182 + code: "custom", 183 + path: ["assertions"], 184 + message: e instanceof Error ? e.message : "Invalid value", 185 + }, 186 + ]); 187 + } 188 + }, z.array(assertion)) 189 + .nullish() 190 + .default([]) 191 + .openapi({ 192 + description: "The assertions to run", 111 193 }), 112 194 active: z 113 195 .boolean()
+27
apps/server/src/v1/monitors/utils.ts
··· 1 + import type { z } from "zod"; 2 + import type { Assertion } from "../../../../../packages/assertions/src"; 3 + import { 4 + HeaderAssertion, 5 + StatusAssertion, 6 + TextBodyAssertion, 7 + } from "../../../../../packages/assertions/src/v1"; 8 + import type { assertion } from "./schema"; 9 + 10 + export const getAssertions = ( 11 + assertions: z.infer<typeof assertion>[], 12 + ): Assertion[] => { 13 + const assert: Assertion[] = []; 14 + 15 + for (const a of assertions) { 16 + if (a.type === "header") { 17 + assert.push(new HeaderAssertion({ ...a, version: "v1" })); 18 + } 19 + if (a.type === "textBody") { 20 + assert.push(new TextBodyAssertion({ ...a, version: "v1" })); 21 + } 22 + if (a.type === "status") { 23 + assert.push(new StatusAssertion({ ...a, version: "v1" })); 24 + } 25 + } 26 + return assert; 27 + };
+1 -1
packages/assertions/src/v1.ts
··· 161 161 162 162 export const base = z 163 163 .object({ 164 - version: z.enum(["v1"]), 164 + version: z.enum(["v1"]).default("v1"), 165 165 type: z.string(), 166 166 }) 167 167 .passthrough();
+12
packages/db/drizzle/0032_hot_swordsman.sql
··· 1 + CREATE TABLE `check` ( 2 + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, 3 + `regions` text DEFAULT '' NOT NULL, 4 + `url` text(4096) NOT NULL, 5 + `headers` text DEFAULT '', 6 + `body` text DEFAULT '', 7 + `method` text DEFAULT 'GET', 8 + `count_requests` integer DEFAULT 1, 9 + `workspace_id` integer, 10 + `created_at` integer DEFAULT (strftime('%s', 'now')), 11 + FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE no action ON DELETE no action 12 + );
+2217
packages/db/drizzle/meta/0032_snapshot.json
··· 1 + { 2 + "version": "6", 3 + "dialect": "sqlite", 4 + "id": "de3d58e5-d801-4b9e-a35a-39cfc09b7807", 5 + "prevId": "afc5bf87-9bfd-4753-af9f-edbf7b3351ba", 6 + "tables": { 7 + "status_report_to_monitors": { 8 + "name": "status_report_to_monitors", 9 + "columns": { 10 + "monitor_id": { 11 + "name": "monitor_id", 12 + "type": "integer", 13 + "primaryKey": false, 14 + "notNull": true, 15 + "autoincrement": false 16 + }, 17 + "status_report_id": { 18 + "name": "status_report_id", 19 + "type": "integer", 20 + "primaryKey": false, 21 + "notNull": true, 22 + "autoincrement": false 23 + }, 24 + "created_at": { 25 + "name": "created_at", 26 + "type": "integer", 27 + "primaryKey": false, 28 + "notNull": false, 29 + "autoincrement": false, 30 + "default": "(strftime('%s', 'now'))" 31 + } 32 + }, 33 + "indexes": {}, 34 + "foreignKeys": { 35 + "status_report_to_monitors_monitor_id_monitor_id_fk": { 36 + "name": "status_report_to_monitors_monitor_id_monitor_id_fk", 37 + "tableFrom": "status_report_to_monitors", 38 + "tableTo": "monitor", 39 + "columnsFrom": [ 40 + "monitor_id" 41 + ], 42 + "columnsTo": [ 43 + "id" 44 + ], 45 + "onDelete": "cascade", 46 + "onUpdate": "no action" 47 + }, 48 + "status_report_to_monitors_status_report_id_status_report_id_fk": { 49 + "name": "status_report_to_monitors_status_report_id_status_report_id_fk", 50 + "tableFrom": "status_report_to_monitors", 51 + "tableTo": "status_report", 52 + "columnsFrom": [ 53 + "status_report_id" 54 + ], 55 + "columnsTo": [ 56 + "id" 57 + ], 58 + "onDelete": "cascade", 59 + "onUpdate": "no action" 60 + } 61 + }, 62 + "compositePrimaryKeys": { 63 + "status_report_to_monitors_monitor_id_status_report_id_pk": { 64 + "columns": [ 65 + "monitor_id", 66 + "status_report_id" 67 + ], 68 + "name": "status_report_to_monitors_monitor_id_status_report_id_pk" 69 + } 70 + }, 71 + "uniqueConstraints": {} 72 + }, 73 + "status_reports_to_pages": { 74 + "name": "status_reports_to_pages", 75 + "columns": { 76 + "page_id": { 77 + "name": "page_id", 78 + "type": "integer", 79 + "primaryKey": false, 80 + "notNull": true, 81 + "autoincrement": false 82 + }, 83 + "status_report_id": { 84 + "name": "status_report_id", 85 + "type": "integer", 86 + "primaryKey": false, 87 + "notNull": true, 88 + "autoincrement": false 89 + }, 90 + "created_at": { 91 + "name": "created_at", 92 + "type": "integer", 93 + "primaryKey": false, 94 + "notNull": false, 95 + "autoincrement": false, 96 + "default": "(strftime('%s', 'now'))" 97 + } 98 + }, 99 + "indexes": {}, 100 + "foreignKeys": { 101 + "status_reports_to_pages_page_id_page_id_fk": { 102 + "name": "status_reports_to_pages_page_id_page_id_fk", 103 + "tableFrom": "status_reports_to_pages", 104 + "tableTo": "page", 105 + "columnsFrom": [ 106 + "page_id" 107 + ], 108 + "columnsTo": [ 109 + "id" 110 + ], 111 + "onDelete": "cascade", 112 + "onUpdate": "no action" 113 + }, 114 + "status_reports_to_pages_status_report_id_status_report_id_fk": { 115 + "name": "status_reports_to_pages_status_report_id_status_report_id_fk", 116 + "tableFrom": "status_reports_to_pages", 117 + "tableTo": "status_report", 118 + "columnsFrom": [ 119 + "status_report_id" 120 + ], 121 + "columnsTo": [ 122 + "id" 123 + ], 124 + "onDelete": "cascade", 125 + "onUpdate": "no action" 126 + } 127 + }, 128 + "compositePrimaryKeys": { 129 + "status_reports_to_pages_page_id_status_report_id_pk": { 130 + "columns": [ 131 + "page_id", 132 + "status_report_id" 133 + ], 134 + "name": "status_reports_to_pages_page_id_status_report_id_pk" 135 + } 136 + }, 137 + "uniqueConstraints": {} 138 + }, 139 + "status_report": { 140 + "name": "status_report", 141 + "columns": { 142 + "id": { 143 + "name": "id", 144 + "type": "integer", 145 + "primaryKey": true, 146 + "notNull": true, 147 + "autoincrement": false 148 + }, 149 + "status": { 150 + "name": "status", 151 + "type": "text", 152 + "primaryKey": false, 153 + "notNull": true, 154 + "autoincrement": false 155 + }, 156 + "title": { 157 + "name": "title", 158 + "type": "text(256)", 159 + "primaryKey": false, 160 + "notNull": true, 161 + "autoincrement": false 162 + }, 163 + "workspace_id": { 164 + "name": "workspace_id", 165 + "type": "integer", 166 + "primaryKey": false, 167 + "notNull": false, 168 + "autoincrement": false 169 + }, 170 + "created_at": { 171 + "name": "created_at", 172 + "type": "integer", 173 + "primaryKey": false, 174 + "notNull": false, 175 + "autoincrement": false, 176 + "default": "(strftime('%s', 'now'))" 177 + }, 178 + "updated_at": { 179 + "name": "updated_at", 180 + "type": "integer", 181 + "primaryKey": false, 182 + "notNull": false, 183 + "autoincrement": false, 184 + "default": "(strftime('%s', 'now'))" 185 + } 186 + }, 187 + "indexes": {}, 188 + "foreignKeys": { 189 + "status_report_workspace_id_workspace_id_fk": { 190 + "name": "status_report_workspace_id_workspace_id_fk", 191 + "tableFrom": "status_report", 192 + "tableTo": "workspace", 193 + "columnsFrom": [ 194 + "workspace_id" 195 + ], 196 + "columnsTo": [ 197 + "id" 198 + ], 199 + "onDelete": "no action", 200 + "onUpdate": "no action" 201 + } 202 + }, 203 + "compositePrimaryKeys": {}, 204 + "uniqueConstraints": {} 205 + }, 206 + "status_report_update": { 207 + "name": "status_report_update", 208 + "columns": { 209 + "id": { 210 + "name": "id", 211 + "type": "integer", 212 + "primaryKey": true, 213 + "notNull": true, 214 + "autoincrement": false 215 + }, 216 + "status": { 217 + "name": "status", 218 + "type": "text(4)", 219 + "primaryKey": false, 220 + "notNull": true, 221 + "autoincrement": false 222 + }, 223 + "date": { 224 + "name": "date", 225 + "type": "integer", 226 + "primaryKey": false, 227 + "notNull": true, 228 + "autoincrement": false 229 + }, 230 + "message": { 231 + "name": "message", 232 + "type": "text", 233 + "primaryKey": false, 234 + "notNull": true, 235 + "autoincrement": false 236 + }, 237 + "status_report_id": { 238 + "name": "status_report_id", 239 + "type": "integer", 240 + "primaryKey": false, 241 + "notNull": true, 242 + "autoincrement": false 243 + }, 244 + "created_at": { 245 + "name": "created_at", 246 + "type": "integer", 247 + "primaryKey": false, 248 + "notNull": false, 249 + "autoincrement": false, 250 + "default": "(strftime('%s', 'now'))" 251 + }, 252 + "updated_at": { 253 + "name": "updated_at", 254 + "type": "integer", 255 + "primaryKey": false, 256 + "notNull": false, 257 + "autoincrement": false, 258 + "default": "(strftime('%s', 'now'))" 259 + } 260 + }, 261 + "indexes": {}, 262 + "foreignKeys": { 263 + "status_report_update_status_report_id_status_report_id_fk": { 264 + "name": "status_report_update_status_report_id_status_report_id_fk", 265 + "tableFrom": "status_report_update", 266 + "tableTo": "status_report", 267 + "columnsFrom": [ 268 + "status_report_id" 269 + ], 270 + "columnsTo": [ 271 + "id" 272 + ], 273 + "onDelete": "cascade", 274 + "onUpdate": "no action" 275 + } 276 + }, 277 + "compositePrimaryKeys": {}, 278 + "uniqueConstraints": {} 279 + }, 280 + "integration": { 281 + "name": "integration", 282 + "columns": { 283 + "id": { 284 + "name": "id", 285 + "type": "integer", 286 + "primaryKey": true, 287 + "notNull": true, 288 + "autoincrement": false 289 + }, 290 + "name": { 291 + "name": "name", 292 + "type": "text(256)", 293 + "primaryKey": false, 294 + "notNull": true, 295 + "autoincrement": false 296 + }, 297 + "workspace_id": { 298 + "name": "workspace_id", 299 + "type": "integer", 300 + "primaryKey": false, 301 + "notNull": false, 302 + "autoincrement": false 303 + }, 304 + "credential": { 305 + "name": "credential", 306 + "type": "text", 307 + "primaryKey": false, 308 + "notNull": false, 309 + "autoincrement": false 310 + }, 311 + "external_id": { 312 + "name": "external_id", 313 + "type": "text", 314 + "primaryKey": false, 315 + "notNull": true, 316 + "autoincrement": false 317 + }, 318 + "created_at": { 319 + "name": "created_at", 320 + "type": "integer", 321 + "primaryKey": false, 322 + "notNull": false, 323 + "autoincrement": false, 324 + "default": "(strftime('%s', 'now'))" 325 + }, 326 + "updated_at": { 327 + "name": "updated_at", 328 + "type": "integer", 329 + "primaryKey": false, 330 + "notNull": false, 331 + "autoincrement": false, 332 + "default": "(strftime('%s', 'now'))" 333 + }, 334 + "data": { 335 + "name": "data", 336 + "type": "text", 337 + "primaryKey": false, 338 + "notNull": true, 339 + "autoincrement": false 340 + } 341 + }, 342 + "indexes": {}, 343 + "foreignKeys": { 344 + "integration_workspace_id_workspace_id_fk": { 345 + "name": "integration_workspace_id_workspace_id_fk", 346 + "tableFrom": "integration", 347 + "tableTo": "workspace", 348 + "columnsFrom": [ 349 + "workspace_id" 350 + ], 351 + "columnsTo": [ 352 + "id" 353 + ], 354 + "onDelete": "no action", 355 + "onUpdate": "no action" 356 + } 357 + }, 358 + "compositePrimaryKeys": {}, 359 + "uniqueConstraints": {} 360 + }, 361 + "page": { 362 + "name": "page", 363 + "columns": { 364 + "id": { 365 + "name": "id", 366 + "type": "integer", 367 + "primaryKey": true, 368 + "notNull": true, 369 + "autoincrement": false 370 + }, 371 + "workspace_id": { 372 + "name": "workspace_id", 373 + "type": "integer", 374 + "primaryKey": false, 375 + "notNull": true, 376 + "autoincrement": false 377 + }, 378 + "title": { 379 + "name": "title", 380 + "type": "text", 381 + "primaryKey": false, 382 + "notNull": true, 383 + "autoincrement": false 384 + }, 385 + "description": { 386 + "name": "description", 387 + "type": "text", 388 + "primaryKey": false, 389 + "notNull": true, 390 + "autoincrement": false 391 + }, 392 + "icon": { 393 + "name": "icon", 394 + "type": "text(256)", 395 + "primaryKey": false, 396 + "notNull": false, 397 + "autoincrement": false, 398 + "default": "''" 399 + }, 400 + "slug": { 401 + "name": "slug", 402 + "type": "text(256)", 403 + "primaryKey": false, 404 + "notNull": true, 405 + "autoincrement": false 406 + }, 407 + "custom_domain": { 408 + "name": "custom_domain", 409 + "type": "text(256)", 410 + "primaryKey": false, 411 + "notNull": true, 412 + "autoincrement": false 413 + }, 414 + "published": { 415 + "name": "published", 416 + "type": "integer", 417 + "primaryKey": false, 418 + "notNull": false, 419 + "autoincrement": false, 420 + "default": false 421 + }, 422 + "password": { 423 + "name": "password", 424 + "type": "text(256)", 425 + "primaryKey": false, 426 + "notNull": false, 427 + "autoincrement": false 428 + }, 429 + "password_protected": { 430 + "name": "password_protected", 431 + "type": "integer", 432 + "primaryKey": false, 433 + "notNull": false, 434 + "autoincrement": false, 435 + "default": false 436 + }, 437 + "created_at": { 438 + "name": "created_at", 439 + "type": "integer", 440 + "primaryKey": false, 441 + "notNull": false, 442 + "autoincrement": false, 443 + "default": "(strftime('%s', 'now'))" 444 + }, 445 + "updated_at": { 446 + "name": "updated_at", 447 + "type": "integer", 448 + "primaryKey": false, 449 + "notNull": false, 450 + "autoincrement": false, 451 + "default": "(strftime('%s', 'now'))" 452 + } 453 + }, 454 + "indexes": { 455 + "page_slug_unique": { 456 + "name": "page_slug_unique", 457 + "columns": [ 458 + "slug" 459 + ], 460 + "isUnique": true 461 + } 462 + }, 463 + "foreignKeys": { 464 + "page_workspace_id_workspace_id_fk": { 465 + "name": "page_workspace_id_workspace_id_fk", 466 + "tableFrom": "page", 467 + "tableTo": "workspace", 468 + "columnsFrom": [ 469 + "workspace_id" 470 + ], 471 + "columnsTo": [ 472 + "id" 473 + ], 474 + "onDelete": "cascade", 475 + "onUpdate": "no action" 476 + } 477 + }, 478 + "compositePrimaryKeys": {}, 479 + "uniqueConstraints": {} 480 + }, 481 + "monitor": { 482 + "name": "monitor", 483 + "columns": { 484 + "id": { 485 + "name": "id", 486 + "type": "integer", 487 + "primaryKey": true, 488 + "notNull": true, 489 + "autoincrement": false 490 + }, 491 + "job_type": { 492 + "name": "job_type", 493 + "type": "text", 494 + "primaryKey": false, 495 + "notNull": true, 496 + "autoincrement": false, 497 + "default": "'other'" 498 + }, 499 + "periodicity": { 500 + "name": "periodicity", 501 + "type": "text", 502 + "primaryKey": false, 503 + "notNull": true, 504 + "autoincrement": false, 505 + "default": "'other'" 506 + }, 507 + "status": { 508 + "name": "status", 509 + "type": "text", 510 + "primaryKey": false, 511 + "notNull": true, 512 + "autoincrement": false, 513 + "default": "'active'" 514 + }, 515 + "active": { 516 + "name": "active", 517 + "type": "integer", 518 + "primaryKey": false, 519 + "notNull": false, 520 + "autoincrement": false, 521 + "default": false 522 + }, 523 + "regions": { 524 + "name": "regions", 525 + "type": "text", 526 + "primaryKey": false, 527 + "notNull": true, 528 + "autoincrement": false, 529 + "default": "''" 530 + }, 531 + "url": { 532 + "name": "url", 533 + "type": "text(2048)", 534 + "primaryKey": false, 535 + "notNull": true, 536 + "autoincrement": false 537 + }, 538 + "name": { 539 + "name": "name", 540 + "type": "text(256)", 541 + "primaryKey": false, 542 + "notNull": true, 543 + "autoincrement": false, 544 + "default": "''" 545 + }, 546 + "description": { 547 + "name": "description", 548 + "type": "text", 549 + "primaryKey": false, 550 + "notNull": true, 551 + "autoincrement": false, 552 + "default": "''" 553 + }, 554 + "headers": { 555 + "name": "headers", 556 + "type": "text", 557 + "primaryKey": false, 558 + "notNull": false, 559 + "autoincrement": false, 560 + "default": "''" 561 + }, 562 + "body": { 563 + "name": "body", 564 + "type": "text", 565 + "primaryKey": false, 566 + "notNull": false, 567 + "autoincrement": false, 568 + "default": "''" 569 + }, 570 + "method": { 571 + "name": "method", 572 + "type": "text", 573 + "primaryKey": false, 574 + "notNull": false, 575 + "autoincrement": false, 576 + "default": "'GET'" 577 + }, 578 + "workspace_id": { 579 + "name": "workspace_id", 580 + "type": "integer", 581 + "primaryKey": false, 582 + "notNull": false, 583 + "autoincrement": false 584 + }, 585 + "assertions": { 586 + "name": "assertions", 587 + "type": "text", 588 + "primaryKey": false, 589 + "notNull": false, 590 + "autoincrement": false 591 + }, 592 + "public": { 593 + "name": "public", 594 + "type": "integer", 595 + "primaryKey": false, 596 + "notNull": false, 597 + "autoincrement": false, 598 + "default": false 599 + }, 600 + "created_at": { 601 + "name": "created_at", 602 + "type": "integer", 603 + "primaryKey": false, 604 + "notNull": false, 605 + "autoincrement": false, 606 + "default": "(strftime('%s', 'now'))" 607 + }, 608 + "updated_at": { 609 + "name": "updated_at", 610 + "type": "integer", 611 + "primaryKey": false, 612 + "notNull": false, 613 + "autoincrement": false, 614 + "default": "(strftime('%s', 'now'))" 615 + }, 616 + "deleted_at": { 617 + "name": "deleted_at", 618 + "type": "integer", 619 + "primaryKey": false, 620 + "notNull": false, 621 + "autoincrement": false 622 + } 623 + }, 624 + "indexes": {}, 625 + "foreignKeys": { 626 + "monitor_workspace_id_workspace_id_fk": { 627 + "name": "monitor_workspace_id_workspace_id_fk", 628 + "tableFrom": "monitor", 629 + "tableTo": "workspace", 630 + "columnsFrom": [ 631 + "workspace_id" 632 + ], 633 + "columnsTo": [ 634 + "id" 635 + ], 636 + "onDelete": "no action", 637 + "onUpdate": "no action" 638 + } 639 + }, 640 + "compositePrimaryKeys": {}, 641 + "uniqueConstraints": {} 642 + }, 643 + "monitors_to_pages": { 644 + "name": "monitors_to_pages", 645 + "columns": { 646 + "monitor_id": { 647 + "name": "monitor_id", 648 + "type": "integer", 649 + "primaryKey": false, 650 + "notNull": true, 651 + "autoincrement": false 652 + }, 653 + "page_id": { 654 + "name": "page_id", 655 + "type": "integer", 656 + "primaryKey": false, 657 + "notNull": true, 658 + "autoincrement": false 659 + }, 660 + "created_at": { 661 + "name": "created_at", 662 + "type": "integer", 663 + "primaryKey": false, 664 + "notNull": false, 665 + "autoincrement": false, 666 + "default": "(strftime('%s', 'now'))" 667 + }, 668 + "order": { 669 + "name": "order", 670 + "type": "integer", 671 + "primaryKey": false, 672 + "notNull": false, 673 + "autoincrement": false, 674 + "default": 0 675 + } 676 + }, 677 + "indexes": {}, 678 + "foreignKeys": { 679 + "monitors_to_pages_monitor_id_monitor_id_fk": { 680 + "name": "monitors_to_pages_monitor_id_monitor_id_fk", 681 + "tableFrom": "monitors_to_pages", 682 + "tableTo": "monitor", 683 + "columnsFrom": [ 684 + "monitor_id" 685 + ], 686 + "columnsTo": [ 687 + "id" 688 + ], 689 + "onDelete": "cascade", 690 + "onUpdate": "no action" 691 + }, 692 + "monitors_to_pages_page_id_page_id_fk": { 693 + "name": "monitors_to_pages_page_id_page_id_fk", 694 + "tableFrom": "monitors_to_pages", 695 + "tableTo": "page", 696 + "columnsFrom": [ 697 + "page_id" 698 + ], 699 + "columnsTo": [ 700 + "id" 701 + ], 702 + "onDelete": "cascade", 703 + "onUpdate": "no action" 704 + } 705 + }, 706 + "compositePrimaryKeys": { 707 + "monitors_to_pages_monitor_id_page_id_pk": { 708 + "columns": [ 709 + "monitor_id", 710 + "page_id" 711 + ], 712 + "name": "monitors_to_pages_monitor_id_page_id_pk" 713 + } 714 + }, 715 + "uniqueConstraints": {} 716 + }, 717 + "account": { 718 + "name": "account", 719 + "columns": { 720 + "user_id": { 721 + "name": "user_id", 722 + "type": "integer", 723 + "primaryKey": false, 724 + "notNull": true, 725 + "autoincrement": false 726 + }, 727 + "type": { 728 + "name": "type", 729 + "type": "text", 730 + "primaryKey": false, 731 + "notNull": true, 732 + "autoincrement": false 733 + }, 734 + "provider": { 735 + "name": "provider", 736 + "type": "text", 737 + "primaryKey": false, 738 + "notNull": true, 739 + "autoincrement": false 740 + }, 741 + "provider_account_id": { 742 + "name": "provider_account_id", 743 + "type": "text", 744 + "primaryKey": false, 745 + "notNull": true, 746 + "autoincrement": false 747 + }, 748 + "refresh_token": { 749 + "name": "refresh_token", 750 + "type": "text", 751 + "primaryKey": false, 752 + "notNull": false, 753 + "autoincrement": false 754 + }, 755 + "access_token": { 756 + "name": "access_token", 757 + "type": "text", 758 + "primaryKey": false, 759 + "notNull": false, 760 + "autoincrement": false 761 + }, 762 + "expires_at": { 763 + "name": "expires_at", 764 + "type": "integer", 765 + "primaryKey": false, 766 + "notNull": false, 767 + "autoincrement": false 768 + }, 769 + "token_type": { 770 + "name": "token_type", 771 + "type": "text", 772 + "primaryKey": false, 773 + "notNull": false, 774 + "autoincrement": false 775 + }, 776 + "scope": { 777 + "name": "scope", 778 + "type": "text", 779 + "primaryKey": false, 780 + "notNull": false, 781 + "autoincrement": false 782 + }, 783 + "id_token": { 784 + "name": "id_token", 785 + "type": "text", 786 + "primaryKey": false, 787 + "notNull": false, 788 + "autoincrement": false 789 + }, 790 + "session_state": { 791 + "name": "session_state", 792 + "type": "text", 793 + "primaryKey": false, 794 + "notNull": false, 795 + "autoincrement": false 796 + } 797 + }, 798 + "indexes": {}, 799 + "foreignKeys": { 800 + "account_user_id_user_id_fk": { 801 + "name": "account_user_id_user_id_fk", 802 + "tableFrom": "account", 803 + "tableTo": "user", 804 + "columnsFrom": [ 805 + "user_id" 806 + ], 807 + "columnsTo": [ 808 + "id" 809 + ], 810 + "onDelete": "cascade", 811 + "onUpdate": "no action" 812 + } 813 + }, 814 + "compositePrimaryKeys": { 815 + "account_provider_provider_account_id_pk": { 816 + "columns": [ 817 + "provider", 818 + "provider_account_id" 819 + ], 820 + "name": "account_provider_provider_account_id_pk" 821 + } 822 + }, 823 + "uniqueConstraints": {} 824 + }, 825 + "session": { 826 + "name": "session", 827 + "columns": { 828 + "session_token": { 829 + "name": "session_token", 830 + "type": "text", 831 + "primaryKey": true, 832 + "notNull": true, 833 + "autoincrement": false 834 + }, 835 + "user_id": { 836 + "name": "user_id", 837 + "type": "integer", 838 + "primaryKey": false, 839 + "notNull": true, 840 + "autoincrement": false 841 + }, 842 + "expires": { 843 + "name": "expires", 844 + "type": "integer", 845 + "primaryKey": false, 846 + "notNull": true, 847 + "autoincrement": false 848 + } 849 + }, 850 + "indexes": {}, 851 + "foreignKeys": { 852 + "session_user_id_user_id_fk": { 853 + "name": "session_user_id_user_id_fk", 854 + "tableFrom": "session", 855 + "tableTo": "user", 856 + "columnsFrom": [ 857 + "user_id" 858 + ], 859 + "columnsTo": [ 860 + "id" 861 + ], 862 + "onDelete": "cascade", 863 + "onUpdate": "no action" 864 + } 865 + }, 866 + "compositePrimaryKeys": {}, 867 + "uniqueConstraints": {} 868 + }, 869 + "user": { 870 + "name": "user", 871 + "columns": { 872 + "id": { 873 + "name": "id", 874 + "type": "integer", 875 + "primaryKey": true, 876 + "notNull": true, 877 + "autoincrement": false 878 + }, 879 + "tenant_id": { 880 + "name": "tenant_id", 881 + "type": "text(256)", 882 + "primaryKey": false, 883 + "notNull": false, 884 + "autoincrement": false 885 + }, 886 + "first_name": { 887 + "name": "first_name", 888 + "type": "text", 889 + "primaryKey": false, 890 + "notNull": false, 891 + "autoincrement": false, 892 + "default": "''" 893 + }, 894 + "last_name": { 895 + "name": "last_name", 896 + "type": "text", 897 + "primaryKey": false, 898 + "notNull": false, 899 + "autoincrement": false, 900 + "default": "''" 901 + }, 902 + "photo_url": { 903 + "name": "photo_url", 904 + "type": "text", 905 + "primaryKey": false, 906 + "notNull": false, 907 + "autoincrement": false, 908 + "default": "''" 909 + }, 910 + "name": { 911 + "name": "name", 912 + "type": "text", 913 + "primaryKey": false, 914 + "notNull": false, 915 + "autoincrement": false 916 + }, 917 + "email": { 918 + "name": "email", 919 + "type": "text", 920 + "primaryKey": false, 921 + "notNull": false, 922 + "autoincrement": false, 923 + "default": "''" 924 + }, 925 + "emailVerified": { 926 + "name": "emailVerified", 927 + "type": "integer", 928 + "primaryKey": false, 929 + "notNull": false, 930 + "autoincrement": false 931 + }, 932 + "created_at": { 933 + "name": "created_at", 934 + "type": "integer", 935 + "primaryKey": false, 936 + "notNull": false, 937 + "autoincrement": false, 938 + "default": "(strftime('%s', 'now'))" 939 + }, 940 + "updated_at": { 941 + "name": "updated_at", 942 + "type": "integer", 943 + "primaryKey": false, 944 + "notNull": false, 945 + "autoincrement": false, 946 + "default": "(strftime('%s', 'now'))" 947 + } 948 + }, 949 + "indexes": { 950 + "user_tenant_id_unique": { 951 + "name": "user_tenant_id_unique", 952 + "columns": [ 953 + "tenant_id" 954 + ], 955 + "isUnique": true 956 + } 957 + }, 958 + "foreignKeys": {}, 959 + "compositePrimaryKeys": {}, 960 + "uniqueConstraints": {} 961 + }, 962 + "users_to_workspaces": { 963 + "name": "users_to_workspaces", 964 + "columns": { 965 + "user_id": { 966 + "name": "user_id", 967 + "type": "integer", 968 + "primaryKey": false, 969 + "notNull": true, 970 + "autoincrement": false 971 + }, 972 + "workspace_id": { 973 + "name": "workspace_id", 974 + "type": "integer", 975 + "primaryKey": false, 976 + "notNull": true, 977 + "autoincrement": false 978 + }, 979 + "role": { 980 + "name": "role", 981 + "type": "text", 982 + "primaryKey": false, 983 + "notNull": true, 984 + "autoincrement": false, 985 + "default": "'member'" 986 + }, 987 + "created_at": { 988 + "name": "created_at", 989 + "type": "integer", 990 + "primaryKey": false, 991 + "notNull": false, 992 + "autoincrement": false, 993 + "default": "(strftime('%s', 'now'))" 994 + } 995 + }, 996 + "indexes": {}, 997 + "foreignKeys": { 998 + "users_to_workspaces_user_id_user_id_fk": { 999 + "name": "users_to_workspaces_user_id_user_id_fk", 1000 + "tableFrom": "users_to_workspaces", 1001 + "tableTo": "user", 1002 + "columnsFrom": [ 1003 + "user_id" 1004 + ], 1005 + "columnsTo": [ 1006 + "id" 1007 + ], 1008 + "onDelete": "no action", 1009 + "onUpdate": "no action" 1010 + }, 1011 + "users_to_workspaces_workspace_id_workspace_id_fk": { 1012 + "name": "users_to_workspaces_workspace_id_workspace_id_fk", 1013 + "tableFrom": "users_to_workspaces", 1014 + "tableTo": "workspace", 1015 + "columnsFrom": [ 1016 + "workspace_id" 1017 + ], 1018 + "columnsTo": [ 1019 + "id" 1020 + ], 1021 + "onDelete": "no action", 1022 + "onUpdate": "no action" 1023 + } 1024 + }, 1025 + "compositePrimaryKeys": { 1026 + "users_to_workspaces_user_id_workspace_id_pk": { 1027 + "columns": [ 1028 + "user_id", 1029 + "workspace_id" 1030 + ], 1031 + "name": "users_to_workspaces_user_id_workspace_id_pk" 1032 + } 1033 + }, 1034 + "uniqueConstraints": {} 1035 + }, 1036 + "verification_token": { 1037 + "name": "verification_token", 1038 + "columns": { 1039 + "identifier": { 1040 + "name": "identifier", 1041 + "type": "text", 1042 + "primaryKey": false, 1043 + "notNull": true, 1044 + "autoincrement": false 1045 + }, 1046 + "token": { 1047 + "name": "token", 1048 + "type": "text", 1049 + "primaryKey": false, 1050 + "notNull": true, 1051 + "autoincrement": false 1052 + }, 1053 + "expires": { 1054 + "name": "expires", 1055 + "type": "integer", 1056 + "primaryKey": false, 1057 + "notNull": true, 1058 + "autoincrement": false 1059 + } 1060 + }, 1061 + "indexes": {}, 1062 + "foreignKeys": {}, 1063 + "compositePrimaryKeys": { 1064 + "verification_token_identifier_token_pk": { 1065 + "columns": [ 1066 + "identifier", 1067 + "token" 1068 + ], 1069 + "name": "verification_token_identifier_token_pk" 1070 + } 1071 + }, 1072 + "uniqueConstraints": {} 1073 + }, 1074 + "page_subscriber": { 1075 + "name": "page_subscriber", 1076 + "columns": { 1077 + "id": { 1078 + "name": "id", 1079 + "type": "integer", 1080 + "primaryKey": true, 1081 + "notNull": true, 1082 + "autoincrement": false 1083 + }, 1084 + "email": { 1085 + "name": "email", 1086 + "type": "text", 1087 + "primaryKey": false, 1088 + "notNull": true, 1089 + "autoincrement": false 1090 + }, 1091 + "page_id": { 1092 + "name": "page_id", 1093 + "type": "integer", 1094 + "primaryKey": false, 1095 + "notNull": true, 1096 + "autoincrement": false 1097 + }, 1098 + "token": { 1099 + "name": "token", 1100 + "type": "text", 1101 + "primaryKey": false, 1102 + "notNull": false, 1103 + "autoincrement": false 1104 + }, 1105 + "accepted_at": { 1106 + "name": "accepted_at", 1107 + "type": "integer", 1108 + "primaryKey": false, 1109 + "notNull": false, 1110 + "autoincrement": false 1111 + }, 1112 + "expires_at": { 1113 + "name": "expires_at", 1114 + "type": "integer", 1115 + "primaryKey": false, 1116 + "notNull": false, 1117 + "autoincrement": false 1118 + }, 1119 + "created_at": { 1120 + "name": "created_at", 1121 + "type": "integer", 1122 + "primaryKey": false, 1123 + "notNull": false, 1124 + "autoincrement": false, 1125 + "default": "(strftime('%s', 'now'))" 1126 + }, 1127 + "updated_at": { 1128 + "name": "updated_at", 1129 + "type": "integer", 1130 + "primaryKey": false, 1131 + "notNull": false, 1132 + "autoincrement": false, 1133 + "default": "(strftime('%s', 'now'))" 1134 + } 1135 + }, 1136 + "indexes": {}, 1137 + "foreignKeys": { 1138 + "page_subscriber_page_id_page_id_fk": { 1139 + "name": "page_subscriber_page_id_page_id_fk", 1140 + "tableFrom": "page_subscriber", 1141 + "tableTo": "page", 1142 + "columnsFrom": [ 1143 + "page_id" 1144 + ], 1145 + "columnsTo": [ 1146 + "id" 1147 + ], 1148 + "onDelete": "no action", 1149 + "onUpdate": "no action" 1150 + } 1151 + }, 1152 + "compositePrimaryKeys": {}, 1153 + "uniqueConstraints": {} 1154 + }, 1155 + "workspace": { 1156 + "name": "workspace", 1157 + "columns": { 1158 + "id": { 1159 + "name": "id", 1160 + "type": "integer", 1161 + "primaryKey": true, 1162 + "notNull": true, 1163 + "autoincrement": false 1164 + }, 1165 + "slug": { 1166 + "name": "slug", 1167 + "type": "text", 1168 + "primaryKey": false, 1169 + "notNull": true, 1170 + "autoincrement": false 1171 + }, 1172 + "name": { 1173 + "name": "name", 1174 + "type": "text", 1175 + "primaryKey": false, 1176 + "notNull": false, 1177 + "autoincrement": false 1178 + }, 1179 + "stripe_id": { 1180 + "name": "stripe_id", 1181 + "type": "text(256)", 1182 + "primaryKey": false, 1183 + "notNull": false, 1184 + "autoincrement": false 1185 + }, 1186 + "subscription_id": { 1187 + "name": "subscription_id", 1188 + "type": "text", 1189 + "primaryKey": false, 1190 + "notNull": false, 1191 + "autoincrement": false 1192 + }, 1193 + "plan": { 1194 + "name": "plan", 1195 + "type": "text", 1196 + "primaryKey": false, 1197 + "notNull": false, 1198 + "autoincrement": false 1199 + }, 1200 + "ends_at": { 1201 + "name": "ends_at", 1202 + "type": "integer", 1203 + "primaryKey": false, 1204 + "notNull": false, 1205 + "autoincrement": false 1206 + }, 1207 + "paid_until": { 1208 + "name": "paid_until", 1209 + "type": "integer", 1210 + "primaryKey": false, 1211 + "notNull": false, 1212 + "autoincrement": false 1213 + }, 1214 + "created_at": { 1215 + "name": "created_at", 1216 + "type": "integer", 1217 + "primaryKey": false, 1218 + "notNull": false, 1219 + "autoincrement": false, 1220 + "default": "(strftime('%s', 'now'))" 1221 + }, 1222 + "updated_at": { 1223 + "name": "updated_at", 1224 + "type": "integer", 1225 + "primaryKey": false, 1226 + "notNull": false, 1227 + "autoincrement": false, 1228 + "default": "(strftime('%s', 'now'))" 1229 + }, 1230 + "dsn": { 1231 + "name": "dsn", 1232 + "type": "text", 1233 + "primaryKey": false, 1234 + "notNull": false, 1235 + "autoincrement": false 1236 + } 1237 + }, 1238 + "indexes": { 1239 + "workspace_slug_unique": { 1240 + "name": "workspace_slug_unique", 1241 + "columns": [ 1242 + "slug" 1243 + ], 1244 + "isUnique": true 1245 + }, 1246 + "workspace_stripe_id_unique": { 1247 + "name": "workspace_stripe_id_unique", 1248 + "columns": [ 1249 + "stripe_id" 1250 + ], 1251 + "isUnique": true 1252 + }, 1253 + "workspace_id_dsn_unique": { 1254 + "name": "workspace_id_dsn_unique", 1255 + "columns": [ 1256 + "id", 1257 + "dsn" 1258 + ], 1259 + "isUnique": true 1260 + } 1261 + }, 1262 + "foreignKeys": {}, 1263 + "compositePrimaryKeys": {}, 1264 + "uniqueConstraints": {} 1265 + }, 1266 + "notification": { 1267 + "name": "notification", 1268 + "columns": { 1269 + "id": { 1270 + "name": "id", 1271 + "type": "integer", 1272 + "primaryKey": true, 1273 + "notNull": true, 1274 + "autoincrement": false 1275 + }, 1276 + "name": { 1277 + "name": "name", 1278 + "type": "text", 1279 + "primaryKey": false, 1280 + "notNull": true, 1281 + "autoincrement": false 1282 + }, 1283 + "provider": { 1284 + "name": "provider", 1285 + "type": "text", 1286 + "primaryKey": false, 1287 + "notNull": true, 1288 + "autoincrement": false 1289 + }, 1290 + "data": { 1291 + "name": "data", 1292 + "type": "text", 1293 + "primaryKey": false, 1294 + "notNull": false, 1295 + "autoincrement": false, 1296 + "default": "'{}'" 1297 + }, 1298 + "workspace_id": { 1299 + "name": "workspace_id", 1300 + "type": "integer", 1301 + "primaryKey": false, 1302 + "notNull": false, 1303 + "autoincrement": false 1304 + }, 1305 + "created_at": { 1306 + "name": "created_at", 1307 + "type": "integer", 1308 + "primaryKey": false, 1309 + "notNull": false, 1310 + "autoincrement": false, 1311 + "default": "(strftime('%s', 'now'))" 1312 + }, 1313 + "updated_at": { 1314 + "name": "updated_at", 1315 + "type": "integer", 1316 + "primaryKey": false, 1317 + "notNull": false, 1318 + "autoincrement": false, 1319 + "default": "(strftime('%s', 'now'))" 1320 + } 1321 + }, 1322 + "indexes": {}, 1323 + "foreignKeys": { 1324 + "notification_workspace_id_workspace_id_fk": { 1325 + "name": "notification_workspace_id_workspace_id_fk", 1326 + "tableFrom": "notification", 1327 + "tableTo": "workspace", 1328 + "columnsFrom": [ 1329 + "workspace_id" 1330 + ], 1331 + "columnsTo": [ 1332 + "id" 1333 + ], 1334 + "onDelete": "no action", 1335 + "onUpdate": "no action" 1336 + } 1337 + }, 1338 + "compositePrimaryKeys": {}, 1339 + "uniqueConstraints": {} 1340 + }, 1341 + "notifications_to_monitors": { 1342 + "name": "notifications_to_monitors", 1343 + "columns": { 1344 + "monitor_id": { 1345 + "name": "monitor_id", 1346 + "type": "integer", 1347 + "primaryKey": false, 1348 + "notNull": true, 1349 + "autoincrement": false 1350 + }, 1351 + "notification_id": { 1352 + "name": "notification_id", 1353 + "type": "integer", 1354 + "primaryKey": false, 1355 + "notNull": true, 1356 + "autoincrement": false 1357 + }, 1358 + "created_at": { 1359 + "name": "created_at", 1360 + "type": "integer", 1361 + "primaryKey": false, 1362 + "notNull": false, 1363 + "autoincrement": false, 1364 + "default": "(strftime('%s', 'now'))" 1365 + } 1366 + }, 1367 + "indexes": {}, 1368 + "foreignKeys": { 1369 + "notifications_to_monitors_monitor_id_monitor_id_fk": { 1370 + "name": "notifications_to_monitors_monitor_id_monitor_id_fk", 1371 + "tableFrom": "notifications_to_monitors", 1372 + "tableTo": "monitor", 1373 + "columnsFrom": [ 1374 + "monitor_id" 1375 + ], 1376 + "columnsTo": [ 1377 + "id" 1378 + ], 1379 + "onDelete": "cascade", 1380 + "onUpdate": "no action" 1381 + }, 1382 + "notifications_to_monitors_notification_id_notification_id_fk": { 1383 + "name": "notifications_to_monitors_notification_id_notification_id_fk", 1384 + "tableFrom": "notifications_to_monitors", 1385 + "tableTo": "notification", 1386 + "columnsFrom": [ 1387 + "notification_id" 1388 + ], 1389 + "columnsTo": [ 1390 + "id" 1391 + ], 1392 + "onDelete": "cascade", 1393 + "onUpdate": "no action" 1394 + } 1395 + }, 1396 + "compositePrimaryKeys": { 1397 + "notifications_to_monitors_monitor_id_notification_id_pk": { 1398 + "columns": [ 1399 + "monitor_id", 1400 + "notification_id" 1401 + ], 1402 + "name": "notifications_to_monitors_monitor_id_notification_id_pk" 1403 + } 1404 + }, 1405 + "uniqueConstraints": {} 1406 + }, 1407 + "monitor_status": { 1408 + "name": "monitor_status", 1409 + "columns": { 1410 + "monitor_id": { 1411 + "name": "monitor_id", 1412 + "type": "integer", 1413 + "primaryKey": false, 1414 + "notNull": true, 1415 + "autoincrement": false 1416 + }, 1417 + "region": { 1418 + "name": "region", 1419 + "type": "text", 1420 + "primaryKey": false, 1421 + "notNull": true, 1422 + "autoincrement": false, 1423 + "default": "''" 1424 + }, 1425 + "status": { 1426 + "name": "status", 1427 + "type": "text", 1428 + "primaryKey": false, 1429 + "notNull": true, 1430 + "autoincrement": false, 1431 + "default": "'active'" 1432 + }, 1433 + "created_at": { 1434 + "name": "created_at", 1435 + "type": "integer", 1436 + "primaryKey": false, 1437 + "notNull": false, 1438 + "autoincrement": false, 1439 + "default": "(strftime('%s', 'now'))" 1440 + }, 1441 + "updated_at": { 1442 + "name": "updated_at", 1443 + "type": "integer", 1444 + "primaryKey": false, 1445 + "notNull": false, 1446 + "autoincrement": false, 1447 + "default": "(strftime('%s', 'now'))" 1448 + } 1449 + }, 1450 + "indexes": { 1451 + "monitor_status_idx": { 1452 + "name": "monitor_status_idx", 1453 + "columns": [ 1454 + "monitor_id", 1455 + "region" 1456 + ], 1457 + "isUnique": false 1458 + } 1459 + }, 1460 + "foreignKeys": { 1461 + "monitor_status_monitor_id_monitor_id_fk": { 1462 + "name": "monitor_status_monitor_id_monitor_id_fk", 1463 + "tableFrom": "monitor_status", 1464 + "tableTo": "monitor", 1465 + "columnsFrom": [ 1466 + "monitor_id" 1467 + ], 1468 + "columnsTo": [ 1469 + "id" 1470 + ], 1471 + "onDelete": "cascade", 1472 + "onUpdate": "no action" 1473 + } 1474 + }, 1475 + "compositePrimaryKeys": { 1476 + "monitor_status_monitor_id_region_pk": { 1477 + "columns": [ 1478 + "monitor_id", 1479 + "region" 1480 + ], 1481 + "name": "monitor_status_monitor_id_region_pk" 1482 + } 1483 + }, 1484 + "uniqueConstraints": {} 1485 + }, 1486 + "invitation": { 1487 + "name": "invitation", 1488 + "columns": { 1489 + "id": { 1490 + "name": "id", 1491 + "type": "integer", 1492 + "primaryKey": true, 1493 + "notNull": true, 1494 + "autoincrement": false 1495 + }, 1496 + "email": { 1497 + "name": "email", 1498 + "type": "text", 1499 + "primaryKey": false, 1500 + "notNull": true, 1501 + "autoincrement": false 1502 + }, 1503 + "role": { 1504 + "name": "role", 1505 + "type": "text", 1506 + "primaryKey": false, 1507 + "notNull": true, 1508 + "autoincrement": false, 1509 + "default": "'member'" 1510 + }, 1511 + "workspace_id": { 1512 + "name": "workspace_id", 1513 + "type": "integer", 1514 + "primaryKey": false, 1515 + "notNull": true, 1516 + "autoincrement": false 1517 + }, 1518 + "token": { 1519 + "name": "token", 1520 + "type": "text", 1521 + "primaryKey": false, 1522 + "notNull": true, 1523 + "autoincrement": false 1524 + }, 1525 + "expires_at": { 1526 + "name": "expires_at", 1527 + "type": "integer", 1528 + "primaryKey": false, 1529 + "notNull": true, 1530 + "autoincrement": false 1531 + }, 1532 + "created_at": { 1533 + "name": "created_at", 1534 + "type": "integer", 1535 + "primaryKey": false, 1536 + "notNull": false, 1537 + "autoincrement": false, 1538 + "default": "(strftime('%s', 'now'))" 1539 + }, 1540 + "accepted_at": { 1541 + "name": "accepted_at", 1542 + "type": "integer", 1543 + "primaryKey": false, 1544 + "notNull": false, 1545 + "autoincrement": false 1546 + } 1547 + }, 1548 + "indexes": {}, 1549 + "foreignKeys": {}, 1550 + "compositePrimaryKeys": {}, 1551 + "uniqueConstraints": {} 1552 + }, 1553 + "incident": { 1554 + "name": "incident", 1555 + "columns": { 1556 + "id": { 1557 + "name": "id", 1558 + "type": "integer", 1559 + "primaryKey": true, 1560 + "notNull": true, 1561 + "autoincrement": false 1562 + }, 1563 + "title": { 1564 + "name": "title", 1565 + "type": "text", 1566 + "primaryKey": false, 1567 + "notNull": true, 1568 + "autoincrement": false, 1569 + "default": "''" 1570 + }, 1571 + "summary": { 1572 + "name": "summary", 1573 + "type": "text", 1574 + "primaryKey": false, 1575 + "notNull": true, 1576 + "autoincrement": false, 1577 + "default": "''" 1578 + }, 1579 + "status": { 1580 + "name": "status", 1581 + "type": "text", 1582 + "primaryKey": false, 1583 + "notNull": true, 1584 + "autoincrement": false, 1585 + "default": "'triage'" 1586 + }, 1587 + "monitor_id": { 1588 + "name": "monitor_id", 1589 + "type": "integer", 1590 + "primaryKey": false, 1591 + "notNull": false, 1592 + "autoincrement": false 1593 + }, 1594 + "workspace_id": { 1595 + "name": "workspace_id", 1596 + "type": "integer", 1597 + "primaryKey": false, 1598 + "notNull": false, 1599 + "autoincrement": false 1600 + }, 1601 + "started_at": { 1602 + "name": "started_at", 1603 + "type": "integer", 1604 + "primaryKey": false, 1605 + "notNull": true, 1606 + "autoincrement": false, 1607 + "default": "(strftime('%s', 'now'))" 1608 + }, 1609 + "acknowledged_at": { 1610 + "name": "acknowledged_at", 1611 + "type": "integer", 1612 + "primaryKey": false, 1613 + "notNull": false, 1614 + "autoincrement": false 1615 + }, 1616 + "acknowledged_by": { 1617 + "name": "acknowledged_by", 1618 + "type": "integer", 1619 + "primaryKey": false, 1620 + "notNull": false, 1621 + "autoincrement": false 1622 + }, 1623 + "resolved_at": { 1624 + "name": "resolved_at", 1625 + "type": "integer", 1626 + "primaryKey": false, 1627 + "notNull": false, 1628 + "autoincrement": false 1629 + }, 1630 + "resolved_by": { 1631 + "name": "resolved_by", 1632 + "type": "integer", 1633 + "primaryKey": false, 1634 + "notNull": false, 1635 + "autoincrement": false 1636 + }, 1637 + "incident_screenshot_url": { 1638 + "name": "incident_screenshot_url", 1639 + "type": "text", 1640 + "primaryKey": false, 1641 + "notNull": false, 1642 + "autoincrement": false 1643 + }, 1644 + "recovery_screenshot_url": { 1645 + "name": "recovery_screenshot_url", 1646 + "type": "text", 1647 + "primaryKey": false, 1648 + "notNull": false, 1649 + "autoincrement": false 1650 + }, 1651 + "auto_resolved": { 1652 + "name": "auto_resolved", 1653 + "type": "integer", 1654 + "primaryKey": false, 1655 + "notNull": false, 1656 + "autoincrement": false, 1657 + "default": false 1658 + }, 1659 + "created_at": { 1660 + "name": "created_at", 1661 + "type": "integer", 1662 + "primaryKey": false, 1663 + "notNull": false, 1664 + "autoincrement": false, 1665 + "default": "(strftime('%s', 'now'))" 1666 + }, 1667 + "updated_at": { 1668 + "name": "updated_at", 1669 + "type": "integer", 1670 + "primaryKey": false, 1671 + "notNull": false, 1672 + "autoincrement": false, 1673 + "default": "(strftime('%s', 'now'))" 1674 + } 1675 + }, 1676 + "indexes": { 1677 + "incident_monitor_id_started_at_unique": { 1678 + "name": "incident_monitor_id_started_at_unique", 1679 + "columns": [ 1680 + "monitor_id", 1681 + "started_at" 1682 + ], 1683 + "isUnique": true 1684 + } 1685 + }, 1686 + "foreignKeys": { 1687 + "incident_monitor_id_monitor_id_fk": { 1688 + "name": "incident_monitor_id_monitor_id_fk", 1689 + "tableFrom": "incident", 1690 + "tableTo": "monitor", 1691 + "columnsFrom": [ 1692 + "monitor_id" 1693 + ], 1694 + "columnsTo": [ 1695 + "id" 1696 + ], 1697 + "onDelete": "set default", 1698 + "onUpdate": "no action" 1699 + }, 1700 + "incident_workspace_id_workspace_id_fk": { 1701 + "name": "incident_workspace_id_workspace_id_fk", 1702 + "tableFrom": "incident", 1703 + "tableTo": "workspace", 1704 + "columnsFrom": [ 1705 + "workspace_id" 1706 + ], 1707 + "columnsTo": [ 1708 + "id" 1709 + ], 1710 + "onDelete": "no action", 1711 + "onUpdate": "no action" 1712 + }, 1713 + "incident_acknowledged_by_user_id_fk": { 1714 + "name": "incident_acknowledged_by_user_id_fk", 1715 + "tableFrom": "incident", 1716 + "tableTo": "user", 1717 + "columnsFrom": [ 1718 + "acknowledged_by" 1719 + ], 1720 + "columnsTo": [ 1721 + "id" 1722 + ], 1723 + "onDelete": "no action", 1724 + "onUpdate": "no action" 1725 + }, 1726 + "incident_resolved_by_user_id_fk": { 1727 + "name": "incident_resolved_by_user_id_fk", 1728 + "tableFrom": "incident", 1729 + "tableTo": "user", 1730 + "columnsFrom": [ 1731 + "resolved_by" 1732 + ], 1733 + "columnsTo": [ 1734 + "id" 1735 + ], 1736 + "onDelete": "no action", 1737 + "onUpdate": "no action" 1738 + } 1739 + }, 1740 + "compositePrimaryKeys": {}, 1741 + "uniqueConstraints": {} 1742 + }, 1743 + "monitor_tag": { 1744 + "name": "monitor_tag", 1745 + "columns": { 1746 + "id": { 1747 + "name": "id", 1748 + "type": "integer", 1749 + "primaryKey": true, 1750 + "notNull": true, 1751 + "autoincrement": false 1752 + }, 1753 + "workspace_id": { 1754 + "name": "workspace_id", 1755 + "type": "integer", 1756 + "primaryKey": false, 1757 + "notNull": true, 1758 + "autoincrement": false 1759 + }, 1760 + "name": { 1761 + "name": "name", 1762 + "type": "text", 1763 + "primaryKey": false, 1764 + "notNull": true, 1765 + "autoincrement": false 1766 + }, 1767 + "color": { 1768 + "name": "color", 1769 + "type": "text", 1770 + "primaryKey": false, 1771 + "notNull": true, 1772 + "autoincrement": false 1773 + }, 1774 + "created_at": { 1775 + "name": "created_at", 1776 + "type": "integer", 1777 + "primaryKey": false, 1778 + "notNull": false, 1779 + "autoincrement": false, 1780 + "default": "(strftime('%s', 'now'))" 1781 + }, 1782 + "updated_at": { 1783 + "name": "updated_at", 1784 + "type": "integer", 1785 + "primaryKey": false, 1786 + "notNull": false, 1787 + "autoincrement": false, 1788 + "default": "(strftime('%s', 'now'))" 1789 + } 1790 + }, 1791 + "indexes": {}, 1792 + "foreignKeys": { 1793 + "monitor_tag_workspace_id_workspace_id_fk": { 1794 + "name": "monitor_tag_workspace_id_workspace_id_fk", 1795 + "tableFrom": "monitor_tag", 1796 + "tableTo": "workspace", 1797 + "columnsFrom": [ 1798 + "workspace_id" 1799 + ], 1800 + "columnsTo": [ 1801 + "id" 1802 + ], 1803 + "onDelete": "cascade", 1804 + "onUpdate": "no action" 1805 + } 1806 + }, 1807 + "compositePrimaryKeys": {}, 1808 + "uniqueConstraints": {} 1809 + }, 1810 + "monitor_tag_to_monitor": { 1811 + "name": "monitor_tag_to_monitor", 1812 + "columns": { 1813 + "monitor_id": { 1814 + "name": "monitor_id", 1815 + "type": "integer", 1816 + "primaryKey": false, 1817 + "notNull": true, 1818 + "autoincrement": false 1819 + }, 1820 + "monitor_tag_id": { 1821 + "name": "monitor_tag_id", 1822 + "type": "integer", 1823 + "primaryKey": false, 1824 + "notNull": true, 1825 + "autoincrement": false 1826 + }, 1827 + "created_at": { 1828 + "name": "created_at", 1829 + "type": "integer", 1830 + "primaryKey": false, 1831 + "notNull": false, 1832 + "autoincrement": false, 1833 + "default": "(strftime('%s', 'now'))" 1834 + } 1835 + }, 1836 + "indexes": {}, 1837 + "foreignKeys": { 1838 + "monitor_tag_to_monitor_monitor_id_monitor_id_fk": { 1839 + "name": "monitor_tag_to_monitor_monitor_id_monitor_id_fk", 1840 + "tableFrom": "monitor_tag_to_monitor", 1841 + "tableTo": "monitor", 1842 + "columnsFrom": [ 1843 + "monitor_id" 1844 + ], 1845 + "columnsTo": [ 1846 + "id" 1847 + ], 1848 + "onDelete": "cascade", 1849 + "onUpdate": "no action" 1850 + }, 1851 + "monitor_tag_to_monitor_monitor_tag_id_monitor_tag_id_fk": { 1852 + "name": "monitor_tag_to_monitor_monitor_tag_id_monitor_tag_id_fk", 1853 + "tableFrom": "monitor_tag_to_monitor", 1854 + "tableTo": "monitor_tag", 1855 + "columnsFrom": [ 1856 + "monitor_tag_id" 1857 + ], 1858 + "columnsTo": [ 1859 + "id" 1860 + ], 1861 + "onDelete": "cascade", 1862 + "onUpdate": "no action" 1863 + } 1864 + }, 1865 + "compositePrimaryKeys": { 1866 + "monitor_tag_to_monitor_monitor_id_monitor_tag_id_pk": { 1867 + "columns": [ 1868 + "monitor_id", 1869 + "monitor_tag_id" 1870 + ], 1871 + "name": "monitor_tag_to_monitor_monitor_id_monitor_tag_id_pk" 1872 + } 1873 + }, 1874 + "uniqueConstraints": {} 1875 + }, 1876 + "application": { 1877 + "name": "application", 1878 + "columns": { 1879 + "id": { 1880 + "name": "id", 1881 + "type": "integer", 1882 + "primaryKey": true, 1883 + "notNull": true, 1884 + "autoincrement": false 1885 + }, 1886 + "name": { 1887 + "name": "name", 1888 + "type": "text", 1889 + "primaryKey": false, 1890 + "notNull": false, 1891 + "autoincrement": false 1892 + }, 1893 + "dsn": { 1894 + "name": "dsn", 1895 + "type": "text", 1896 + "primaryKey": false, 1897 + "notNull": false, 1898 + "autoincrement": false 1899 + }, 1900 + "workspace_id": { 1901 + "name": "workspace_id", 1902 + "type": "integer", 1903 + "primaryKey": false, 1904 + "notNull": false, 1905 + "autoincrement": false 1906 + }, 1907 + "created_at": { 1908 + "name": "created_at", 1909 + "type": "integer", 1910 + "primaryKey": false, 1911 + "notNull": false, 1912 + "autoincrement": false, 1913 + "default": "(strftime('%s', 'now'))" 1914 + }, 1915 + "updated_at": { 1916 + "name": "updated_at", 1917 + "type": "integer", 1918 + "primaryKey": false, 1919 + "notNull": false, 1920 + "autoincrement": false, 1921 + "default": "(strftime('%s', 'now'))" 1922 + } 1923 + }, 1924 + "indexes": { 1925 + "application_dsn_unique": { 1926 + "name": "application_dsn_unique", 1927 + "columns": [ 1928 + "dsn" 1929 + ], 1930 + "isUnique": true 1931 + } 1932 + }, 1933 + "foreignKeys": { 1934 + "application_workspace_id_workspace_id_fk": { 1935 + "name": "application_workspace_id_workspace_id_fk", 1936 + "tableFrom": "application", 1937 + "tableTo": "workspace", 1938 + "columnsFrom": [ 1939 + "workspace_id" 1940 + ], 1941 + "columnsTo": [ 1942 + "id" 1943 + ], 1944 + "onDelete": "no action", 1945 + "onUpdate": "no action" 1946 + } 1947 + }, 1948 + "compositePrimaryKeys": {}, 1949 + "uniqueConstraints": {} 1950 + }, 1951 + "maintenance": { 1952 + "name": "maintenance", 1953 + "columns": { 1954 + "id": { 1955 + "name": "id", 1956 + "type": "integer", 1957 + "primaryKey": true, 1958 + "notNull": true, 1959 + "autoincrement": false 1960 + }, 1961 + "title": { 1962 + "name": "title", 1963 + "type": "text(256)", 1964 + "primaryKey": false, 1965 + "notNull": true, 1966 + "autoincrement": false 1967 + }, 1968 + "message": { 1969 + "name": "message", 1970 + "type": "text", 1971 + "primaryKey": false, 1972 + "notNull": true, 1973 + "autoincrement": false 1974 + }, 1975 + "from": { 1976 + "name": "from", 1977 + "type": "integer", 1978 + "primaryKey": false, 1979 + "notNull": true, 1980 + "autoincrement": false 1981 + }, 1982 + "to": { 1983 + "name": "to", 1984 + "type": "integer", 1985 + "primaryKey": false, 1986 + "notNull": true, 1987 + "autoincrement": false 1988 + }, 1989 + "workspace_id": { 1990 + "name": "workspace_id", 1991 + "type": "integer", 1992 + "primaryKey": false, 1993 + "notNull": false, 1994 + "autoincrement": false 1995 + }, 1996 + "page_id": { 1997 + "name": "page_id", 1998 + "type": "integer", 1999 + "primaryKey": false, 2000 + "notNull": false, 2001 + "autoincrement": false 2002 + }, 2003 + "created_at": { 2004 + "name": "created_at", 2005 + "type": "integer", 2006 + "primaryKey": false, 2007 + "notNull": false, 2008 + "autoincrement": false, 2009 + "default": "(strftime('%s', 'now'))" 2010 + }, 2011 + "updated_at": { 2012 + "name": "updated_at", 2013 + "type": "integer", 2014 + "primaryKey": false, 2015 + "notNull": false, 2016 + "autoincrement": false, 2017 + "default": "(strftime('%s', 'now'))" 2018 + } 2019 + }, 2020 + "indexes": {}, 2021 + "foreignKeys": { 2022 + "maintenance_workspace_id_workspace_id_fk": { 2023 + "name": "maintenance_workspace_id_workspace_id_fk", 2024 + "tableFrom": "maintenance", 2025 + "tableTo": "workspace", 2026 + "columnsFrom": [ 2027 + "workspace_id" 2028 + ], 2029 + "columnsTo": [ 2030 + "id" 2031 + ], 2032 + "onDelete": "no action", 2033 + "onUpdate": "no action" 2034 + }, 2035 + "maintenance_page_id_page_id_fk": { 2036 + "name": "maintenance_page_id_page_id_fk", 2037 + "tableFrom": "maintenance", 2038 + "tableTo": "page", 2039 + "columnsFrom": [ 2040 + "page_id" 2041 + ], 2042 + "columnsTo": [ 2043 + "id" 2044 + ], 2045 + "onDelete": "no action", 2046 + "onUpdate": "no action" 2047 + } 2048 + }, 2049 + "compositePrimaryKeys": {}, 2050 + "uniqueConstraints": {} 2051 + }, 2052 + "maintenance_to_monitor": { 2053 + "name": "maintenance_to_monitor", 2054 + "columns": { 2055 + "monitor_id": { 2056 + "name": "monitor_id", 2057 + "type": "integer", 2058 + "primaryKey": false, 2059 + "notNull": true, 2060 + "autoincrement": false 2061 + }, 2062 + "maintenance_id": { 2063 + "name": "maintenance_id", 2064 + "type": "integer", 2065 + "primaryKey": false, 2066 + "notNull": true, 2067 + "autoincrement": false 2068 + }, 2069 + "created_at": { 2070 + "name": "created_at", 2071 + "type": "integer", 2072 + "primaryKey": false, 2073 + "notNull": false, 2074 + "autoincrement": false, 2075 + "default": "(strftime('%s', 'now'))" 2076 + } 2077 + }, 2078 + "indexes": {}, 2079 + "foreignKeys": { 2080 + "maintenance_to_monitor_monitor_id_monitor_id_fk": { 2081 + "name": "maintenance_to_monitor_monitor_id_monitor_id_fk", 2082 + "tableFrom": "maintenance_to_monitor", 2083 + "tableTo": "monitor", 2084 + "columnsFrom": [ 2085 + "monitor_id" 2086 + ], 2087 + "columnsTo": [ 2088 + "id" 2089 + ], 2090 + "onDelete": "cascade", 2091 + "onUpdate": "no action" 2092 + }, 2093 + "maintenance_to_monitor_maintenance_id_maintenance_id_fk": { 2094 + "name": "maintenance_to_monitor_maintenance_id_maintenance_id_fk", 2095 + "tableFrom": "maintenance_to_monitor", 2096 + "tableTo": "maintenance", 2097 + "columnsFrom": [ 2098 + "maintenance_id" 2099 + ], 2100 + "columnsTo": [ 2101 + "id" 2102 + ], 2103 + "onDelete": "cascade", 2104 + "onUpdate": "no action" 2105 + } 2106 + }, 2107 + "compositePrimaryKeys": { 2108 + "maintenance_to_monitor_monitor_id_maintenance_id_pk": { 2109 + "columns": [ 2110 + "maintenance_id", 2111 + "monitor_id" 2112 + ], 2113 + "name": "maintenance_to_monitor_monitor_id_maintenance_id_pk" 2114 + } 2115 + }, 2116 + "uniqueConstraints": {} 2117 + }, 2118 + "check": { 2119 + "name": "check", 2120 + "columns": { 2121 + "id": { 2122 + "name": "id", 2123 + "type": "integer", 2124 + "primaryKey": true, 2125 + "notNull": true, 2126 + "autoincrement": true 2127 + }, 2128 + "regions": { 2129 + "name": "regions", 2130 + "type": "text", 2131 + "primaryKey": false, 2132 + "notNull": true, 2133 + "autoincrement": false, 2134 + "default": "''" 2135 + }, 2136 + "url": { 2137 + "name": "url", 2138 + "type": "text(4096)", 2139 + "primaryKey": false, 2140 + "notNull": true, 2141 + "autoincrement": false 2142 + }, 2143 + "headers": { 2144 + "name": "headers", 2145 + "type": "text", 2146 + "primaryKey": false, 2147 + "notNull": false, 2148 + "autoincrement": false, 2149 + "default": "''" 2150 + }, 2151 + "body": { 2152 + "name": "body", 2153 + "type": "text", 2154 + "primaryKey": false, 2155 + "notNull": false, 2156 + "autoincrement": false, 2157 + "default": "''" 2158 + }, 2159 + "method": { 2160 + "name": "method", 2161 + "type": "text", 2162 + "primaryKey": false, 2163 + "notNull": false, 2164 + "autoincrement": false, 2165 + "default": "'GET'" 2166 + }, 2167 + "count_requests": { 2168 + "name": "count_requests", 2169 + "type": "integer", 2170 + "primaryKey": false, 2171 + "notNull": false, 2172 + "autoincrement": false, 2173 + "default": 1 2174 + }, 2175 + "workspace_id": { 2176 + "name": "workspace_id", 2177 + "type": "integer", 2178 + "primaryKey": false, 2179 + "notNull": false, 2180 + "autoincrement": false 2181 + }, 2182 + "created_at": { 2183 + "name": "created_at", 2184 + "type": "integer", 2185 + "primaryKey": false, 2186 + "notNull": false, 2187 + "autoincrement": false, 2188 + "default": "(strftime('%s', 'now'))" 2189 + } 2190 + }, 2191 + "indexes": {}, 2192 + "foreignKeys": { 2193 + "check_workspace_id_workspace_id_fk": { 2194 + "name": "check_workspace_id_workspace_id_fk", 2195 + "tableFrom": "check", 2196 + "tableTo": "workspace", 2197 + "columnsFrom": [ 2198 + "workspace_id" 2199 + ], 2200 + "columnsTo": [ 2201 + "id" 2202 + ], 2203 + "onDelete": "no action", 2204 + "onUpdate": "no action" 2205 + } 2206 + }, 2207 + "compositePrimaryKeys": {}, 2208 + "uniqueConstraints": {} 2209 + } 2210 + }, 2211 + "enums": {}, 2212 + "_meta": { 2213 + "schemas": {}, 2214 + "tables": {}, 2215 + "columns": {} 2216 + } 2217 + }
+7
packages/db/drizzle/meta/_journal.json
··· 225 225 "when": 1717837961923, 226 226 "tag": "0031_lowly_gabe_jones", 227 227 "breakpoints": true 228 + }, 229 + { 230 + "idx": 32, 231 + "version": "6", 232 + "when": 1718027484219, 233 + "tag": "0032_hot_swordsman", 234 + "breakpoints": true 228 235 } 229 236 ] 230 237 }
+21
packages/db/src/schema/check/check.ts
··· 1 + import { sql } from "drizzle-orm"; 2 + import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; 3 + import { monitorMethods } from "../monitors/constants"; 4 + import { workspace } from "../workspaces"; 5 + 6 + export const check = sqliteTable("check", { 7 + id: integer("id").primaryKey({ autoIncrement: true }), 8 + regions: text("regions").default("").notNull(), 9 + url: text("url", { length: 4096 }).notNull(), 10 + headers: text("headers").default(""), 11 + body: text("body").default(""), 12 + method: text("method", { enum: monitorMethods }).default("GET"), 13 + 14 + countRequests: integer("count_requests").default(1), 15 + 16 + workspaceId: integer("workspace_id").references(() => workspace.id), 17 + 18 + createdAt: integer("created_at", { mode: "timestamp" }).default( 19 + sql`(strftime('%s', 'now'))`, 20 + ), 21 + });
+9
packages/db/src/schema/check/constants.ts
··· 1 + export const flyCheckerRegions = [ 2 + // TODO: Add more regions 3 + "ams", 4 + "iad", 5 + "hkg", 6 + "jnb", 7 + "syd", 8 + "gru", 9 + ] as const;
+2
packages/db/src/schema/check/index.ts
··· 1 + export * from "./check"; 2 + export * from "./constants";
+1
packages/db/src/schema/index.ts
··· 13 13 export * from "./monitor_tags"; 14 14 export * from "./applications"; 15 15 export * from "./maintenances"; 16 + export * from "./check";
+38 -24
pnpm-lock.yaml
··· 144 144 dependencies: 145 145 '@hono/sentry': 146 146 specifier: 1.1.0 147 - version: 1.1.0(hono@4.0.0) 147 + version: 1.1.0(hono@4.4.5) 148 148 '@hono/zod-openapi': 149 - specifier: 0.13.0 150 - version: 0.13.0(hono@4.0.0)(zod@3.22.4) 149 + specifier: 0.14.2 150 + version: 0.14.2(hono@4.4.5)(zod@3.22.4) 151 151 '@hono/zod-validator': 152 - specifier: 0.2.1 153 - version: 0.2.1(hono@4.0.0)(zod@3.22.4) 152 + specifier: 0.2.2 153 + version: 0.2.2(hono@4.4.5)(zod@3.22.4) 154 154 '@openstatus/analytics': 155 155 specifier: workspace:^ 156 156 version: link:../../packages/analytics ··· 200 200 specifier: 2.1.8 201 201 version: 2.1.8 202 202 hono: 203 - specifier: 4.0.0 204 - version: 4.0.0 203 + specifier: 4.4.5 204 + version: 4.4.5 205 205 nanoid: 206 206 specifier: 5.0.2 207 207 version: 5.0.2 208 + percentile: 209 + specifier: ^1.6.0 210 + version: 1.6.0 208 211 validator: 209 212 specifier: 13.11.0 210 213 version: 13.11.0 ··· 2387 2390 peerDependencies: 2388 2391 hono: '>=3.*' 2389 2392 2390 - '@hono/zod-openapi@0.13.0': 2391 - resolution: {integrity: sha512-viL2N3apZOwtsgaUBgWSLof0hOivRPPVy96gIp3FPbkXB5cfYUC4QrY2nB4EnYc4pAwsnIBYd9JW7QJFQ4DMdg==} 2393 + '@hono/zod-openapi@0.14.2': 2394 + resolution: {integrity: sha512-nt4TCMwGebajiJ5QEZ2QOVRp3N7GMLs1P7csNQJSu6Aean4D+8TWgZTaAJSytLvVZWy90qnBbpRlVSS6C6M+0g==} 2392 2395 engines: {node: '>=16.0.0'} 2393 2396 peerDependencies: 2394 2397 hono: '>=4.3.6' ··· 2396 2399 2397 2400 '@hono/zod-validator@0.2.1': 2398 2401 resolution: {integrity: sha512-HFoxln7Q6JsE64qz2WBS28SD33UB2alp3aRKmcWnNLDzEL1BLsWfbdX6e1HIiUprHYTIXf5y7ax8eYidKUwyaA==} 2402 + peerDependencies: 2403 + hono: '>=3.9.0' 2404 + zod: ^3.19.1 2405 + 2406 + '@hono/zod-validator@0.2.2': 2407 + resolution: {integrity: sha512-dSDxaPV70Py8wuIU2QNpoVEIOSzSXZ/6/B/h4xA7eOMz7+AarKTSGV8E6QwrdcCbBLkpqfJ4Q2TmBO0eP1tCBQ==} 2399 2408 peerDependencies: 2400 2409 hono: '>=3.9.0' 2401 2410 zod: ^3.19.1 ··· 5677 5686 5678 5687 hoist-non-react-statics@3.3.2: 5679 5688 resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} 5680 - 5681 - hono@4.0.0: 5682 - resolution: {integrity: sha512-8dKhuBBpRZEodUttQhrSFJ6PQqHRjXHyeeegfxOf132pvgbf0tOb9qqb7q7eYwAWpOcYrsUOsWdJ0sQIIovhZg==} 5683 - engines: {node: '>=16.0.0'} 5684 5689 5685 5690 hono@4.2.2: 5686 5691 resolution: {integrity: sha512-mDmjBHF6uBNN3TASdAbDCFsN9FLbrlgXyFZkhLEkU7hUgk0+T9hcsUrL/nho4qV+Xk0RDHx7gop4Q1gelZZVRw==} ··· 5690 5695 resolution: {integrity: sha512-6c5LVE23HnIS8iBhY+XPmYJlPeeClznOi7mBNsAsJCgxo8Ciz75LTjqRUf5wv4RYq8kL+1KPLUZHCtKmbZssNg==} 5691 5696 engines: {node: '>=16.0.0'} 5692 5697 5698 + hono@4.4.5: 5699 + resolution: {integrity: sha512-hyf+1c+gTEo0+xjdYT2e8y4M3HcEy0ARuRZHPRgeRhq/mupkI4j9/qEWVKsyj0se4KhfRnYrld4Tk6z/9veq/Q==} 5700 + engines: {node: '>=16.0.0'} 5701 + 5693 5702 hosted-git-info@2.8.9: 5694 5703 resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} 5695 5704 ··· 6840 6849 6841 6850 peberminta@0.9.0: 6842 6851 resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==} 6852 + 6853 + percentile@1.6.0: 6854 + resolution: {integrity: sha512-8vSyjdzwxGDHHwH+cSGch3A9Uj2On3UpgOWxWXMKwUvoAbnujx6DaqmV1duWXNiH/oEWpyVd6nSQccix6DM3Ng==} 6843 6855 6844 6856 periscopic@3.1.0: 6845 6857 resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} ··· 9659 9671 dependencies: 9660 9672 tailwindcss: 3.4.3(ts-node@10.9.2(@types/node@20.8.0)(typescript@5.4.5)) 9661 9673 9662 - '@hono/sentry@1.1.0(hono@4.0.0)': 9674 + '@hono/sentry@1.1.0(hono@4.4.5)': 9663 9675 dependencies: 9664 - hono: 4.0.0 9676 + hono: 4.4.5 9665 9677 toucan-js: 3.4.0 9666 9678 9667 - '@hono/zod-openapi@0.13.0(hono@4.0.0)(zod@3.22.4)': 9679 + '@hono/zod-openapi@0.14.2(hono@4.4.5)(zod@3.22.4)': 9668 9680 dependencies: 9669 9681 '@asteasolutions/zod-to-openapi': 7.0.0(zod@3.22.4) 9670 - '@hono/zod-validator': 0.2.1(hono@4.0.0)(zod@3.22.4) 9671 - hono: 4.0.0 9682 + '@hono/zod-validator': 0.2.2(hono@4.4.5)(zod@3.22.4) 9683 + hono: 4.4.5 9672 9684 zod: 3.22.4 9673 9685 9674 - '@hono/zod-validator@0.2.1(hono@4.0.0)(zod@3.22.4)': 9686 + '@hono/zod-validator@0.2.1(hono@4.2.2)(zod@3.22.4)': 9675 9687 dependencies: 9676 - hono: 4.0.0 9688 + hono: 4.2.2 9677 9689 zod: 3.22.4 9678 9690 9679 - '@hono/zod-validator@0.2.1(hono@4.2.2)(zod@3.22.4)': 9691 + '@hono/zod-validator@0.2.2(hono@4.4.5)(zod@3.22.4)': 9680 9692 dependencies: 9681 - hono: 4.2.2 9693 + hono: 4.4.5 9682 9694 zod: 3.22.4 9683 9695 9684 9696 '@hookform/resolvers@3.3.1(react-hook-form@7.47.0(react@18.2.0))': ··· 13606 13618 dependencies: 13607 13619 react-is: 16.13.1 13608 13620 13609 - hono@4.0.0: {} 13610 - 13611 13621 hono@4.2.2: {} 13612 13622 13613 13623 hono@4.3.9: {} 13624 + 13625 + hono@4.4.5: {} 13614 13626 13615 13627 hosted-git-info@2.8.9: {} 13616 13628 ··· 15178 15190 peberminta@0.8.0: {} 15179 15191 15180 15192 peberminta@0.9.0: {} 15193 + 15194 + percentile@1.6.0: {} 15181 15195 15182 15196 periscopic@3.1.0: 15183 15197 dependencies: