diff --git a/app/Services/MeService.ts b/app/Services/MeService.ts index 0343a3b..a0a8595 100644 --- a/app/Services/MeService.ts +++ b/app/Services/MeService.ts @@ -77,11 +77,16 @@ export default class MeService { }); } + const notifications = await Ws.notificationStore.getNotifications(user.id); + return { success: true, status: HttpStatusEnum.OK, message: 'Fetched authorized user data.', - data: user, + data: { + ...user.toJSON(), + notifications, + }, }; } diff --git a/app/Services/WsService.ts b/app/Services/WsService.ts index 731016f..fb08d15 100644 --- a/app/Services/WsService.ts +++ b/app/Services/WsService.ts @@ -18,6 +18,7 @@ import AuthMiddleware from 'App/Middleware/Ws/Auth'; */ import RedisSessionStore from 'App/Store/SessionStore'; import RedisMessageStore from 'App/Store/MessageStore'; +import RedisNotificationStore from 'App/Store/NotificationStore'; export interface ServerToClientEvents { /** @@ -61,6 +62,8 @@ class WsService { public messageStore: RedisMessageStore; + public notificationStore: RedisNotificationStore; + public booted = false; public boot() { @@ -81,6 +84,7 @@ class WsService { */ this.sessionStore = new RedisSessionStore(Redis.connection('session')); this.messageStore = new RedisMessageStore(Redis.connection('message')); + this.notificationStore = new RedisNotificationStore(Redis.connection('notification')); /** * Clean up sessions before start @@ -125,6 +129,7 @@ class WsService { }; await this.messageStore.add(userId, to, message); + await this.notificationStore.add(to, `You received a new message.`); this.io.to(to).emit('chat:message', message); } }); diff --git a/app/Store/NotificationStore.ts b/app/Store/NotificationStore.ts new file mode 100644 index 0000000..9009ac5 --- /dev/null +++ b/app/Store/NotificationStore.ts @@ -0,0 +1,53 @@ +import { RedisConnectionContract } from '@ioc:Adonis/Addons/Redis'; +import Logger from '@ioc:Adonis/Core/Logger'; +import { nanoid } from 'nanoid'; + +const NOTIFICATION_TTL = 185 * 24 * 60 * 60; // 185 days + +type NotificationType = { + content: string; + time: string; +}; + +class RedisNotificationStore { + public redisClient: RedisConnectionContract; + + constructor(redisClient: RedisConnectionContract) { + this.redisClient = redisClient; + } + + public async add(userId: string, message: string): Promise<'OK' | 'ERROR'> { + try { + const key = `notification:${nanoid()}`; + const timestamp = Date.now(); + + await this.redisClient.zadd(`user:${userId}`, timestamp, key); + await this.redisClient + .multi() + .hmset(key, { + content: message, + time: new Date(timestamp).toISOString(), + }) + .expire(key, NOTIFICATION_TTL) + .exec(); + + return 'OK'; + } catch (error) { + Logger.error(error); + return 'ERROR'; + } + } + + public async getNotifications(userId: string) { + const keys = await this.redisClient.zrevrangebyscore(`user:${userId}`, '+inf', '-inf'); + const notifications = await Promise.all(keys.map(key => this.get(key))); + return notifications; + } + + private async get(key: string): Promise { + const notification = (await this.redisClient.hgetall(key)) as NotificationType; + return notification; + } +} + +export default RedisNotificationStore; diff --git a/config/redis.ts b/config/redis.ts index 075fa67..805b5bc 100644 --- a/config/redis.ts +++ b/config/redis.ts @@ -59,6 +59,14 @@ const redisConfig: RedisConfig = { db: 2, healthCheck: true, }, + notification: { + host: Env.get('REDIS_HOST'), + port: Env.get('REDIS_PORT'), + password: Env.get('REDIS_PASSWORD', ''), + keyPrefix: '', + db: 3, + healthCheck: true, + }, }, }; diff --git a/contracts/redis.ts b/contracts/redis.ts index 698585a..c124ecc 100644 --- a/contracts/redis.ts +++ b/contracts/redis.ts @@ -10,5 +10,6 @@ declare module '@ioc:Adonis/Addons/Redis' { local: RedisConnectionConfig; session: RedisConnectionConfig; message: RedisConnectionConfig; + notification: RedisConnectionConfig; } }