Openstatus www.openstatus.dev

feat: incidents to pages (#375)

authored by

Maximilian Kaske and committed by
GitHub
591175b6 ecb14386

+1314 -83
+8 -3
apps/web/src/app/app/(dashboard)/[workspaceSlug]/incidents/edit/page.tsx
··· 26 26 } 27 27 28 28 const { id } = search.data; 29 + const { workspaceSlug } = params; 29 30 30 31 const incident = id 31 32 ? await api.incident.getIncidentById.query({ ··· 34 35 : undefined; 35 36 36 37 const monitors = await api.monitor.getMonitorsByWorkspace.query({ 37 - workspaceSlug: params.workspaceSlug, 38 + workspaceSlug, 38 39 }); 39 40 41 + const pages = await api.page.getPagesByWorkspace.query({ workspaceSlug }); 42 + 40 43 return ( 41 44 <div className="grid gap-6 md:grid-cols-2 md:gap-8"> 42 45 <Header title="Incident" description="Upsert your incident." /> 43 46 <div className="col-span-full"> 44 47 <IncidentForm 45 - workspaceSlug={params.workspaceSlug} 48 + workspaceSlug={workspaceSlug} 46 49 monitors={monitors} 50 + pages={pages} 47 51 defaultValues={ 48 52 incident 49 53 ? { 50 54 ...incident, 51 - workspaceSlug: params.workspaceSlug, 55 + workspaceSlug, 52 56 monitors: incident?.monitorsToIncidents.map( 53 57 ({ monitorId }) => monitorId, 54 58 ), 59 + pages: incident?.pagesToIncidents.map(({ pageId }) => pageId), 55 60 } 56 61 : undefined 57 62 }
+11 -10
apps/web/src/app/app/(dashboard)/[workspaceSlug]/incidents/page.tsx
··· 41 41 {incidents?.map((incident, i) => { 42 42 const { label, icon } = 43 43 statusDict[incident.status as keyof typeof statusDict]; 44 + const monitors = incident.monitorsToIncidents.map( 45 + ({ monitor }) => monitor, 46 + ); 44 47 return ( 45 48 <li key={i} className="grid gap-2"> 46 49 <time className="text-muted-foreground pl-3 text-xs"> ··· 71 74 ]} 72 75 > 73 76 <div className="grid gap-4"> 74 - <div> 75 - <p className="text-muted-foreground mb-1.5 text-xs"> 76 - Affected Monitors 77 - </p> 78 - <AffectedMonitors 79 - monitors={incident.monitorsToIncidents.map( 80 - ({ monitor }) => monitor, 81 - )} 82 - /> 83 - </div> 77 + {Boolean(monitors.length) ? ( 78 + <div> 79 + <p className="text-muted-foreground mb-1.5 text-xs"> 80 + Affected Monitors 81 + </p> 82 + <AffectedMonitors monitors={monitors} /> 83 + </div> 84 + ) : null} 84 85 <div> 85 86 <p className="text-muted-foreground mb-1.5 text-xs"> 86 87 Last Updates
+34 -7
apps/web/src/app/status-page/[domain]/page.tsx
··· 1 1 import type { Metadata } from "next"; 2 + import Link from "next/link"; 2 3 import { notFound } from "next/navigation"; 4 + 5 + import { Button } from "@openstatus/ui"; 3 6 4 7 import { 5 8 defaultMetadata, 6 9 ogMetadata, 7 10 twitterMetadata, 8 11 } from "@/app/shared-metadata"; 12 + import { EmptyState } from "@/components/dashboard/empty-state"; 9 13 import { Header } from "@/components/dashboard/header"; 10 14 import { IncidentList } from "@/components/status-page/incident-list"; 11 15 import { MonitorList } from "@/components/status-page/monitor-list"; 12 16 import { api } from "@/trpc/server"; 13 17 18 + const url = 19 + process.env.NODE_ENV === "development" 20 + ? "http://localhost:3000" 21 + : "https://www.openstatus.dev"; 22 + 14 23 type Props = { 15 24 params: { domain: string }; 16 25 searchParams: { [key: string]: string | string[] | undefined }; ··· 24 33 if (!page) { 25 34 return notFound(); 26 35 } 36 + 37 + const isEmptyState = !( 38 + Boolean(page.monitors.length) || Boolean(page.incidents.length) 39 + ); 40 + 27 41 return ( 28 42 <div className="grid gap-6"> 29 43 <Header ··· 31 45 description={page.description} 32 46 className="text-left" 33 47 /> 34 - <MonitorList monitors={page.monitors} /> 35 - {page.monitors?.length > 0 ? ( 36 - <IncidentList 37 - incidents={page.incidents} 38 - monitors={page.monitors} 39 - context="latest" 48 + {isEmptyState ? ( 49 + <EmptyState 50 + icon="activity" 51 + title="Missing Monitors" 52 + description="Fill your status page with monitors." 53 + action={ 54 + <Button asChild> 55 + <Link href={`${url}/app`}>Go to Dashboard</Link> 56 + </Button> 57 + } 40 58 /> 41 - ) : null} 59 + ) : ( 60 + <> 61 + <MonitorList monitors={page.monitors} /> 62 + <IncidentList 63 + incidents={page.incidents} 64 + monitors={page.monitors} 65 + context="latest" 66 + /> 67 + </> 68 + )} 42 69 </div> 43 70 ); 44 71 }
+70 -2
apps/web/src/components/forms/incident-form.tsx
··· 6 6 import { useForm } from "react-hook-form"; 7 7 import * as z from "zod"; 8 8 9 - import type { allMonitorsExtendedSchema } from "@openstatus/db/src/schema"; 9 + import type { 10 + allMonitorsExtendedSchema, 11 + Page, 12 + } from "@openstatus/db/src/schema"; 10 13 import { 11 14 availableStatus, 12 15 insertIncidentSchema, ··· 57 60 interface Props { 58 61 defaultValues?: IncidentProps; 59 62 monitors?: MonitorsProps; 63 + pages?: Page[]; // 60 64 workspaceSlug: string; 61 65 } 62 66 63 67 export function IncidentForm({ 64 68 defaultValues, 65 69 monitors, 70 + pages, 66 71 workspaceSlug, 67 72 }: Props) { 68 73 const form = useForm<IncidentProps>({ ··· 72 77 title: defaultValues?.title || "", 73 78 status: defaultValues?.status || "investigating", 74 79 monitors: defaultValues?.monitors || [], 80 + pages: defaultValues?.pages || [], 75 81 workspaceSlug, 76 82 // include update on creation 77 83 message: "", ··· 190 196 <FormItem className="sm:col-span-full"> 191 197 <div className="mb-4"> 192 198 <FormLabel>Monitors</FormLabel> 199 + {/* TODO: second phrase can be set inside of a (?) tooltip */} 193 200 <FormDescription> 194 201 Select the monitors that you want to refer the incident 195 - to. 202 + to. It will be displayed on the status page they are 203 + attached to. 196 204 </FormDescription> 197 205 </div> 198 206 <div className="grid grid-cols-2 gap-4 sm:grid-cols-4"> ··· 237 245 : "bg-red-500", 238 246 )} 239 247 ></span> 248 + </div> 249 + <p className="text-muted-foreground truncate text-sm"> 250 + {item.description} 251 + </p> 252 + </div> 253 + </FormItem> 254 + ); 255 + }} 256 + /> 257 + ))} 258 + </div> 259 + <FormMessage /> 260 + </FormItem> 261 + )} 262 + /> 263 + <FormField 264 + control={form.control} 265 + name="pages" 266 + render={() => ( 267 + <FormItem className="sm:col-span-full"> 268 + <div className="mb-4"> 269 + <FormLabel>Pages</FormLabel> 270 + <FormDescription> 271 + Select the pages that you want to refer the incident to. 272 + </FormDescription> 273 + </div> 274 + <div className="grid grid-cols-2 gap-4 sm:grid-cols-4"> 275 + {pages?.map((item) => ( 276 + <FormField 277 + key={item.id} 278 + control={form.control} 279 + name="pages" 280 + render={({ field }) => { 281 + return ( 282 + <FormItem 283 + key={item.id} 284 + className="flex flex-row items-start space-x-3 space-y-0" 285 + > 286 + <FormControl> 287 + <Checkbox 288 + checked={field.value?.includes(item.id)} 289 + onCheckedChange={(checked) => { 290 + return checked 291 + ? field.onChange([ 292 + ...(field.value || []), 293 + item.id, 294 + ]) 295 + : field.onChange( 296 + field.value?.filter( 297 + (value) => value !== item.id, 298 + ), 299 + ); 300 + }} 301 + /> 302 + </FormControl> 303 + <div className="grid gap-1.5 leading-none"> 304 + <div className="flex items-center gap-2"> 305 + <FormLabel className="font-normal"> 306 + {item.title} 307 + </FormLabel> 240 308 </div> 241 309 <p className="text-muted-foreground truncate text-sm"> 242 310 {item.description}
+23 -15
apps/web/src/components/status-page/incident-list.tsx
··· 45 45 {context === "all" ? "All incidents" : "Latest incidents"} 46 46 </h2> 47 47 {_incidents.map((incident) => { 48 + const affectedMonitors = incident.monitorsToIncidents 49 + .map(({ monitorId }) => { 50 + const monitor = monitors.find(({ id }) => monitorId === id); 51 + return monitor || undefined; 52 + }) 53 + .filter(notEmpty); 48 54 return ( 49 55 <div key={incident.id} className="grid gap-4 text-left"> 50 56 <div className="max-w-3xl font-semibold"> 51 57 {incident.title} 52 58 <StatusBadge status={incident.status} className="ml-2" /> 53 59 </div> 54 - <div className="overflow-hidden text-ellipsis"> 55 - <p className="text-muted-foreground mb-2 text-xs"> 56 - Affected Monitors 57 - </p> 58 - <AffectedMonitors 59 - monitors={incident.monitorsToIncidents 60 - .map(({ monitorId }) => { 61 - const monitor = monitors.find( 62 - ({ id }) => monitorId === id, 63 - ); 64 - return monitor || undefined; 65 - }) 66 - .filter(notEmpty)} 67 - /> 68 - </div> 60 + {Boolean(affectedMonitors.length) ? ( 61 + <div className="overflow-hidden text-ellipsis"> 62 + <p className="text-muted-foreground mb-2 text-xs"> 63 + Affected Monitors 64 + </p> 65 + <AffectedMonitors 66 + monitors={incident.monitorsToIncidents 67 + .map(({ monitorId }) => { 68 + const monitor = monitors.find( 69 + ({ id }) => monitorId === id, 70 + ); 71 + return monitor || undefined; 72 + }) 73 + .filter(notEmpty)} 74 + /> 75 + </div> 76 + ) : null} 69 77 <div> 70 78 <p className="text-muted-foreground mb-2 text-xs"> 71 79 Latest Updates
+6 -26
apps/web/src/components/status-page/monitor-list.tsx
··· 1 - import Link from "next/link"; 2 1 import type { z } from "zod"; 3 2 4 3 import type { selectPublicMonitorSchema } from "@openstatus/db/src/schema"; 5 - import { Button } from "@openstatus/ui"; 6 4 7 - import { EmptyState } from "../dashboard/empty-state"; 8 5 import { Monitor } from "./monitor"; 9 6 10 7 export const MonitorList = ({ ··· 12 9 }: { 13 10 monitors: z.infer<typeof selectPublicMonitorSchema>[]; 14 11 }) => { 15 - const url = 16 - process.env.NODE_ENV === "development" 17 - ? "http://localhost:3000" 18 - : "https://www.openstatus.dev"; 19 12 return ( 20 13 <div className="grid gap-4"> 21 - {Boolean(monitors.length) ? ( 22 - monitors.map((monitor, index) => ( 23 - <div key={index}> 24 - {/* Fetch tracker and data */} 25 - <Monitor monitor={monitor} /> 26 - </div> 27 - )) 28 - ) : ( 29 - <EmptyState 30 - icon="activity" 31 - title="Missing Monitors" 32 - description="Fill your status page with monitors." 33 - action={ 34 - <Button asChild> 35 - <Link href={`${url}/app`}>Go to Dashboard</Link> 36 - </Button> 37 - } 38 - /> 39 - )} 14 + {monitors.map((monitor, index) => ( 15 + <div key={index}> 16 + {/* Fetch tracker and data */} 17 + <Monitor monitor={monitor} /> 18 + </div> 19 + ))} 40 20 </div> 41 21 ); 42 22 };
+71 -12
packages/api/src/router/incident.ts
··· 8 8 insertIncidentSchemaWithMonitors, 9 9 insertIncidentUpdateSchema, 10 10 monitorsToIncidents, 11 + pagesToIncidents, 11 12 selectIncidentSchema, 12 13 selectIncidentUpdateSchema, 13 14 selectMonitorSchema, ··· 30 31 }); 31 32 if (!result) return; 32 33 33 - const { id, workspaceSlug, monitors, date, ...incidentInput } = 34 + const { id, workspaceSlug, monitors, pages, date, ...incidentInput } = 34 35 opts.input; 35 36 36 37 const newIncident = await opts.ctx.db ··· 54 55 // await opts.ctx.db.insert(monitorsToIncidents).values(values).run(); 55 56 // } 56 57 57 - await opts.ctx.db 58 - .insert(monitorsToIncidents) 59 - .values( 60 - monitors.map((monitor) => ({ 61 - monitorId: monitor, 62 - incidentId: newIncident.id, 63 - })), 64 - ) 65 - .returning() 66 - .get(); 58 + if (monitors.length > 0) { 59 + await opts.ctx.db 60 + .insert(monitorsToIncidents) 61 + .values( 62 + monitors.map((monitor) => ({ 63 + monitorId: monitor, 64 + incidentId: newIncident.id, 65 + })), 66 + ) 67 + .returning() 68 + .get(); 69 + } 70 + 71 + if (pages.length > 0) { 72 + await opts.ctx.db 73 + .insert(pagesToIncidents) 74 + .values( 75 + pages.map((page) => ({ 76 + pageId: page, 77 + incidentId: newIncident.id, 78 + })), 79 + ) 80 + .returning() 81 + .get(); 82 + } 67 83 68 84 return newIncident; 69 85 }), ··· 103 119 }); 104 120 if (!data) return; 105 121 106 - const { monitors, workspaceSlug, ...incidentInput } = opts.input; 122 + const { monitors, pages, workspaceSlug, ...incidentInput } = opts.input; 107 123 108 124 if (!incidentInput.id) return; 109 125 ··· 155 171 .run(); 156 172 } 157 173 174 + const currentPagesToIncidents = await opts.ctx.db 175 + .select() 176 + .from(pagesToIncidents) 177 + .where(eq(pagesToIncidents.incidentId, currentIncident.id)) 178 + .all(); 179 + 180 + const currentPagesToIncidentsIds = currentPagesToIncidents.map( 181 + ({ pageId }) => pageId, 182 + ); 183 + 184 + const removedPages = currentPagesToIncidentsIds.filter( 185 + (x) => !pages?.includes(x), 186 + ); 187 + 188 + const addedPages = pages?.filter( 189 + (x) => !currentPagesToIncidentsIds?.includes(x), 190 + ); 191 + 192 + if (addedPages && addedPages.length > 0) { 193 + const values = addedPages.map((pageId) => ({ 194 + pageId, 195 + incidentId: currentIncident.id, 196 + })); 197 + 198 + await opts.ctx.db.insert(pagesToIncidents).values(values).run(); 199 + } 200 + 201 + if (removedPages && removedPages.length > 0) { 202 + await opts.ctx.db 203 + .delete(pagesToIncidents) 204 + .where( 205 + and( 206 + eq(pagesToIncidents.incidentId, currentIncident.id), 207 + inArray(pagesToIncidents.pageId, removedPages), 208 + ), 209 + ) 210 + .run(); 211 + } 212 + 158 213 return currentIncident; 159 214 }), 160 215 ··· 272 327 monitorsToIncidents: z 273 328 .array(z.object({ incidentId: z.number(), monitorId: z.number() })) 274 329 .default([]), 330 + pagesToIncidents: z 331 + .array(z.object({ incidentId: z.number(), pageId: z.number() })) 332 + .default([]), 275 333 incidentUpdates: z.array(selectIncidentUpdateSchema), 276 334 date: z.date().default(new Date()), 277 335 }); ··· 280 338 where: eq(incident.id, opts.input.id), 281 339 with: { 282 340 monitorsToIncidents: true, 341 + pagesToIncidents: true, 283 342 incidentUpdates: { 284 343 orderBy: (incidentUpdate, { desc }) => [ 285 344 desc(incidentUpdate.createdAt),
+24 -5
packages/api/src/router/page.ts
··· 2 2 import { z } from "zod"; 3 3 4 4 import { analytics, trackAnalytics } from "@openstatus/analytics"; 5 - import { and, eq, inArray, not, sql } from "@openstatus/db"; 5 + import { and, eq, inArray, not, or, sql } from "@openstatus/db"; 6 6 import { 7 7 incident, 8 8 insertPageSchemaWithMonitors, ··· 10 10 monitorsToIncidents, 11 11 monitorsToPages, 12 12 page, 13 + pagesToIncidents, 13 14 selectPublicPageSchemaWithRelation, 14 15 user, 15 16 usersToWorkspaces, ··· 287 288 .all() 288 289 : []; 289 290 290 - const incidentsId = monitorsToIncidentsResult.map( 291 + const incidentsToPagesResult = await opts.ctx.db 292 + .select() 293 + .from(pagesToIncidents) 294 + .where(eq(pagesToIncidents.pageId, result.id)) 295 + .all(); 296 + 297 + const monitorIncidentIds = monitorsToIncidentsResult.map( 291 298 ({ incidentId }) => incidentId, 292 299 ); 293 300 301 + const pageIncidentIds = incidentsToPagesResult.map( 302 + ({ incidentId }) => incidentId, 303 + ); 304 + 305 + const incidentIds = Array.from( 306 + new Set([...monitorIncidentIds, ...pageIncidentIds]), 307 + ); 308 + 294 309 const incidents = 295 - incidentsId.length > 0 310 + incidentIds.length > 0 296 311 ? await opts.ctx.db.query.incident.findMany({ 297 - where: and(inArray(incident.id, incidentsId)), 298 - with: { incidentUpdates: true, monitorsToIncidents: true }, 312 + where: or(inArray(incident.id, incidentIds)), 313 + with: { 314 + incidentUpdates: true, 315 + monitorsToIncidents: true, 316 + pagesToIncidents: true, 317 + }, 299 318 }) 300 319 : []; 301 320
+7
packages/db/drizzle/0009_small_maximus.sql
··· 1 + CREATE TABLE `incidents_to_pages` ( 2 + `page_id` integer NOT NULL, 3 + `incident_id` integer NOT NULL, 4 + PRIMARY KEY(`incident_id`, `page_id`), 5 + FOREIGN KEY (`page_id`) REFERENCES `page`(`id`) ON UPDATE no action ON DELETE cascade, 6 + FOREIGN KEY (`incident_id`) REFERENCES `incident`(`id`) ON UPDATE no action ON DELETE cascade 7 + );
+1016
packages/db/drizzle/meta/0009_snapshot.json
··· 1 + { 2 + "version": "5", 3 + "dialect": "sqlite", 4 + "id": "0f3dddb6-09cf-46a4-b12c-c5bb82475b94", 5 + "prevId": "72f58965-4d96-43e5-9101-3ceb67dac648", 6 + "tables": { 7 + "incident": { 8 + "name": "incident", 9 + "columns": { 10 + "id": { 11 + "name": "id", 12 + "type": "integer", 13 + "primaryKey": true, 14 + "notNull": true, 15 + "autoincrement": false 16 + }, 17 + "status": { 18 + "name": "status", 19 + "type": "text(4)", 20 + "primaryKey": false, 21 + "notNull": true, 22 + "autoincrement": false 23 + }, 24 + "title": { 25 + "name": "title", 26 + "type": "text(256)", 27 + "primaryKey": false, 28 + "notNull": true, 29 + "autoincrement": false 30 + }, 31 + "workspace_id": { 32 + "name": "workspace_id", 33 + "type": "integer", 34 + "primaryKey": false, 35 + "notNull": false, 36 + "autoincrement": false 37 + }, 38 + "created_at": { 39 + "name": "created_at", 40 + "type": "integer", 41 + "primaryKey": false, 42 + "notNull": false, 43 + "autoincrement": false, 44 + "default": "(strftime('%s', 'now'))" 45 + }, 46 + "updated_at": { 47 + "name": "updated_at", 48 + "type": "integer", 49 + "primaryKey": false, 50 + "notNull": false, 51 + "autoincrement": false, 52 + "default": "(strftime('%s', 'now'))" 53 + } 54 + }, 55 + "indexes": {}, 56 + "foreignKeys": { 57 + "incident_workspace_id_workspace_id_fk": { 58 + "name": "incident_workspace_id_workspace_id_fk", 59 + "tableFrom": "incident", 60 + "tableTo": "workspace", 61 + "columnsFrom": [ 62 + "workspace_id" 63 + ], 64 + "columnsTo": [ 65 + "id" 66 + ], 67 + "onDelete": "no action", 68 + "onUpdate": "no action" 69 + } 70 + }, 71 + "compositePrimaryKeys": {}, 72 + "uniqueConstraints": {} 73 + }, 74 + "incident_update": { 75 + "name": "incident_update", 76 + "columns": { 77 + "id": { 78 + "name": "id", 79 + "type": "integer", 80 + "primaryKey": true, 81 + "notNull": true, 82 + "autoincrement": false 83 + }, 84 + "status": { 85 + "name": "status", 86 + "type": "text(4)", 87 + "primaryKey": false, 88 + "notNull": true, 89 + "autoincrement": false 90 + }, 91 + "date": { 92 + "name": "date", 93 + "type": "integer", 94 + "primaryKey": false, 95 + "notNull": true, 96 + "autoincrement": false 97 + }, 98 + "message": { 99 + "name": "message", 100 + "type": "text", 101 + "primaryKey": false, 102 + "notNull": true, 103 + "autoincrement": false 104 + }, 105 + "incident_id": { 106 + "name": "incident_id", 107 + "type": "integer", 108 + "primaryKey": false, 109 + "notNull": true, 110 + "autoincrement": false 111 + }, 112 + "created_at": { 113 + "name": "created_at", 114 + "type": "integer", 115 + "primaryKey": false, 116 + "notNull": false, 117 + "autoincrement": false, 118 + "default": "(strftime('%s', 'now'))" 119 + }, 120 + "updated_at": { 121 + "name": "updated_at", 122 + "type": "integer", 123 + "primaryKey": false, 124 + "notNull": false, 125 + "autoincrement": false, 126 + "default": "(strftime('%s', 'now'))" 127 + } 128 + }, 129 + "indexes": {}, 130 + "foreignKeys": { 131 + "incident_update_incident_id_incident_id_fk": { 132 + "name": "incident_update_incident_id_incident_id_fk", 133 + "tableFrom": "incident_update", 134 + "tableTo": "incident", 135 + "columnsFrom": [ 136 + "incident_id" 137 + ], 138 + "columnsTo": [ 139 + "id" 140 + ], 141 + "onDelete": "cascade", 142 + "onUpdate": "no action" 143 + } 144 + }, 145 + "compositePrimaryKeys": {}, 146 + "uniqueConstraints": {} 147 + }, 148 + "incidents_to_monitors": { 149 + "name": "incidents_to_monitors", 150 + "columns": { 151 + "monitor_id": { 152 + "name": "monitor_id", 153 + "type": "integer", 154 + "primaryKey": false, 155 + "notNull": true, 156 + "autoincrement": false 157 + }, 158 + "incident_id": { 159 + "name": "incident_id", 160 + "type": "integer", 161 + "primaryKey": false, 162 + "notNull": true, 163 + "autoincrement": false 164 + } 165 + }, 166 + "indexes": {}, 167 + "foreignKeys": { 168 + "incidents_to_monitors_monitor_id_monitor_id_fk": { 169 + "name": "incidents_to_monitors_monitor_id_monitor_id_fk", 170 + "tableFrom": "incidents_to_monitors", 171 + "tableTo": "monitor", 172 + "columnsFrom": [ 173 + "monitor_id" 174 + ], 175 + "columnsTo": [ 176 + "id" 177 + ], 178 + "onDelete": "cascade", 179 + "onUpdate": "no action" 180 + }, 181 + "incidents_to_monitors_incident_id_incident_id_fk": { 182 + "name": "incidents_to_monitors_incident_id_incident_id_fk", 183 + "tableFrom": "incidents_to_monitors", 184 + "tableTo": "incident", 185 + "columnsFrom": [ 186 + "incident_id" 187 + ], 188 + "columnsTo": [ 189 + "id" 190 + ], 191 + "onDelete": "cascade", 192 + "onUpdate": "no action" 193 + } 194 + }, 195 + "compositePrimaryKeys": { 196 + "incidents_to_monitors_monitor_id_incident_id_pk": { 197 + "columns": [ 198 + "incident_id", 199 + "monitor_id" 200 + ] 201 + } 202 + }, 203 + "uniqueConstraints": {} 204 + }, 205 + "incidents_to_pages": { 206 + "name": "incidents_to_pages", 207 + "columns": { 208 + "page_id": { 209 + "name": "page_id", 210 + "type": "integer", 211 + "primaryKey": false, 212 + "notNull": true, 213 + "autoincrement": false 214 + }, 215 + "incident_id": { 216 + "name": "incident_id", 217 + "type": "integer", 218 + "primaryKey": false, 219 + "notNull": true, 220 + "autoincrement": false 221 + } 222 + }, 223 + "indexes": {}, 224 + "foreignKeys": { 225 + "incidents_to_pages_page_id_page_id_fk": { 226 + "name": "incidents_to_pages_page_id_page_id_fk", 227 + "tableFrom": "incidents_to_pages", 228 + "tableTo": "page", 229 + "columnsFrom": [ 230 + "page_id" 231 + ], 232 + "columnsTo": [ 233 + "id" 234 + ], 235 + "onDelete": "cascade", 236 + "onUpdate": "no action" 237 + }, 238 + "incidents_to_pages_incident_id_incident_id_fk": { 239 + "name": "incidents_to_pages_incident_id_incident_id_fk", 240 + "tableFrom": "incidents_to_pages", 241 + "tableTo": "incident", 242 + "columnsFrom": [ 243 + "incident_id" 244 + ], 245 + "columnsTo": [ 246 + "id" 247 + ], 248 + "onDelete": "cascade", 249 + "onUpdate": "no action" 250 + } 251 + }, 252 + "compositePrimaryKeys": { 253 + "incidents_to_pages_page_id_incident_id_pk": { 254 + "columns": [ 255 + "incident_id", 256 + "page_id" 257 + ] 258 + } 259 + }, 260 + "uniqueConstraints": {} 261 + }, 262 + "integration": { 263 + "name": "integration", 264 + "columns": { 265 + "id": { 266 + "name": "id", 267 + "type": "integer", 268 + "primaryKey": true, 269 + "notNull": true, 270 + "autoincrement": false 271 + }, 272 + "name": { 273 + "name": "name", 274 + "type": "text(256)", 275 + "primaryKey": false, 276 + "notNull": true, 277 + "autoincrement": false 278 + }, 279 + "workspace_id": { 280 + "name": "workspace_id", 281 + "type": "integer", 282 + "primaryKey": false, 283 + "notNull": false, 284 + "autoincrement": false 285 + }, 286 + "credential": { 287 + "name": "credential", 288 + "type": "text", 289 + "primaryKey": false, 290 + "notNull": false, 291 + "autoincrement": false 292 + }, 293 + "external_id": { 294 + "name": "external_id", 295 + "type": "text", 296 + "primaryKey": false, 297 + "notNull": true, 298 + "autoincrement": false 299 + }, 300 + "created_at": { 301 + "name": "created_at", 302 + "type": "integer", 303 + "primaryKey": false, 304 + "notNull": false, 305 + "autoincrement": false, 306 + "default": "(strftime('%s', 'now'))" 307 + }, 308 + "updated_at": { 309 + "name": "updated_at", 310 + "type": "integer", 311 + "primaryKey": false, 312 + "notNull": false, 313 + "autoincrement": false, 314 + "default": "(strftime('%s', 'now'))" 315 + }, 316 + "data": { 317 + "name": "data", 318 + "type": "text", 319 + "primaryKey": false, 320 + "notNull": true, 321 + "autoincrement": false 322 + } 323 + }, 324 + "indexes": {}, 325 + "foreignKeys": { 326 + "integration_workspace_id_workspace_id_fk": { 327 + "name": "integration_workspace_id_workspace_id_fk", 328 + "tableFrom": "integration", 329 + "tableTo": "workspace", 330 + "columnsFrom": [ 331 + "workspace_id" 332 + ], 333 + "columnsTo": [ 334 + "id" 335 + ], 336 + "onDelete": "no action", 337 + "onUpdate": "no action" 338 + } 339 + }, 340 + "compositePrimaryKeys": {}, 341 + "uniqueConstraints": {} 342 + }, 343 + "page": { 344 + "name": "page", 345 + "columns": { 346 + "id": { 347 + "name": "id", 348 + "type": "integer", 349 + "primaryKey": true, 350 + "notNull": true, 351 + "autoincrement": false 352 + }, 353 + "workspace_id": { 354 + "name": "workspace_id", 355 + "type": "integer", 356 + "primaryKey": false, 357 + "notNull": true, 358 + "autoincrement": false 359 + }, 360 + "title": { 361 + "name": "title", 362 + "type": "text", 363 + "primaryKey": false, 364 + "notNull": true, 365 + "autoincrement": false 366 + }, 367 + "description": { 368 + "name": "description", 369 + "type": "text", 370 + "primaryKey": false, 371 + "notNull": true, 372 + "autoincrement": false 373 + }, 374 + "icon": { 375 + "name": "icon", 376 + "type": "text(256)", 377 + "primaryKey": false, 378 + "notNull": false, 379 + "autoincrement": false, 380 + "default": "''" 381 + }, 382 + "slug": { 383 + "name": "slug", 384 + "type": "text(256)", 385 + "primaryKey": false, 386 + "notNull": true, 387 + "autoincrement": false 388 + }, 389 + "custom_domain": { 390 + "name": "custom_domain", 391 + "type": "text(256)", 392 + "primaryKey": false, 393 + "notNull": true, 394 + "autoincrement": false 395 + }, 396 + "published": { 397 + "name": "published", 398 + "type": "integer", 399 + "primaryKey": false, 400 + "notNull": false, 401 + "autoincrement": false, 402 + "default": false 403 + }, 404 + "created_at": { 405 + "name": "created_at", 406 + "type": "integer", 407 + "primaryKey": false, 408 + "notNull": false, 409 + "autoincrement": false, 410 + "default": "(strftime('%s', 'now'))" 411 + }, 412 + "updated_at": { 413 + "name": "updated_at", 414 + "type": "integer", 415 + "primaryKey": false, 416 + "notNull": false, 417 + "autoincrement": false, 418 + "default": "(strftime('%s', 'now'))" 419 + } 420 + }, 421 + "indexes": { 422 + "page_slug_unique": { 423 + "name": "page_slug_unique", 424 + "columns": [ 425 + "slug" 426 + ], 427 + "isUnique": true 428 + } 429 + }, 430 + "foreignKeys": { 431 + "page_workspace_id_workspace_id_fk": { 432 + "name": "page_workspace_id_workspace_id_fk", 433 + "tableFrom": "page", 434 + "tableTo": "workspace", 435 + "columnsFrom": [ 436 + "workspace_id" 437 + ], 438 + "columnsTo": [ 439 + "id" 440 + ], 441 + "onDelete": "cascade", 442 + "onUpdate": "no action" 443 + } 444 + }, 445 + "compositePrimaryKeys": {}, 446 + "uniqueConstraints": {} 447 + }, 448 + "monitor": { 449 + "name": "monitor", 450 + "columns": { 451 + "id": { 452 + "name": "id", 453 + "type": "integer", 454 + "primaryKey": true, 455 + "notNull": true, 456 + "autoincrement": false 457 + }, 458 + "job_type": { 459 + "name": "job_type", 460 + "type": "text(3)", 461 + "primaryKey": false, 462 + "notNull": true, 463 + "autoincrement": false, 464 + "default": "'other'" 465 + }, 466 + "periodicity": { 467 + "name": "periodicity", 468 + "type": "text(6)", 469 + "primaryKey": false, 470 + "notNull": true, 471 + "autoincrement": false, 472 + "default": "'other'" 473 + }, 474 + "status": { 475 + "name": "status", 476 + "type": "text(2)", 477 + "primaryKey": false, 478 + "notNull": true, 479 + "autoincrement": false, 480 + "default": "'active'" 481 + }, 482 + "active": { 483 + "name": "active", 484 + "type": "integer", 485 + "primaryKey": false, 486 + "notNull": false, 487 + "autoincrement": false, 488 + "default": false 489 + }, 490 + "regions": { 491 + "name": "regions", 492 + "type": "text", 493 + "primaryKey": false, 494 + "notNull": true, 495 + "autoincrement": false, 496 + "default": "''" 497 + }, 498 + "url": { 499 + "name": "url", 500 + "type": "text(2048)", 501 + "primaryKey": false, 502 + "notNull": true, 503 + "autoincrement": false 504 + }, 505 + "name": { 506 + "name": "name", 507 + "type": "text(256)", 508 + "primaryKey": false, 509 + "notNull": true, 510 + "autoincrement": false, 511 + "default": "''" 512 + }, 513 + "description": { 514 + "name": "description", 515 + "type": "text", 516 + "primaryKey": false, 517 + "notNull": true, 518 + "autoincrement": false, 519 + "default": "''" 520 + }, 521 + "headers": { 522 + "name": "headers", 523 + "type": "text", 524 + "primaryKey": false, 525 + "notNull": false, 526 + "autoincrement": false, 527 + "default": "''" 528 + }, 529 + "body": { 530 + "name": "body", 531 + "type": "text", 532 + "primaryKey": false, 533 + "notNull": false, 534 + "autoincrement": false, 535 + "default": "''" 536 + }, 537 + "method": { 538 + "name": "method", 539 + "type": "text(2)", 540 + "primaryKey": false, 541 + "notNull": false, 542 + "autoincrement": false, 543 + "default": "'GET'" 544 + }, 545 + "workspace_id": { 546 + "name": "workspace_id", 547 + "type": "integer", 548 + "primaryKey": false, 549 + "notNull": false, 550 + "autoincrement": false 551 + }, 552 + "created_at": { 553 + "name": "created_at", 554 + "type": "integer", 555 + "primaryKey": false, 556 + "notNull": false, 557 + "autoincrement": false, 558 + "default": "(strftime('%s', 'now'))" 559 + }, 560 + "updated_at": { 561 + "name": "updated_at", 562 + "type": "integer", 563 + "primaryKey": false, 564 + "notNull": false, 565 + "autoincrement": false, 566 + "default": "(strftime('%s', 'now'))" 567 + } 568 + }, 569 + "indexes": {}, 570 + "foreignKeys": { 571 + "monitor_workspace_id_workspace_id_fk": { 572 + "name": "monitor_workspace_id_workspace_id_fk", 573 + "tableFrom": "monitor", 574 + "tableTo": "workspace", 575 + "columnsFrom": [ 576 + "workspace_id" 577 + ], 578 + "columnsTo": [ 579 + "id" 580 + ], 581 + "onDelete": "no action", 582 + "onUpdate": "no action" 583 + } 584 + }, 585 + "compositePrimaryKeys": {}, 586 + "uniqueConstraints": {} 587 + }, 588 + "monitors_to_pages": { 589 + "name": "monitors_to_pages", 590 + "columns": { 591 + "monitor_id": { 592 + "name": "monitor_id", 593 + "type": "integer", 594 + "primaryKey": false, 595 + "notNull": true, 596 + "autoincrement": false 597 + }, 598 + "page_id": { 599 + "name": "page_id", 600 + "type": "integer", 601 + "primaryKey": false, 602 + "notNull": true, 603 + "autoincrement": false 604 + } 605 + }, 606 + "indexes": {}, 607 + "foreignKeys": { 608 + "monitors_to_pages_monitor_id_monitor_id_fk": { 609 + "name": "monitors_to_pages_monitor_id_monitor_id_fk", 610 + "tableFrom": "monitors_to_pages", 611 + "tableTo": "monitor", 612 + "columnsFrom": [ 613 + "monitor_id" 614 + ], 615 + "columnsTo": [ 616 + "id" 617 + ], 618 + "onDelete": "cascade", 619 + "onUpdate": "no action" 620 + }, 621 + "monitors_to_pages_page_id_page_id_fk": { 622 + "name": "monitors_to_pages_page_id_page_id_fk", 623 + "tableFrom": "monitors_to_pages", 624 + "tableTo": "page", 625 + "columnsFrom": [ 626 + "page_id" 627 + ], 628 + "columnsTo": [ 629 + "id" 630 + ], 631 + "onDelete": "cascade", 632 + "onUpdate": "no action" 633 + } 634 + }, 635 + "compositePrimaryKeys": { 636 + "monitors_to_pages_monitor_id_page_id_pk": { 637 + "columns": [ 638 + "monitor_id", 639 + "page_id" 640 + ] 641 + } 642 + }, 643 + "uniqueConstraints": {} 644 + }, 645 + "user": { 646 + "name": "user", 647 + "columns": { 648 + "id": { 649 + "name": "id", 650 + "type": "integer", 651 + "primaryKey": true, 652 + "notNull": true, 653 + "autoincrement": false 654 + }, 655 + "tenant_id": { 656 + "name": "tenant_id", 657 + "type": "text(256)", 658 + "primaryKey": false, 659 + "notNull": false, 660 + "autoincrement": false 661 + }, 662 + "first_name": { 663 + "name": "first_name", 664 + "type": "text", 665 + "primaryKey": false, 666 + "notNull": false, 667 + "autoincrement": false, 668 + "default": "''" 669 + }, 670 + "last_name": { 671 + "name": "last_name", 672 + "type": "text", 673 + "primaryKey": false, 674 + "notNull": false, 675 + "autoincrement": false, 676 + "default": "''" 677 + }, 678 + "email": { 679 + "name": "email", 680 + "type": "text", 681 + "primaryKey": false, 682 + "notNull": false, 683 + "autoincrement": false, 684 + "default": "''" 685 + }, 686 + "photo_url": { 687 + "name": "photo_url", 688 + "type": "text", 689 + "primaryKey": false, 690 + "notNull": false, 691 + "autoincrement": false, 692 + "default": "''" 693 + }, 694 + "created_at": { 695 + "name": "created_at", 696 + "type": "integer", 697 + "primaryKey": false, 698 + "notNull": false, 699 + "autoincrement": false, 700 + "default": "(strftime('%s', 'now'))" 701 + }, 702 + "updated_at": { 703 + "name": "updated_at", 704 + "type": "integer", 705 + "primaryKey": false, 706 + "notNull": false, 707 + "autoincrement": false, 708 + "default": "(strftime('%s', 'now'))" 709 + } 710 + }, 711 + "indexes": { 712 + "user_tenant_id_unique": { 713 + "name": "user_tenant_id_unique", 714 + "columns": [ 715 + "tenant_id" 716 + ], 717 + "isUnique": true 718 + } 719 + }, 720 + "foreignKeys": {}, 721 + "compositePrimaryKeys": {}, 722 + "uniqueConstraints": {} 723 + }, 724 + "users_to_workspaces": { 725 + "name": "users_to_workspaces", 726 + "columns": { 727 + "user_id": { 728 + "name": "user_id", 729 + "type": "integer", 730 + "primaryKey": false, 731 + "notNull": true, 732 + "autoincrement": false 733 + }, 734 + "workspace_id": { 735 + "name": "workspace_id", 736 + "type": "integer", 737 + "primaryKey": false, 738 + "notNull": true, 739 + "autoincrement": false 740 + } 741 + }, 742 + "indexes": {}, 743 + "foreignKeys": { 744 + "users_to_workspaces_user_id_user_id_fk": { 745 + "name": "users_to_workspaces_user_id_user_id_fk", 746 + "tableFrom": "users_to_workspaces", 747 + "tableTo": "user", 748 + "columnsFrom": [ 749 + "user_id" 750 + ], 751 + "columnsTo": [ 752 + "id" 753 + ], 754 + "onDelete": "no action", 755 + "onUpdate": "no action" 756 + }, 757 + "users_to_workspaces_workspace_id_workspace_id_fk": { 758 + "name": "users_to_workspaces_workspace_id_workspace_id_fk", 759 + "tableFrom": "users_to_workspaces", 760 + "tableTo": "workspace", 761 + "columnsFrom": [ 762 + "workspace_id" 763 + ], 764 + "columnsTo": [ 765 + "id" 766 + ], 767 + "onDelete": "no action", 768 + "onUpdate": "no action" 769 + } 770 + }, 771 + "compositePrimaryKeys": { 772 + "users_to_workspaces_user_id_workspace_id_pk": { 773 + "columns": [ 774 + "user_id", 775 + "workspace_id" 776 + ] 777 + } 778 + }, 779 + "uniqueConstraints": {} 780 + }, 781 + "workspace": { 782 + "name": "workspace", 783 + "columns": { 784 + "id": { 785 + "name": "id", 786 + "type": "integer", 787 + "primaryKey": true, 788 + "notNull": true, 789 + "autoincrement": false 790 + }, 791 + "slug": { 792 + "name": "slug", 793 + "type": "text", 794 + "primaryKey": false, 795 + "notNull": true, 796 + "autoincrement": false 797 + }, 798 + "name": { 799 + "name": "name", 800 + "type": "text", 801 + "primaryKey": false, 802 + "notNull": false, 803 + "autoincrement": false 804 + }, 805 + "stripe_id": { 806 + "name": "stripe_id", 807 + "type": "text(256)", 808 + "primaryKey": false, 809 + "notNull": false, 810 + "autoincrement": false 811 + }, 812 + "subscription_id": { 813 + "name": "subscription_id", 814 + "type": "text", 815 + "primaryKey": false, 816 + "notNull": false, 817 + "autoincrement": false 818 + }, 819 + "plan": { 820 + "name": "plan", 821 + "type": "text(2)", 822 + "primaryKey": false, 823 + "notNull": false, 824 + "autoincrement": false 825 + }, 826 + "ends_at": { 827 + "name": "ends_at", 828 + "type": "integer", 829 + "primaryKey": false, 830 + "notNull": false, 831 + "autoincrement": false 832 + }, 833 + "paid_until": { 834 + "name": "paid_until", 835 + "type": "integer", 836 + "primaryKey": false, 837 + "notNull": false, 838 + "autoincrement": false 839 + }, 840 + "created_at": { 841 + "name": "created_at", 842 + "type": "integer", 843 + "primaryKey": false, 844 + "notNull": false, 845 + "autoincrement": false, 846 + "default": "(strftime('%s', 'now'))" 847 + }, 848 + "updated_at": { 849 + "name": "updated_at", 850 + "type": "integer", 851 + "primaryKey": false, 852 + "notNull": false, 853 + "autoincrement": false, 854 + "default": "(strftime('%s', 'now'))" 855 + } 856 + }, 857 + "indexes": { 858 + "workspace_slug_unique": { 859 + "name": "workspace_slug_unique", 860 + "columns": [ 861 + "slug" 862 + ], 863 + "isUnique": true 864 + }, 865 + "workspace_stripe_id_unique": { 866 + "name": "workspace_stripe_id_unique", 867 + "columns": [ 868 + "stripe_id" 869 + ], 870 + "isUnique": true 871 + } 872 + }, 873 + "foreignKeys": {}, 874 + "compositePrimaryKeys": {}, 875 + "uniqueConstraints": {} 876 + }, 877 + "notification": { 878 + "name": "notification", 879 + "columns": { 880 + "id": { 881 + "name": "id", 882 + "type": "integer", 883 + "primaryKey": true, 884 + "notNull": true, 885 + "autoincrement": false 886 + }, 887 + "name": { 888 + "name": "name", 889 + "type": "text", 890 + "primaryKey": false, 891 + "notNull": true, 892 + "autoincrement": false 893 + }, 894 + "provider": { 895 + "name": "provider", 896 + "type": "text", 897 + "primaryKey": false, 898 + "notNull": true, 899 + "autoincrement": false 900 + }, 901 + "data": { 902 + "name": "data", 903 + "type": "text", 904 + "primaryKey": false, 905 + "notNull": false, 906 + "autoincrement": false, 907 + "default": "'{}'" 908 + }, 909 + "workspace_id": { 910 + "name": "workspace_id", 911 + "type": "integer", 912 + "primaryKey": false, 913 + "notNull": false, 914 + "autoincrement": false 915 + }, 916 + "created_at": { 917 + "name": "created_at", 918 + "type": "integer", 919 + "primaryKey": false, 920 + "notNull": false, 921 + "autoincrement": false, 922 + "default": "(strftime('%s', 'now'))" 923 + }, 924 + "updated_at": { 925 + "name": "updated_at", 926 + "type": "integer", 927 + "primaryKey": false, 928 + "notNull": false, 929 + "autoincrement": false, 930 + "default": "(strftime('%s', 'now'))" 931 + } 932 + }, 933 + "indexes": {}, 934 + "foreignKeys": { 935 + "notification_workspace_id_workspace_id_fk": { 936 + "name": "notification_workspace_id_workspace_id_fk", 937 + "tableFrom": "notification", 938 + "tableTo": "workspace", 939 + "columnsFrom": [ 940 + "workspace_id" 941 + ], 942 + "columnsTo": [ 943 + "id" 944 + ], 945 + "onDelete": "no action", 946 + "onUpdate": "no action" 947 + } 948 + }, 949 + "compositePrimaryKeys": {}, 950 + "uniqueConstraints": {} 951 + }, 952 + "notifications_to_monitors": { 953 + "name": "notifications_to_monitors", 954 + "columns": { 955 + "monitor_id": { 956 + "name": "monitor_id", 957 + "type": "integer", 958 + "primaryKey": false, 959 + "notNull": true, 960 + "autoincrement": false 961 + }, 962 + "notification_id": { 963 + "name": "notification_id", 964 + "type": "integer", 965 + "primaryKey": false, 966 + "notNull": true, 967 + "autoincrement": false 968 + } 969 + }, 970 + "indexes": {}, 971 + "foreignKeys": { 972 + "notifications_to_monitors_monitor_id_monitor_id_fk": { 973 + "name": "notifications_to_monitors_monitor_id_monitor_id_fk", 974 + "tableFrom": "notifications_to_monitors", 975 + "tableTo": "monitor", 976 + "columnsFrom": [ 977 + "monitor_id" 978 + ], 979 + "columnsTo": [ 980 + "id" 981 + ], 982 + "onDelete": "cascade", 983 + "onUpdate": "no action" 984 + }, 985 + "notifications_to_monitors_notification_id_notification_id_fk": { 986 + "name": "notifications_to_monitors_notification_id_notification_id_fk", 987 + "tableFrom": "notifications_to_monitors", 988 + "tableTo": "notification", 989 + "columnsFrom": [ 990 + "notification_id" 991 + ], 992 + "columnsTo": [ 993 + "id" 994 + ], 995 + "onDelete": "cascade", 996 + "onUpdate": "no action" 997 + } 998 + }, 999 + "compositePrimaryKeys": { 1000 + "notifications_to_monitors_monitor_id_notification_id_pk": { 1001 + "columns": [ 1002 + "monitor_id", 1003 + "notification_id" 1004 + ] 1005 + } 1006 + }, 1007 + "uniqueConstraints": {} 1008 + } 1009 + }, 1010 + "enums": {}, 1011 + "_meta": { 1012 + "schemas": {}, 1013 + "tables": {}, 1014 + "columns": {} 1015 + } 1016 + }
+7
packages/db/drizzle/meta/_journal.json
··· 64 64 "when": 1695756345957, 65 65 "tag": "0008_overjoyed_sunset_bain", 66 66 "breakpoints": true 67 + }, 68 + { 69 + "idx": 9, 70 + "version": "5", 71 + "when": 1697285841283, 72 + "tag": "0009_small_maximus", 73 + "breakpoints": true 67 74 } 68 75 ] 69 76 }
+35 -3
packages/db/src/schema/incident.ts
··· 9 9 import * as z from "zod"; 10 10 11 11 import { monitor } from "./monitor"; 12 + import { page } from "./page"; 12 13 import { workspace } from "./workspace"; 13 14 14 15 export const availableStatus = [ ··· 57 58 58 59 export const incidentRelations = relations(incident, ({ one, many }) => ({ 59 60 monitorsToIncidents: many(monitorsToIncidents), 61 + pagesToIncidents: many(pagesToIncidents), 60 62 incidentUpdates: many(incidentUpdate), 61 63 workspace: one(workspace, { 62 64 fields: [incident.workspaceId], ··· 100 102 }), 101 103 ); 102 104 105 + export const pagesToIncidents = sqliteTable( 106 + "incidents_to_pages", 107 + { 108 + pageId: integer("page_id") 109 + .notNull() 110 + .references(() => page.id, { onDelete: "cascade" }), 111 + incidentId: integer("incident_id") 112 + .notNull() 113 + .references(() => incident.id, { onDelete: "cascade" }), 114 + }, 115 + (t) => ({ 116 + pk: primaryKey(t.pageId, t.incidentId), 117 + }), 118 + ); 119 + 120 + export const pagesToIncidentsRelations = relations( 121 + pagesToIncidents, 122 + ({ one }) => ({ 123 + page: one(page, { 124 + fields: [pagesToIncidents.pageId], 125 + references: [page.id], 126 + }), 127 + incident: one(incident, { 128 + fields: [pagesToIncidents.incidentId], 129 + references: [incident.id], 130 + }), 131 + }), 132 + ); 133 + 103 134 // Schema for inserting a Incident - can be used to validate API requests 104 135 export const insertIncidentSchema = createInsertSchema(incident).extend({ 105 136 title: z.string().default(""), ··· 108 139 date: z.date().optional().default(new Date()), 109 140 // date: z.number().optional().default(new Date().getTime()), 110 141 workspaceSlug: z.string(), 111 - monitors: z.number().array().min(1), 142 + monitors: z.number().array(), 143 + pages: z.number().array(), 112 144 }); 113 145 114 146 export const insertIncidentUpdateSchema = createInsertSchema( ··· 125 157 incidentUpdates: insertIncidentUpdateSchema.array(), 126 158 }); 127 159 128 - // TODO: remove! 160 + // TODO: remove!!! 129 161 export const insertIncidentSchemaWithMonitors = insertIncidentSchema.extend({ 130 - monitors: z.number().array().min(1), 162 + monitors: z.number().array(), 131 163 }); 132 164 133 165 export const selectIncidentSchema = createSelectSchema(incident).extend({
+2
packages/db/src/schema/page.ts
··· 3 3 import { createInsertSchema, createSelectSchema } from "drizzle-zod"; 4 4 import { z } from "zod"; 5 5 6 + import { pagesToIncidents } from "./incident"; 6 7 import { monitorsToPages } from "./monitor"; 7 8 import { workspace } from "./workspace"; 8 9 ··· 30 31 31 32 export const pageRelations = relations(page, ({ many, one }) => ({ 32 33 monitorsToPages: many(monitorsToPages), 34 + pagesToIncidents: many(pagesToIncidents), 33 35 workspace: one(workspace, { 34 36 fields: [page.workspaceId], 35 37 references: [workspace.id],