import { logger } from "~/services/logger.server"; import { BasePresenter } from "./basePresenter.server"; import { type RuntimeEnvironmentType, type ProjectAlertChannel } from "@trigger.dev/database"; import { decryptSecret } from "~/services/secrets/secretStore.server"; import { env } from "~/env.server"; import { ProjectAlertEmailProperties, ProjectAlertSlackProperties, ProjectAlertWebhookProperties, } from "~/models/projectAlert.server"; import { getLimit } from "~/services/platform.v3.server"; export type AlertChannelListPresenterData = Awaited>; export type AlertChannelListPresenterRecord = AlertChannelListPresenterData["alertChannels"][number]; export type AlertChannelListPresenterAlertProperties = NonNullable< AlertChannelListPresenterRecord["properties"] >; export class AlertChannelListPresenter extends BasePresenter { public async call(projectId: string, environmentType?: RuntimeEnvironmentType) { logger.debug("AlertChannelListPresenter", { projectId }); const alertChannels = await this._prisma.projectAlertChannel.findMany({ where: { projectId, }, orderBy: { createdAt: "desc", }, }); const organization = await this._replica.project.findFirst({ where: { id: projectId, }, select: { organizationId: true, }, }); if (!organization) { throw new Error(`Project not found: ${projectId}`); } const limit = await getLimit(organization.organizationId, "alerts", 100_000_000); const relevantChannels = alertChannels.filter((channel) => { if (!environmentType) return true; return channel.environmentTypes.includes(environmentType); }); return { alertChannels: await Promise.all( relevantChannels.map(async (alertChannel) => ({ ...alertChannel, properties: await this.#presentProperties(alertChannel), })) ), limits: { used: alertChannels.length, limit, }, }; } async #presentProperties(alertChannel: ProjectAlertChannel) { if (!alertChannel.properties) { return; } switch (alertChannel.type) { case "WEBHOOK": const parsedProperties = ProjectAlertWebhookProperties.parse(alertChannel.properties); const secret = await decryptSecret(env.ENCRYPTION_KEY, parsedProperties.secret); return { type: "WEBHOOK" as const, url: parsedProperties.url, secret, }; case "EMAIL": return { type: "EMAIL" as const, ...ProjectAlertEmailProperties.parse(alertChannel.properties), }; case "SLACK": { return { type: "SLACK" as const, ...ProjectAlertSlackProperties.parse(alertChannel.properties), }; } default: throw new Error(`Unsupported alert channel type: ${alertChannel.type}`); } } }