Openstatus www.openstatus.dev
at main 254 lines 7.5 kB view raw
1import { z } from "zod"; 2 3import { 4 type SQL, 5 and, 6 asc, 7 desc, 8 eq, 9 gte, 10 inArray, 11 syncMaintenanceToPageComponentDeleteByMaintenance, 12 syncMaintenanceToPageComponentInsertMany, 13} from "@openstatus/db"; 14import { 15 maintenance, 16 maintenancesToPageComponents, 17 pageComponent, 18 selectMaintenanceSchema, 19 selectPageComponentSchema, 20} from "@openstatus/db/src/schema"; 21 22import { Events } from "@openstatus/analytics"; 23import { TRPCError } from "@trpc/server"; 24import { createTRPCRouter, protectedProcedure } from "../trpc"; 25import { getPeriodDate, periods } from "./utils"; 26 27export const maintenanceRouter = createTRPCRouter({ 28 delete: protectedProcedure 29 .meta({ track: Events.DeleteMaintenance }) 30 .input(z.object({ id: z.number() })) 31 .mutation(async (opts) => { 32 return await opts.ctx.db 33 .delete(maintenance) 34 .where( 35 and( 36 eq(maintenance.id, opts.input.id), 37 eq(maintenance.workspaceId, opts.ctx.workspace.id), 38 ), 39 ) 40 .returning(); 41 }), 42 43 list: protectedProcedure 44 .input( 45 z 46 .object({ 47 period: z.enum(periods).optional(), 48 pageId: z.number().optional(), 49 order: z.enum(["asc", "desc"]).optional(), 50 }) 51 .optional(), 52 ) 53 .query(async (opts) => { 54 const whereConditions: SQL[] = [ 55 eq(maintenance.workspaceId, opts.ctx.workspace.id), 56 ]; 57 58 if (opts.input?.period) { 59 whereConditions.push( 60 gte(maintenance.createdAt, getPeriodDate(opts.input.period)), 61 ); 62 } 63 64 if (opts.input?.pageId) { 65 whereConditions.push(eq(maintenance.pageId, opts.input.pageId)); 66 } 67 68 const query = opts.ctx.db.query.maintenance.findMany({ 69 where: and(...whereConditions), 70 orderBy: 71 opts.input?.order === "asc" 72 ? asc(maintenance.createdAt) 73 : desc(maintenance.createdAt), 74 with: { 75 maintenancesToPageComponents: { with: { pageComponent: true } }, 76 }, 77 }); 78 79 const result = await query; 80 81 return selectMaintenanceSchema 82 .extend({ 83 pageComponents: z.array(selectPageComponentSchema).prefault([]), 84 }) 85 .array() 86 .parse( 87 result.map((m) => ({ 88 ...m, 89 pageComponents: m.maintenancesToPageComponents.map( 90 ({ pageComponent }) => pageComponent, 91 ), 92 })), 93 ); 94 }), 95 96 new: protectedProcedure 97 .meta({ track: Events.CreateMaintenance }) 98 .input( 99 z.object({ 100 pageId: z.number(), 101 title: z.string(), 102 message: z.string(), 103 startDate: z.coerce.date(), 104 endDate: z.coerce.date(), 105 pageComponents: z.array(z.number()).optional(), 106 notifySubscribers: z.boolean().nullish(), 107 }), 108 ) 109 .mutation(async (opts) => { 110 // Check if the user has access to the monitors 111 if (opts.input.pageComponents?.length) { 112 const whereConditions: SQL[] = [ 113 eq(pageComponent.workspaceId, opts.ctx.workspace.id), 114 inArray(pageComponent.id, opts.input.pageComponents), 115 ]; 116 const pageComponents = await opts.ctx.db 117 .select() 118 .from(pageComponent) 119 .where(and(...whereConditions)) 120 .all(); 121 122 if (pageComponents.length !== opts.input.pageComponents.length) { 123 throw new TRPCError({ 124 code: "BAD_REQUEST", 125 message: "You do not have access to all the page components", 126 }); 127 } 128 } 129 130 const newMaintenance = await opts.ctx.db.transaction(async (tx) => { 131 const newMaintenance = await tx 132 .insert(maintenance) 133 .values({ 134 pageId: opts.input.pageId, 135 workspaceId: opts.ctx.workspace.id, 136 title: opts.input.title, 137 message: opts.input.message, 138 from: opts.input.startDate, 139 to: opts.input.endDate, 140 }) 141 .returning() 142 .get(); 143 144 if (opts.input.pageComponents?.length) { 145 await tx.insert(maintenancesToPageComponents).values( 146 opts.input.pageComponents.map((pageComponentId) => ({ 147 maintenanceId: newMaintenance.id, 148 pageComponentId, 149 })), 150 ); 151 // Sync to monitors (inverse sync for backward compatibility) 152 await syncMaintenanceToPageComponentInsertMany( 153 tx, 154 newMaintenance.id, 155 opts.input.pageComponents, 156 ); 157 } 158 159 return newMaintenance; 160 }); 161 162 return { 163 ...newMaintenance, 164 notifySubscribers: opts.input.notifySubscribers, 165 }; 166 }), 167 168 update: protectedProcedure 169 .meta({ track: Events.UpdateMaintenance }) 170 .input( 171 z.object({ 172 id: z.number(), 173 title: z.string(), 174 message: z.string(), 175 startDate: z.coerce.date(), 176 endDate: z.coerce.date(), 177 pageComponents: z.array(z.number()).optional(), 178 }), 179 ) 180 .mutation(async (opts) => { 181 // Check if the user has access to the monitors 182 if (opts.input.pageComponents?.length) { 183 const whereConditions: SQL[] = [ 184 eq(pageComponent.workspaceId, opts.ctx.workspace.id), 185 inArray(pageComponent.id, opts.input.pageComponents), 186 ]; 187 const pageComponents = await opts.ctx.db 188 .select() 189 .from(pageComponent) 190 .where(and(...whereConditions)) 191 .all(); 192 193 if (pageComponents.length !== opts.input.pageComponents.length) { 194 throw new TRPCError({ 195 code: "BAD_REQUEST", 196 message: "You do not have access to all the page components", 197 }); 198 } 199 } 200 201 await opts.ctx.db.transaction(async (tx) => { 202 const whereConditions: SQL[] = [ 203 eq(maintenance.id, opts.input.id), 204 eq(maintenance.workspaceId, opts.ctx.workspace.id), 205 ]; 206 207 // Update the maintenance 208 const _maintenance = await tx 209 .update(maintenance) 210 .set({ 211 title: opts.input.title, 212 message: opts.input.message, 213 from: opts.input.startDate, 214 to: opts.input.endDate, 215 workspaceId: opts.ctx.workspace.id, 216 updatedAt: new Date(), 217 }) 218 .where(and(...whereConditions)) 219 .returning() 220 .get(); 221 222 // Delete all existing relations 223 await tx 224 .delete(maintenancesToPageComponents) 225 .where( 226 eq(maintenancesToPageComponents.maintenanceId, _maintenance.id), 227 ) 228 .run(); 229 // Sync to monitors (inverse sync for backward compatibility) 230 await syncMaintenanceToPageComponentDeleteByMaintenance( 231 tx, 232 _maintenance.id, 233 ); 234 235 // Create new relations if page components are provided 236 if (opts.input.pageComponents?.length) { 237 await tx.insert(maintenancesToPageComponents).values( 238 opts.input.pageComponents.map((pageComponentId) => ({ 239 maintenanceId: _maintenance.id, 240 pageComponentId, 241 })), 242 ); 243 // Sync to monitors (inverse sync for backward compatibility) 244 await syncMaintenanceToPageComponentInsertMany( 245 tx, 246 _maintenance.id, 247 opts.input.pageComponents, 248 ); 249 } 250 251 return _maintenance; 252 }); 253 }), 254});