From 5d12bf5595e314f0bcf1ccc2e0b267fb43ec8c37 Mon Sep 17 00:00:00 2001 From: K09-0 <71677059+K09-0@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:19:05 +0500 Subject: [PATCH 1/6] Update schema.ts --- packages/db/src/drizzle/schema.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/db/src/drizzle/schema.ts b/packages/db/src/drizzle/schema.ts index 214ed8384..5462c06f3 100644 --- a/packages/db/src/drizzle/schema.ts +++ b/packages/db/src/drizzle/schema.ts @@ -1116,3 +1116,12 @@ export type Feedback = typeof feedback.$inferSelect; export type FeedbackInsert = typeof feedback.$inferInsert; export type FeedbackRedemption = typeof feedbackRedemptions.$inferSelect; export type FeedbackRedemptionInsert = typeof feedbackRedemptions.$inferInsert; +// --- ALARMS SYSTEM --- +export const alarms = pgTable("alarms", { + id: text("id").primaryKey(), // ID аларма (обычно генерируется через createId) + websiteId: text("website_id").notNullable(), // Привязка к сайту + channels: jsonb("channels").$type().notNullable().default([]), // ['slack', 'discord', 'email'] + triggerConditions: jsonb("trigger_conditions").notNullable(), // Условия срабатывания + createdAt: timestamp("created_at").defaultNow().notNullable(), + updatedAt: timestamp("updated_at").defaultNow().notNullable(), +}); From 416f6029ce8713b63d1b02ed66e736cef8384148 Mon Sep 17 00:00:00 2001 From: K09-0 <71677059+K09-0@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:28:20 +0500 Subject: [PATCH 2/6] Update schema.ts Add alarms table schema From 3e909f30f444795cf11e67e1f1c32276243e7158 Mon Sep 17 00:00:00 2001 From: K09-0 <71677059+K09-0@users.noreply.github.com> Date: Mon, 23 Mar 2026 16:48:24 +0500 Subject: [PATCH 3/6] Refactor alarms router and validation schemas Implement alarms router with CRUD operations (list, get, create, update, delete) --- packages/rpc/src/routers/alarms.ts | 87 ++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 packages/rpc/src/routers/alarms.ts diff --git a/packages/rpc/src/routers/alarms.ts b/packages/rpc/src/routers/alarms.ts new file mode 100644 index 000000000..dd619dffe --- /dev/null +++ b/packages/rpc/src/routers/alarms.ts @@ -0,0 +1,87 @@ +import { z } from "zod"; +import { eq } from "drizzle-orm"; +import { protectedProcedure, router } from "../trpc"; +import { alarms } from "@databuddy/db/schema"; +import { createId } from "@paralleldrive/cuid2"; + +export const createAlarmSchema = z.object({ + websiteId: z.string().min(1), + channels: z.array(z.enum(["slack", "discord", "email"])).min(1), + triggerConditions: z.object({ + status: z.enum(["down", "up", "degraded"]), + durationMinutes: z.number().int().min(1).default(5), + }), +}); + +export const updateAlarmSchema = z.object({ + id: z.string().min(1), + data: createAlarmSchema.partial(), +}); + +export const alarmsRouter = router({ + list: protectedProcedure + .input(z.object({ websiteId: z.string() })) + .query(async ({ ctx, input }) => { + return await ctx.db + .select() + .from(alarms) + .where(eq(alarms.websiteId, input.websiteId)); + }), + + get: protectedProcedure + .input(z.object({ id: z.string() })) + .query(async ({ ctx, input }) => { + const result = await ctx.db + .select() + .from(alarms) + .where(eq(alarms.id, input.id)) + .limit(1); + + if (!result.length) throw new Error("Alarm not found"); + return result[0]; + }), + + create: protectedProcedure + .input(createAlarmSchema) + .mutation(async ({ ctx, input }) => { + const newAlarm = await ctx.db + .insert(alarms) + .values({ + id: createId(), + websiteId: input.websiteId, + channels: input.channels, + triggerConditions: input.triggerConditions, + }) + .returning(); + + return newAlarm[0]; + }), + + update: protectedProcedure + .input(updateAlarmSchema) + .mutation(async ({ ctx, input }) => { + const updatedAlarm = await ctx.db + .update(alarms) + .set({ + ...input.data, + updatedAt: new Date(), + }) + .where(eq(alarms.id, input.id)) + .returning(); + + if (!updatedAlarm.length) throw new Error("Alarm not found"); + return updatedAlarm[0]; + }), + + delete: protectedProcedure + .input(z.object({ id: z.string() })) + .mutation(async ({ ctx, input }) => { + const deletedAlarm = await ctx.db + .delete(alarms) + .where(eq(alarms.id, input.id)) + .returning(); + + if (!deletedAlarm.length) throw new Error("Alarm not found"); + return deletedAlarm[0]; + }), +}); From 9bba76c912243a7610dc6ab7a8748ddb7da694fd Mon Sep 17 00:00:00 2001 From: K09-0 <71677059+K09-0@users.noreply.github.com> Date: Mon, 23 Mar 2026 17:30:37 +0500 Subject: [PATCH 4/6] Create notifications.ts Implement notification trigger logic --- packages/db/src/drizzle/notifications.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 packages/db/src/drizzle/notifications.ts diff --git a/packages/db/src/drizzle/notifications.ts b/packages/db/src/drizzle/notifications.ts new file mode 100644 index 000000000..1ce9ddb3d --- /dev/null +++ b/packages/db/src/drizzle/notifications.ts @@ -0,0 +1,22 @@ +import { alarms } from "@databuddy/db/schema"; +import { eq, and } from "drizzle-orm"; + +export const sendNotification = async (db: any, websiteId: string, status: string) => { + const siteAlarms = await db + .select() + .from(alarms) + .where( + and( + eq(alarms.websiteId, websiteId), + // Условие: проверяем статус внутри JSON triggerConditions + eq(alarms.triggerConditions, { status: status }) + ) + ); + + for (const alarm of siteAlarms) { + // В консоль для логов сервера + console.log(`[Alarm] Site ${websiteId} is ${status}. Notifying: ${alarm.channels.join(", ")}`); + + // Интеграция с провайдерами (Slack/Discord) будет добавлена в следующих итерациях + } +}; From 0d5662ac3641d2aa392d4d3aaf257501db58f807 Mon Sep 17 00:00:00 2001 From: K09-0 <71677059+K09-0@users.noreply.github.com> Date: Mon, 23 Mar 2026 17:53:25 +0500 Subject: [PATCH 5/6] Integrate notifications for schedule status changes Implement notification trigger logic --- packages/rpc/src/routers/uptime.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/rpc/src/routers/uptime.ts b/packages/rpc/src/routers/uptime.ts index 6c2c2c6e3..2934521ac 100644 --- a/packages/rpc/src/routers/uptime.ts +++ b/packages/rpc/src/routers/uptime.ts @@ -6,6 +6,7 @@ import { z } from "zod"; import { rpcError } from "../errors"; import { protectedProcedure } from "../orpc"; import { withWorkspace } from "../procedures/with-workspace"; +import { sendNotification } from "./notifications"; const client = new Client({ token: process.env.UPSTASH_QSTASH_TOKEN }); @@ -406,6 +407,10 @@ export const uptimeRouter = { .set({ isPaused: input.pause, updatedAt: new Date() }) .where(eq(uptimeSchedules.id, input.scheduleId)), ]); + + // ВЫЗОВ УВЕДОМЛЕНИЯ ПРИ СМЕНЕ СТАТУСА (ПАУЗА/РЕЗЮМЕ) + await sendNotification(db, schedule.websiteId ?? "", input.pause ? "paused" : "active"); + } catch (error) { logger.error( { scheduleId: input.scheduleId, error }, @@ -447,6 +452,9 @@ export const uptimeRouter = { .set({ isPaused: true, updatedAt: new Date() }) .where(eq(uptimeSchedules.id, input.scheduleId)), ]); + + await sendNotification(db, schedule.websiteId ?? "", "paused"); + } catch (error) { logger.error( { scheduleId: input.scheduleId, error }, @@ -484,6 +492,9 @@ export const uptimeRouter = { .set({ isPaused: false, updatedAt: new Date() }) .where(eq(uptimeSchedules.id, input.scheduleId)), ]); + + await sendNotification(db, schedule.websiteId ?? "", "active"); + } catch (error) { logger.error( { scheduleId: input.scheduleId, error }, From a6ec718d75bad7ce9aab63014b662619c06526da Mon Sep 17 00:00:00 2001 From: K09-0 <71677059+K09-0@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:26:42 +0500 Subject: [PATCH 6/6] Update uptime.ts --- packages/rpc/src/routers/uptime.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rpc/src/routers/uptime.ts b/packages/rpc/src/routers/uptime.ts index 2934521ac..02d1c7400 100644 --- a/packages/rpc/src/routers/uptime.ts +++ b/packages/rpc/src/routers/uptime.ts @@ -6,7 +6,7 @@ import { z } from "zod"; import { rpcError } from "../errors"; import { protectedProcedure } from "../orpc"; import { withWorkspace } from "../procedures/with-workspace"; -import { sendNotification } from "./notifications"; +import { sendNotification } from "../../../db/src/drizzle/notifications"; const client = new Client({ token: process.env.UPSTASH_QSTASH_TOKEN });