From 3039fb9b696fd29821f593e80b4bbce18041080d Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 25 Mar 2026 13:50:46 -0700 Subject: [PATCH] nottificaitons repo --- .../TimescaleNotificationsRepository.ts | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 server/src/repositories/notifications/TimescaleNotificationsRepository.ts diff --git a/server/src/repositories/notifications/TimescaleNotificationsRepository.ts b/server/src/repositories/notifications/TimescaleNotificationsRepository.ts new file mode 100644 index 000000000..b2c369c86 --- /dev/null +++ b/server/src/repositories/notifications/TimescaleNotificationsRepository.ts @@ -0,0 +1,140 @@ +import type { Pool } from "pg"; +import { INotificationsRepository } from "@/repositories/notifications/INotificationsRepository.js"; +import type { Notification, NotificationChannel } from "@/types/notification.js"; +import { AppError } from "@/utils/AppError.js"; + +interface NotificationRow { + id: string; + user_id: string; + team_id: string; + type: NotificationChannel; + notification_name: string; + address: string | null; + phone: string | null; + homeserver_url: string | null; + room_id: string | null; + access_token: string | null; + created_at: Date; + updated_at: Date; +} + +const COLUMNS = `id, user_id, team_id, type, notification_name, address, phone, + homeserver_url, room_id, access_token, created_at, updated_at`; + +export class TimescaleNotificationsRepository implements INotificationsRepository { + constructor(private pool: Pool) {} + + create = async (data: Partial): Promise => { + const result = await this.pool.query( + `INSERT INTO notifications (user_id, team_id, type, notification_name, address, phone, homeserver_url, room_id, access_token) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) + RETURNING ${COLUMNS}`, + [ + data.userId, + data.teamId, + data.type, + data.notificationName, + data.address ?? null, + data.phone ?? null, + data.homeserverUrl ?? null, + data.roomId ?? null, + data.accessToken ?? null, + ] + ); + const row = result.rows[0]; + if (!row) { + throw new AppError({ message: "Failed to create notification", status: 500 }); + } + return this.toEntity(row); + }; + + findById = async (id: string, teamId: string): Promise => { + const result = await this.pool.query(`SELECT ${COLUMNS} FROM notifications WHERE id = $1 AND team_id = $2`, [id, teamId]); + const row = result.rows[0]; + if (!row) { + throw new AppError({ message: "Notification not found", status: 404 }); + } + return this.toEntity(row); + }; + + findNotificationsByIds = async (ids: string[]): Promise => { + if (!ids.length) { + return []; + } + const result = await this.pool.query(`SELECT ${COLUMNS} FROM notifications WHERE id = ANY($1)`, [ids]); + return result.rows.map(this.toEntity); + }; + + findByTeamId = async (teamId: string): Promise => { + const result = await this.pool.query(`SELECT ${COLUMNS} FROM notifications WHERE team_id = $1`, [teamId]); + return result.rows.map(this.toEntity); + }; + + updateById = async (id: string, teamId: string, patch: Partial): Promise => { + const sets: string[] = []; + const values: unknown[] = []; + let paramIndex = 1; + + const fieldMap: [keyof Notification, string][] = [ + ["type", "type"], + ["notificationName", "notification_name"], + ["address", "address"], + ["phone", "phone"], + ["homeserverUrl", "homeserver_url"], + ["roomId", "room_id"], + ["accessToken", "access_token"], + ]; + + for (const [key, column] of fieldMap) { + if (patch[key] !== undefined) { + sets.push(`${column} = $${paramIndex++}`); + values.push(patch[key]); + } + } + + if (sets.length === 0) { + return this.findById(id, teamId); + } + + sets.push(`updated_at = NOW()`); + values.push(id, teamId); + + const result = await this.pool.query( + `UPDATE notifications SET ${sets.join(", ")} WHERE id = $${paramIndex++} AND team_id = $${paramIndex} + RETURNING ${COLUMNS}`, + values + ); + const row = result.rows[0]; + if (!row) { + throw new AppError({ message: "Notification not found or could not be updated", status: 404 }); + } + return this.toEntity(row); + }; + + deleteById = async (id: string, teamId: string): Promise => { + const result = await this.pool.query(`DELETE FROM notifications WHERE id = $1 AND team_id = $2 RETURNING ${COLUMNS}`, [ + id, + teamId, + ]); + const row = result.rows[0]; + if (!row) { + throw new AppError({ message: "Notification not found or could not be deleted", status: 404 }); + } + return this.toEntity(row); + }; + + private toEntity = (row: NotificationRow): Notification => ({ + id: row.id, + userId: row.user_id, + teamId: row.team_id, + type: row.type, + notificationName: row.notification_name, + address: row.address ?? undefined, + phone: row.phone ?? undefined, + homeserverUrl: row.homeserver_url ?? undefined, + roomId: row.room_id ?? undefined, + accessToken: row.access_token ?? undefined, + createdAt: row.created_at.toISOString(), + updatedAt: row.updated_at.toISOString(), + }); +}