mirror of
https://gitlab.com/comunic/comunicapiv2
synced 2024-11-25 23:09:22 +00:00
674 lines
16 KiB
TypeScript
674 lines
16 KiB
TypeScript
import { Conversation, BaseConversation } from "../entities/Conversation";
|
|
import { DatabaseHelper } from "./DatabaseHelper";
|
|
import { time } from "../utils/DateUtils";
|
|
import { ConversationMessage, BaseConversationMessage } from "../entities/ConversationMessage";
|
|
import { UnreadConversation } from "../entities/UnreadConversation";
|
|
import { existsSync, unlinkSync } from "fs";
|
|
|
|
/**
|
|
* 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<number> {
|
|
|
|
// 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<Array<Conversation>> {
|
|
|
|
// 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<Conversation | null> {
|
|
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<boolean> {
|
|
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<number>) {
|
|
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<boolean> {
|
|
return await DatabaseHelper.Count({
|
|
table: LIST_TABLE,
|
|
where: {
|
|
id: convID,
|
|
user_id: userID
|
|
}
|
|
}) == 1;
|
|
}
|
|
|
|
/**
|
|
* Check out whether a user is the owner of a message or not
|
|
*
|
|
* @param userID Target user ID
|
|
* @param messageID Target message ID
|
|
*/
|
|
public static async IsUserMessageOwner(userID: number, messageID: number) : Promise<boolean> {
|
|
return (await DatabaseHelper.Count({
|
|
table: MESSAGES_TABLE,
|
|
where: {
|
|
id: messageID,
|
|
user_id: userID
|
|
}
|
|
})) > 0;
|
|
}
|
|
|
|
/**
|
|
* 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<Array<ConversationMessage>> {
|
|
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<Array<ConversationMessage>> {
|
|
return (await DatabaseHelper.Query({
|
|
table: MESSAGES_TABLE,
|
|
where: {
|
|
conv_id: convID
|
|
},
|
|
customWhere: "ID > ?",
|
|
customWhereArgs: [lastMessageID.toString()],
|
|
order: "id"
|
|
})).map(m => this.DBToConversationMessage(convID, m));
|
|
}
|
|
|
|
/**
|
|
* Get all the messages of a single user for a conversation
|
|
*
|
|
* @param convID Target conversation ID
|
|
* @param userID Target user ID
|
|
*/
|
|
public static async GetUserMessagesForConversation(convID: number, userID: number): Promise<Array<ConversationMessage>> {
|
|
return (await DatabaseHelper.Query({
|
|
table: MESSAGES_TABLE,
|
|
where: {
|
|
conv_id: convID,
|
|
user_id: userID
|
|
}
|
|
})).map(m => this.DBToConversationMessage(convID, m));
|
|
}
|
|
|
|
/**
|
|
* Get information about a single conversation message
|
|
*
|
|
* @param messageID The ID of the message to get
|
|
* @throws If the message was not found
|
|
*/
|
|
private static async GetSingleMessage(messageID: number) : Promise<ConversationMessage> {
|
|
const row = await DatabaseHelper.QueryRow({
|
|
table: MESSAGES_TABLE,
|
|
where: {
|
|
id: messageID
|
|
}
|
|
});
|
|
|
|
if(row == null)
|
|
throw Error("The message was not found!");
|
|
|
|
return this.DBToConversationMessage(row.conv_id, row);
|
|
}
|
|
|
|
/**
|
|
* Get older messages of a conversation
|
|
*
|
|
* @param convID ID of the target conversation
|
|
* @param startID ID from which the research should start
|
|
* @param limit Maximum number of messages to get
|
|
* @return The list of messages
|
|
*/
|
|
public static async GetOlderMessage(convID: number, startID: number, limit: number) : Promise<Array<ConversationMessage>> {
|
|
return (await DatabaseHelper.Query({
|
|
table: MESSAGES_TABLE,
|
|
where: {
|
|
conv_id: convID,
|
|
},
|
|
customWhere: "ID <= ?",
|
|
customWhereArgs: [startID.toString()],
|
|
order: "id DESC",
|
|
limit: limit
|
|
}))
|
|
.map(m => this.DBToConversationMessage(convID, m)).reverse();
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
}
|
|
});
|
|
|
|
}
|
|
|
|
/**
|
|
* Update message content
|
|
*
|
|
* @param messageID Target message ID
|
|
* @param newContent New message content
|
|
*/
|
|
public static async UpdateMessageContent(messageID: number, newContent: string) {
|
|
await DatabaseHelper.UpdateRows({
|
|
table: MESSAGES_TABLE,
|
|
where: {
|
|
id: messageID
|
|
},
|
|
set: {
|
|
message: newContent
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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<Array<number>> {
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* Count the number of unread conversations of the user
|
|
*
|
|
* @param userID Target user ID
|
|
*/
|
|
public static async CountUnreadForUser(userID: number) : Promise<number> {
|
|
return await DatabaseHelper.Count({
|
|
table: USERS_TABLE,
|
|
where: {
|
|
user_id: userID,
|
|
saw_last_message: 0,
|
|
following: 1
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get the list of unread conversations of the user
|
|
*
|
|
* @param userID Target user ID
|
|
*/
|
|
public static async GetListUnread(userID: number) : Promise<Array<UnreadConversation>> {
|
|
return (await DatabaseHelper.Query({
|
|
table: USERS_TABLE,
|
|
tableAlias: "users",
|
|
|
|
joins: [
|
|
|
|
// Join with conversations list table
|
|
{
|
|
table: LIST_TABLE,
|
|
tableAlias: "list",
|
|
condition: "users.conv_id = list.id"
|
|
},
|
|
|
|
// Join with message table to get the latest message
|
|
{
|
|
table: MESSAGES_TABLE,
|
|
tableAlias: "messages",
|
|
condition: "messages.conv_id = users.conv_id"
|
|
}
|
|
],
|
|
|
|
where: {
|
|
"users.user_id": userID,
|
|
"users.following": 1,
|
|
"users.saw_last_message": 0,
|
|
},
|
|
|
|
customWhere: "list.last_active = messages.time_insert",
|
|
|
|
order: "list.last_active DESC"
|
|
|
|
})).map(m => <UnreadConversation>{
|
|
id: m.conv_id,
|
|
name: m.name,
|
|
lastActive: m.last_active,
|
|
userID: m.user_id,
|
|
message: m.message,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Remove a user from a conversation
|
|
*
|
|
* @param userID Target user ID
|
|
* @param convID Target conversation ID
|
|
*/
|
|
public static async RemoveUserFromConversation(userID: number, convID: number) {
|
|
// Check whether the user is the owner of the conversation or not
|
|
if(await this.IsUserModerator(userID, convID))
|
|
await this.DeleteConversations(convID);
|
|
|
|
else
|
|
// Only delete the messages & membership of teh user
|
|
await this.DeleteMember(convID, userID);
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* Delete a conversation
|
|
*
|
|
* @param convID The ID of the conversation to delete
|
|
*/
|
|
private static async DeleteConversations(convID: number) {
|
|
// Get & delete all the messages of the conversations
|
|
const messages = await this.GetNewMessages(convID, 0);
|
|
for (const message of messages) {
|
|
await this.DeleteMessage(message);
|
|
}
|
|
|
|
// Delete all the members of the conversation
|
|
await DatabaseHelper.DeleteRows(USERS_TABLE, {
|
|
conv_id: convID
|
|
});
|
|
|
|
// Delete the conversation entry itself
|
|
await DatabaseHelper.DeleteRows(LIST_TABLE, {
|
|
id: convID
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Delete a conversation membership
|
|
*
|
|
* @param convID Target conversation
|
|
* @param memberID Target user ID
|
|
*/
|
|
private static async DeleteMember(convID: number, memberID: number) {
|
|
// Get & delete all the messages of the member
|
|
const messages = await this.GetUserMessagesForConversation(convID, memberID);
|
|
for (const message of messages) {
|
|
await this.DeleteMessage(message);
|
|
}
|
|
|
|
// Delete membership
|
|
await this.RemoveMember(convID, memberID);
|
|
}
|
|
|
|
/**
|
|
* Delete a conversation message identified by its ID
|
|
*
|
|
* @param id The ID of the message to delete
|
|
*/
|
|
public static async DeleteMessageById(id: number) {
|
|
// Get information about the message
|
|
const message = await this.GetSingleMessage(id);
|
|
|
|
await this.DeleteMessage(message);
|
|
}
|
|
|
|
/**
|
|
* Delete a conversation message from the database
|
|
*
|
|
* @param m The message to delete
|
|
*/
|
|
private static async DeleteMessage(m: ConversationMessage) {
|
|
|
|
// Delete conversation message image (if any)
|
|
if(m.hasImage) {
|
|
if(existsSync(m.imageSysPath))
|
|
unlinkSync(m.imageSysPath);
|
|
}
|
|
|
|
// Delete the message from the database
|
|
await DatabaseHelper.DeleteRows(
|
|
MESSAGES_TABLE,
|
|
{
|
|
ID: m.id
|
|
}
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Get the list of members of a conversation
|
|
*
|
|
* @param convID The ID of the target conversation
|
|
*/
|
|
private static async GetConversationMembers(convID : number): Promise<Set<number>> {
|
|
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<Conversation> {
|
|
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 : ""
|
|
});
|
|
}
|
|
} |