import { Conversation, BaseConversation } from "../entities/Conversation"; import { DatabaseHelper } from "./DatabaseHelper"; import { time } from "../utils/DateUtils"; import { ConversationMessage, BaseConversationMessage } from "../entities/ConversationMessage"; /** * Conversations helper * * @author Pierre HUBERT */ const LIST_TABLE = "comunic_conversations_list"; const USERS_TABLE = "comunic_conversations_users"; const MESSAGES_TABLE = "comunic_conversations_messages"; export class ConversationsHelper { /** * Create a new conversation * * @param conv Information about the conversation to create */ public static async Create(conv : BaseConversation) : Promise { // Create the conversation in the main table const convID = await DatabaseHelper.InsertRow(LIST_TABLE, { "user_id": conv.ownerID, "name": conv.name, "last_active": time(), "creation_time": time() }); // Add the members to the conversation for (const userID of conv.members) { await this.AddMember( convID, userID, conv.ownerID == userID ? conv.following : true); } return convID; } /** * Add a member to a conversation * * @param convID Conversation ID * @param userID User ID * @param following Specify whether the user is following * the conversation or not */ private static async AddMember(convID : number, userID: number, following : boolean = true) { await DatabaseHelper.InsertRow( USERS_TABLE, { "conv_id": convID, "user_id": userID, "time_add": time(), "following": following ? 1 : 0, "saw_last_message": 1 } ); } /** * Remove a user from a conversation * * @param convID Conversation ID * @param userID ID of the user to remove */ private static async RemoveMember(convID: number, userID: number) { await DatabaseHelper.DeleteRows(USERS_TABLE, { conv_id: convID, user_id: userID }); } /** * Get the list of conversations of the user * * @param userID Target user ID */ public static async GetListUser(userID: number) : Promise> { // Fetch the list of conversations const result = await DatabaseHelper.Query({ fields: [ "*", "l.id as id", "l.user_id as owner_id" // The field conflits with user.user_id ], table: LIST_TABLE + " l", joins: [ // Joins with conversation members table { table: USERS_TABLE + " u", condition: "l.id = u.conv_id" } ], where: { "u.user_id": userID }, order: "l.last_active DESC" }); const list = []; for (const el of result) { list.push(await this.DBToConversationInfo(el)); } return list; } /** * Get information about a single conversation * * @param convID The ID of the conversation to get */ public static async GetSingle(convID : number, userID: number) : Promise { const result = await DatabaseHelper.QueryRow({ fields: [ "*", "l.id as id", "l.user_id as owner_id", ], table: LIST_TABLE + " l", joins: [ // Joins with conversation members table { table: USERS_TABLE + " u", condition: "l.id = u.conv_id" } ], where: { "l.id": convID, "u.user_id": userID } }); if(!result) return null; return await this.DBToConversationInfo(result); } /** * Check out whether a user is the member of a conversation or not * * @param userID Target user ID * @param convID Target conversation */ public static async DoesUsersBelongsTo(userID: number, convID: number) : Promise { return await DatabaseHelper.Count({ table: USERS_TABLE, where: { conv_id: convID, user_id: userID } }) == 1; } /** * Change the name of a conversation * * @param convID Target conversation * @param name New name for the conversation (empty name * to remove it) */ public static async SetName(convID: number, name: string) { await DatabaseHelper.UpdateRows({ table: LIST_TABLE, where: { id: convID }, set: { name: name } }); } /** * Set a new list of members for a given conversation * * @param convID Target conversation ID * @param members The new list of members for the conversation */ public static async SetMembers(convID: number, newList: Set) { const currentList = await this.GetConversationMembers(convID); // Add new members for (const member of newList) { if(currentList.has(member)) continue; await this.AddMember(convID, member, true); } // Remove old members for(const member of currentList) { if(newList.has(member)) continue; await this.RemoveMember(convID, member); } } /** * Update following state of the conversation * * @param userID User to update * @param convID Target conversation ID * @param following New status */ public static async SetFollowing(userID: number, convID: number, following: boolean) { await DatabaseHelper.UpdateRows({ table: USERS_TABLE, set: { "following": following ? 1 : 0 }, where: { "conv_id": convID, "user_id": userID } }); } /** * Check out whether a user is the moderator of a conversation or not * * @param userID User to check * @param convID Target conversation */ public static async IsUserModerator(userID : number, convID : number) : Promise { return await DatabaseHelper.Count({ table: USERS_TABLE, where: { id: convID, user_id: userID } }) == 1; } /** * Get the last messages of a conversation * * @param convID Target conversation ID * @param numberOfMessages The maximum number of messages to return */ public static async GetLastMessages(convID: number, numberOfMessages: number) : Promise> { return (await DatabaseHelper.Query({ table: MESSAGES_TABLE, where: { conv_id: convID }, limit: numberOfMessages, order: "id DESC" })).map(m => this.DBToConversationMessage(convID, m)).reverse(); } /** * Get the new messages of a conversation * * @param convID Target conversation ID * @param lastMessageID The ID of the last known message */ public static async GetNewMessages(convID: number, lastMessageID: number): Promise> { return (await DatabaseHelper.Query({ table: MESSAGES_TABLE, where: { conv_id: convID }, customWhere: "ID > ?", customWhereArgs: [lastMessageID.toString()], order: "id" })).map(m => this.DBToConversationMessage(convID, m)); } /** * Mark the user has seen the last messages of the conversation * * @param convID Target conversation ID * @param userID Target user ID */ public static async MarkUserSeen(convID: number, userID: number) { await DatabaseHelper.UpdateRows({ table: USERS_TABLE, where: { conv_id: convID, user_id: userID }, set: { saw_last_message: 1 } }); } /** * Insert a new message into the database * * @param message The message to insert */ public static async SendMessage(message: BaseConversationMessage) { const t = time(); // Insert the message in the database await DatabaseHelper.InsertRow( MESSAGES_TABLE, { conv_id: message.convID, user_id: message.userID, time_insert: t, message: message.message, image_path: message.imagePath } ); // Update the last activity of the conversation await DatabaseHelper.UpdateRows({ table: LIST_TABLE, where: { id: message.convID }, set: { last_active: t, } }); // Mark all the user of the conversations as unread, except current user await DatabaseHelper.UpdateRows({ table: USERS_TABLE, where: { conv_id: message.convID }, customWhere: "user_id != ?", customWhereArgs: [message.userID.toString()], set: { saw_last_message: 0 } }); } /** * Search for private conversations between two users * * @param user1 The first user * @param user2 The second user * @returns The entire list of found conversations */ public static async FindPrivate(user1: number, user2: number) : Promise> { const result = await DatabaseHelper.Query({ table: USERS_TABLE, tableAlias: "t1", joins: [ { table: USERS_TABLE, tableAlias: "t2", condition: "t1.conv_id = t2.conv_id" } ], where: { "t1.user_id": user1, "t2.user_id": user2 }, customWhere: "(SELECT COUNT(*) FROM " + USERS_TABLE + " WHERE conv_id = t1.conv_id) = 2", fields: ["t1.conv_id AS conv_id"] }); return result.map(r => r.conv_id); } /** * Get the list of members of a conversation * * @param convID The ID of the target conversation */ private static async GetConversationMembers(convID : number): Promise> { const result = await DatabaseHelper.Query({ table: USERS_TABLE, where: { "conv_id": convID }, fields: ["user_id"] }); return new Set(result.map((e) => e.user_id)); } /** * Turn a database entry into a conversation object * * @param row */ private static async DBToConversationInfo(row: any) : Promise { return { id: row.id, ownerID: row.owner_id, name: row.name, lastActive: row.last_active, timeCreate: row.time_add, following: row.following, sawLastMessage: row.saw_last_message == 1, members: await this.GetConversationMembers(row.id) } } /** * Turn a database entry into a conversation message * * @param convID The ID of the conversation the message belongs to * @param row Row to convert * @return Generated conversation message */ private static DBToConversationMessage(convID: number, row: any) : ConversationMessage { return new ConversationMessage({ id: row.id, convID: convID, userID: row.user_id, timeSent: row.time_insert, imagePath: row.image_path ? row.image_path : "", message: row.message ? row.message : "" }); } }