import { type ProjectAlertChannel, type ProjectAlertChannelType, type ProjectAlertType, } from "@trigger.dev/database"; import assertNever from "assert-never"; import { z } from "zod"; import { env } from "~/env.server"; import { ProjectAlertEmailProperties, ProjectAlertWebhookProperties, } from "~/models/projectAlert.server"; import { decryptSecret } from "~/services/secrets/secretStore.server"; export const ApiAlertType = z.enum([ "run_failure", "attempt_failure", "deployment_failure", "deployment_success", "error_group", ]); export type ApiAlertType = z.infer; export const ApiAlertEnvironmentType = z.enum(["STAGING", "PRODUCTION"]); export type ApiAlertEnvironmentType = z.infer; export const ApiAlertChannel = z.enum(["email", "webhook"]); export type ApiAlertChannel = z.infer; export const ApiAlertChannelData = z.object({ email: z.string().optional(), url: z.string().optional(), secret: z.string().optional(), }); export type ApiAlertChannelData = z.infer; export const ApiCreateAlertChannel = z.object({ alertTypes: ApiAlertType.array(), name: z.string(), channel: ApiAlertChannel, channelData: ApiAlertChannelData, deduplicationKey: z.string().optional(), environmentTypes: ApiAlertEnvironmentType.array().default(["STAGING", "PRODUCTION"]), }); export type ApiCreateAlertChannel = z.infer; export const ApiAlertChannelObject = z.object({ id: z.string(), name: z.string(), alertTypes: ApiAlertType.array(), channel: ApiAlertChannel, channelData: ApiAlertChannelData, deduplicationKey: z.string().optional(), }); export type ApiAlertChannelObject = z.infer; export class ApiAlertChannelPresenter { public static async alertChannelToApi( alertChannel: ProjectAlertChannel ): Promise { return { id: alertChannel.friendlyId, name: alertChannel.name, alertTypes: alertChannel.alertTypes.map((type) => this.alertTypeToApi(type)), channel: this.alertChannelTypeToApi(alertChannel.type), channelData: await channelDataFromProperties(alertChannel.type, alertChannel.properties), deduplicationKey: alertChannel.userProvidedDeduplicationKey ? alertChannel.deduplicationKey : undefined, }; } public static alertTypeToApi(alertType: ProjectAlertType): ApiAlertType { switch (alertType) { case "TASK_RUN": return "run_failure"; case "TASK_RUN_ATTEMPT": return "attempt_failure"; case "DEPLOYMENT_FAILURE": return "deployment_failure"; case "DEPLOYMENT_SUCCESS": return "deployment_success"; case "ERROR_GROUP": return "error_group"; default: assertNever(alertType); } } public static alertTypeFromApi(alertType: ApiAlertType): ProjectAlertType { switch (alertType) { case "run_failure": return "TASK_RUN"; case "attempt_failure": return "TASK_RUN_ATTEMPT"; case "deployment_failure": return "DEPLOYMENT_FAILURE"; case "deployment_success": return "DEPLOYMENT_SUCCESS"; case "error_group": return "ERROR_GROUP"; default: assertNever(alertType); } } public static alertChannelTypeToApi(type: ProjectAlertChannelType): ApiAlertChannel { switch (type) { case "EMAIL": return "email"; case "WEBHOOK": return "webhook"; case "SLACK": throw new Error("Slack channels are not supported"); default: assertNever(type); } } } async function channelDataFromProperties( type: ProjectAlertChannelType, properties: ProjectAlertChannel["properties"] ): Promise { if (!properties) { return {}; } switch (type) { case "EMAIL": return ProjectAlertEmailProperties.parse(properties); case "WEBHOOK": const { url, secret } = ProjectAlertWebhookProperties.parse(properties); return { url, secret: await decryptSecret(env.ENCRYPTION_KEY, secret), }; case "SLACK": throw new Error("Slack channels are not supported"); default: assertNever(type); } }