diff --git a/app/Controllers/Http/Api/v1/ChatController.ts b/app/Controllers/Http/Api/v1/ChatController.ts new file mode 100644 index 0000000..7f9e3ad --- /dev/null +++ b/app/Controllers/Http/Api/v1/ChatController.ts @@ -0,0 +1,38 @@ +import { inject, Ioc } from '@adonisjs/core/build/standalone'; +import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'; +import { ConversationType } from 'App/Store/MessageStore'; +import IResponse from 'App/Datatypes/Interfaces/IResponse'; + +/** + * Enums + */ +import HttpStatusEnum from 'App/Datatypes/Enums/HttpStatusEnum'; + +/** + * Services + */ +import WsService from 'App/Services/WsService'; + +import BaseController from '../../BaseController'; + +@inject() +export default class ChatController extends BaseController { + /** + * Get list of conversations (users) + * GET /chat/conversations + */ + public async getConversations(ctx: HttpContextContract) { + const user = await ctx.auth.authenticate(); + const data = await WsService.messageStore.getConversations(user.id); + const result: IResponse = { + success: true, + status: HttpStatusEnum.OK, + message: 'Fetched conversations.', + data, + }; + + return this.sendResponse(ctx, result.data, result.message, result.status); + } +} + +new Ioc().make(ChatController); diff --git a/app/Store/MessageStore.ts b/app/Store/MessageStore.ts index 1403b0b..a15e71e 100644 --- a/app/Store/MessageStore.ts +++ b/app/Store/MessageStore.ts @@ -1,5 +1,6 @@ import { RedisConnectionContract } from '@ioc:Adonis/Addons/Redis'; import Logger from '@ioc:Adonis/Core/Logger'; +import User from 'App/Models/User'; import { DateTime } from 'luxon'; import { nanoid } from 'nanoid'; @@ -10,6 +11,12 @@ type MessageDataType = { time: string; }; +export type ConversationType = { + userId: string; + fullname: string; + lastMessage: MessageDataType; +}; + const MESSAGE_TTL = 185 * 24 * 60 * 60; // 185 days const MAX_MESSAGES_HISTORY = 100; @@ -62,6 +69,40 @@ class RedisMessageStore { return messages.sort((m1, m2) => new Date(m1.time).valueOf() - new Date(m2.time).valueOf()); } + public async getConversations(userId: string): Promise { + /** + * Getting conversations that had some message history + */ + const conversations = await this.redisClient.keys(`conversation:*${userId}*`); + const userIds = conversations + .map(id => id.split(':')[1]) + .map(id => (userId !== id.split('&')[0] ? id.split('&')[0] : id.split('&')[1])); + + /** + * Fetch this users + */ + const users = await User.query().select('id', 'first_name', 'last_name').where('id', 'in', userIds); + + /** + * Fetch last message of each conversation + */ + const result = await Promise.all( + users.map(async u => { + const convKey = this.createConversationKey(userId, u.id); + const msgKey = await this.redisClient.lindex(convKey, 0); + const lastMessage = await this.getMessage(`message:${msgKey}`); + + return { + userId: u.id, + fullname: `${u.first_name} ${u.last_name}`, + lastMessage, + }; + }) + ); + + return result; + } + private async getMessage(key: string): Promise { const message = (await this.redisClient.hgetall(key)) as MessageDataType; return message; @@ -70,7 +111,7 @@ class RedisMessageStore { // eslint-disable-next-line class-methods-use-this private createConversationKey(userId1: string, userId2: string): string { const [id1, id2] = [userId1, userId2].sort(); - return `conversation:${id1}-${id2}`; + return `conversation:${id1}&${id2}`; } } diff --git a/routes/apis/v1/chat.ts b/routes/apis/v1/chat.ts new file mode 100644 index 0000000..0be2749 --- /dev/null +++ b/routes/apis/v1/chat.ts @@ -0,0 +1,7 @@ +import Route from '@ioc:Adonis/Core/Route'; + +Route.group(() => { + Route.get('/conversations', 'Api/v1/ChatController.getConversations') + .middleware('role:admin,teacher,student') + .as('chat.get-conversations'); +}).prefix('chat'); diff --git a/routes/apis/v1/index.ts b/routes/apis/v1/index.ts index a967e66..9b10d4f 100644 --- a/routes/apis/v1/index.ts +++ b/routes/apis/v1/index.ts @@ -68,6 +68,11 @@ Route.group(() => { * Stat module */ require('./stat'); + + /** + * Chat module + */ + require('./chat'); }).middleware('auth'); }) .prefix('v1')