From 50148cc2f4ab18e548236abbd9177a442ea67465 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Mon, 31 Mar 2025 20:26:26 -0700 Subject: [PATCH] implement RedisNotificationRepository --- .../RedisNotificationRepository.ts | 96 +++++++++++++++++-- .../RedisNotificationRepositoryTests.test.ts | 6 -- 2 files changed, 88 insertions(+), 14 deletions(-) delete mode 100644 test/repositories/RedisNotificationRepositoryTests.test.ts diff --git a/src/repositories/RedisNotificationRepository.ts b/src/repositories/RedisNotificationRepository.ts index 76d5765..1690c50 100644 --- a/src/repositories/RedisNotificationRepository.ts +++ b/src/repositories/RedisNotificationRepository.ts @@ -1,5 +1,7 @@ +import { TupleKey } from '../types/TupleKey'; import { Listener, + NotificationEvent, NotificationLookupArguments, NotificationRepository, ScheduledNotification @@ -7,6 +9,9 @@ import { import { createClient } from "redis"; export class RedisNotificationRepository implements NotificationRepository { + private listeners: Listener[] = []; + private readonly NOTIFICATION_KEY_PREFIX = 'notification:'; + constructor( private redisClient = createClient({ url: process.env.REDIS_URL, @@ -37,27 +42,102 @@ export class RedisNotificationRepository implements NotificationRepository { await this.redisClient.flushAll(); } + private getNotificationKey(shuttleId: string, stopId: string): string { + const tuple = new TupleKey(shuttleId, stopId); + return `${this.NOTIFICATION_KEY_PREFIX}${tuple.toString()}`; + } + public async addOrUpdateNotification(notification: ScheduledNotification): Promise { + const { shuttleId, stopId, deviceId, secondsThreshold } = notification; + const key = this.getNotificationKey(shuttleId, stopId); + + await this.redisClient.hSet(key, deviceId, secondsThreshold.toString()); + + this.listeners.forEach((listener: Listener) => { + const event: NotificationEvent = { + event: 'addOrUpdate', + notification + }; + listener(event); + }); } public async deleteNotificationIfExists(lookupArguments: NotificationLookupArguments): Promise { + const { shuttleId, stopId, deviceId } = lookupArguments; + const key = this.getNotificationKey(shuttleId, stopId); + + const secondsThreshold = await this.redisClient.hGet(key, deviceId); + if (secondsThreshold) { + await this.redisClient.hDel(key, deviceId); + + // Check if hash is empty and delete it if so + const remainingFields = await this.redisClient.hLen(key); + if (remainingFields === 0) { + await this.redisClient.del(key); + } + + this.listeners.forEach((listener) => { + const event: NotificationEvent = { + event: 'delete', + notification: { + deviceId, + shuttleId, + stopId, + secondsThreshold: parseInt(secondsThreshold) + } + }; + listener(event); + }); + } } - public async getAllNotificationsForShuttleAndStopId(shuttleId: string, stopId: string): Promise { - return []; + public async getAllNotificationsForShuttleAndStopId( + shuttleId: string, + stopId: string + ): Promise { + const key = this.getNotificationKey(shuttleId, stopId); + const allNotifications = await this.redisClient.hGetAll(key); + + return Object.entries(allNotifications).map(([deviceId, secondsThreshold]) => ({ + shuttleId, + stopId, + deviceId, + secondsThreshold: parseInt(secondsThreshold) + })); } - public async getSecondsThresholdForNotificationIfExists(lookupArguments: NotificationLookupArguments): Promise { - return null; + public async getSecondsThresholdForNotificationIfExists( + lookupArguments: NotificationLookupArguments + ): Promise { + const { shuttleId, stopId, deviceId } = lookupArguments; + const key = this.getNotificationKey(shuttleId, stopId); + + const threshold = await this.redisClient.hGet(key, deviceId); + return threshold ? parseInt(threshold) : null; } - public async isNotificationScheduled(lookupArguments: NotificationLookupArguments): Promise { - return false; + public async isNotificationScheduled( + lookupArguments: NotificationLookupArguments + ): Promise { + const threshold = await this.getSecondsThresholdForNotificationIfExists(lookupArguments); + return threshold !== null; } - subscribeToNotificationChanges(listener: Listener): void { + public subscribeToNotificationChanges(listener: Listener): void { + const index = this.listeners.findIndex( + (existingListener) => existingListener === listener + ); + if (index < 0) { + this.listeners.push(listener); + } } - unsubscribeFromNotificationChanges(listener: Listener): void { + public unsubscribeFromNotificationChanges(listener: Listener): void { + const index = this.listeners.findIndex( + (existingListener) => existingListener === listener + ); + if (index >= 0) { + this.listeners.splice(index, 1); + } } } diff --git a/test/repositories/RedisNotificationRepositoryTests.test.ts b/test/repositories/RedisNotificationRepositoryTests.test.ts deleted file mode 100644 index 7a3dcaf..0000000 --- a/test/repositories/RedisNotificationRepositoryTests.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -// Test additional edge cases like Redis failing to connect, etc. - -import { describe, it } from "@jest/globals"; - -describe("RedisNotificationRepository", () => { -});