import { DatabaseHelper } from "./DatabaseHelper"; import { Notif, NotifElemType, NotifEventVisibility, NotifEventType } from "../entities/Notification"; import { time } from "../utils/DateUtils"; import { PostsHelper } from "./PostsHelper"; import { PostPageKind, PostVisibilityLevel } from "../entities/Post"; import { FriendsHelper } from "./FriendsHelper"; import { GroupsHelper } from "./GroupsHelper"; import { GroupMembershipLevels } from "../entities/GroupMember"; import { EventsHelper } from "./EventsHelper"; /** * Notifications helper * * @author Pierre HUBERT */ const NOTIFICATIONS_TABLE = "comunic_notifications"; export class NotificationsHelper { /** * Create a notification about a post * * @param fromUser Source user ID * @param postID Target post ID * @param action Action to notify */ public static async CreatePostNotification(fromUser: number, postID: number, action: NotifEventType) { await this.Push(new Notif({ timeCreate: time(), fromUserID: fromUser, onElemID: postID, onElemType: NotifElemType.POST, type: action })); } /** * Create & push friendship request notification * * @param fromUser Source user ID * @param destUser Destination user ID * @param action The kind of action */ public static async CreateFriendsNotifications(fromUser: number, destUser: number, action: NotifEventType) { await this.DeleteNotificationsFrienshipRequest(fromUser, destUser); // Push the notification await this.Push(new Notif({ fromUserID: fromUser, destUserID: destUser, onElemID: fromUser, // Same as fromUser onElemType: NotifElemType.FRIENDSHIP_REQUEST, type: action })); } /** * Create & push a group membership notification * * @param userID The ID of the target user for the membership * @param moderatorID The ID of the moderator creating the notification (0 if it is the user) * @param groupID The ID of the target group * @param kind The kind of notification to create */ public static async CreateGroupMembershipNotification(userID: number, moderatorID: number, groupID: number, kind: NotifEventType) { await this.DeleteNotificationsGroupsMembership(userID, groupID); const n = new Notif({ onElemID: groupID, onElemType: NotifElemType.GROUP_MEMBERSHIP, type: kind }); // The notification must be sent to all the moderators of the group if(moderatorID < 1) { n.fromUserID = userID; n.destUserID = -1; } // We specify both the source and the destination of the notification // not to broadcast the notification to all the group members else { n.fromUserID = moderatorID; n.destUserID = userID; } await this.Push(n); } /** * Push a new notification * * @param n Notification to push */ public static async Push(n: Notif) { if(!n.hasTimeCreate) n.timeCreate = time(); // Determine the visibility level of the notification if(n.onElemType == NotifElemType.POST) { const post = await PostsHelper.GetSingle(n.onElemID); // Determine post container switch(post.kindPage) { case PostPageKind.PAGE_KIND_USER: n.fromContainerType = NotifElemType.USER_PAGE n.fromContainerID = post.userPageID break; case PostPageKind.PAGE_KIND_GROUP: n.fromContainerType = NotifElemType.GROUP_PAGE n.fromContainerID = post.groupID break; default: throw new Error("Unsupported page kind: " + post.kindPage) } // Check the scope of the notification // Private post (on user pages) if(post.visibilityLevel == PostVisibilityLevel.VISIBILITY_USER) { // Check if the user does not own the post if(post.userID == post.userPageID) return; // If the personn who posted that is not the owner of the page else if(post.userPageID != n.fromUserID) n.destUserID = post.userPageID // If the user is the one who own the page else n.destUserID = post.userID await this.PushPrivate(n) } // Posts on users page else if(n.fromContainerType == NotifElemType.USER_PAGE) { // Process friends list of the user creating the notification const friendsList = await FriendsHelper.GetList(n.fromUserID); const targetUsers = []; for(const friend of friendsList) { if(friend.friendID != n.fromContainerID && post.userPageID != n.fromUserID && // Skip friendship check if user // creating the notification // & target page are the same !await FriendsHelper.AreFriend(friend.friendID, post.userPageID)) continue; // Check if the user is following his friend if(!await FriendsHelper.IsFollowing(friend.friendID, n.fromUserID)) continue; targetUsers.push(friend.friendID) } await this.PushPublic(n, targetUsers); } // For posts on grou pages else if(n.fromContainerType == NotifElemType.GROUP_PAGE) { await this.PushGroupMembers(n, n.fromContainerID) } // Mark the scenario as unsupported else { console.error(n) throw new Error("Post notification scenario not supported!") } } // Frienship notifications else if(n.onElemType == NotifElemType.FRIENDSHIP_REQUEST) { n.fromContainerID = 0 n.fromContainerType = NotifElemType.EMPTY; await this.PushPrivate(n); } // Groups membership notifications else if (n.onElemType == NotifElemType.GROUP_MEMBERSHIP) { // Complete the notification n.fromContainerType = NotifElemType.EMPTY; n.fromContainerID = 0; // Check whether the notification has to be pushed to a single user // or to all the moderators of the group if(n.hasDestUserID) { //Push the notification in private way (if it has a destination, //generally the target user of the membership request) await this.PushPrivate(n); } else { // Push the notification to all the moderators of the group await this.PushGroupModerators(n, n.onElemID); } } // Unsupported notification type else { console.error(n) throw new Error("Type of notification not supported!") } } /** * Push a notification to all the members of a group * * @param n The notification to push * @param groupID Target group ID */ private static async PushGroupMembers(n: Notif, groupID: number) { let list = await GroupsHelper.GetListFollowers(groupID); // Remove the user at the source of the notification list = list.filter((v) => v != n.fromUserID); await this.PushPublic(n, list); } /** * Push a notification to all the moderators & administrators of a group * * @param n The notification to push * @param groupID Target group ID */ private static async PushGroupModerators(n: Notif, groupID: number) { let list = await GroupsHelper.GetListMembers(groupID); // Remove all the users who are not moderators or administrators list = list.filter((v) => v.level <= GroupMembershipLevels.MODERATOR); const ids = list.map((v) => v.userID); await this.PushPublic(n, ids); } /** * Push a notification to multiple users * * @param n The notification to push * @param users The list of target users */ private static async PushPublic(n: Notif, users: Array) { n.eventVisibility = NotifEventVisibility.EVENT_PUBLIC for(const userID of users) { n.destUserID = userID; if(await this.SimilarExists(n)) continue await this.Create(n); } } /** * Push a private notification * * @param n The notification */ private static async PushPrivate(n: Notif) { n.eventVisibility = NotifEventVisibility.EVENT_PRIVATE if(await this.SimilarExists(n)) return; await this.Create(n) } /** * Check out whether a similar notification exists * for a given notification * * @param n The notification */ public static async SimilarExists(n: Notif) : Promise { return await DatabaseHelper.Count({ table: NOTIFICATIONS_TABLE, where: this.NotifToDB(n, false) }) > 0; } /** * Create the notification * * @param n The notification */ private static async Create(n: Notif) { await DatabaseHelper.InsertRow( NOTIFICATIONS_TABLE, this.NotifToDB(n, true) ) // Trigger notify system await EventsHelper.Emit("updated_number_notifications", {usersID: [n.destUserID]}); } /** * Delete notifications * * @param n Notification */ public static async Delete(n: Notif) { // Delete a specific notification const cond = n.hasId ? {id: n.id} : this.NotifToDB(n, false); // Check for affected users const users = new Set(); (await DatabaseHelper.Query({ table: NOTIFICATIONS_TABLE, where: cond, fields: ["dest_user_id"] })).forEach((row) => users.add(row.dest_user_id)) // Delete notifications await DatabaseHelper.DeleteRows(NOTIFICATIONS_TABLE, cond); // Trigger notifications system await EventsHelper.Emit("updated_number_notifications", {usersID: [...users]}); } /** * Delete all the notifications of a given user * * @param userID Target user ID */ public static async DeleteAllUser(userID: number) { await this.Delete(new Notif({ destUserID: userID })); } /** * Delete all the notifications related with a group * * @param groupID Target group ID */ public static async DeleteAllRelatedWithGroup(groupID: number) { const n = new Notif({ onElemType: NotifElemType.GROUP_MEMBERSHIP, onElemID: groupID }); await this.Delete(n); n.onElemType = NotifElemType.GROUP_PAGE; await this.Delete(n); } /** * Delete all the notifications related with a specified user * * @param userID Target user ID */ public static async DeleteAllRelatedWithUser(userID: number) { // Delete all the notifications targetting the user await this.DeleteAllUser(userID); // Delete all the notifications created by the user await this.Delete(new Notif({ fromUserID: userID })); } /** * Delete all the notifications about a post targetting a specified * user * * @param userID Target user ID * @param postID Target post ID */ public static async DeleteAllPostsNotificationsTargetingUser(userID: number, postID: number) { await this.Delete(new Notif({ destUserID: userID, onElemType: NotifElemType.POST, onElemID: postID })); } /** * Delete all the notification related with a post * * @param postID Target post id */ public static async DeleteAllRelatedWithPost(postID: number) { await this.Delete(new Notif({ onElemType: NotifElemType.POST, onElemID: postID })); } /** * Delete all the notifications related to a friendship request between two users * * @param userOne First user * @param userTwo Second user */ public static async DeleteNotificationsFrienshipRequest(userOne: number, userTwo: number) { // Delete notifications in two ways await this.Delete(new Notif({ onElemType: NotifElemType.FRIENDSHIP_REQUEST, destUserID: userOne, fromUserID: userTwo, })); await this.Delete(new Notif({ onElemType: NotifElemType.FRIENDSHIP_REQUEST, destUserID: userTwo, fromUserID: userOne, })); } /** * Delete all the notifications related to a specific group membership * * @param userID Target user * @param groupID The ID of the target group */ public static async DeleteNotificationsGroupsMembership(userID: number, groupID: number) { const n = new Notif({ onElemType: NotifElemType.GROUP_MEMBERSHIP, onElemID: groupID }); n.destUserID = userID; n.fromUserID = -1; await this.Delete(n); n.destUserID = -1; n.fromUserID = userID; await this.Delete(n); } /** * Count the number of unread notifications of a user * * @param userID Target user ID */ public static async CountUnread(userID: number) : Promise { return await DatabaseHelper.Count({ table: NOTIFICATIONS_TABLE, where: { dest_user_id: userID, seen: 0 } }); } /** * Get the list of unread notifications of a user * * @param userID Target user ID */ public static async GetListUnread(userID: number) : Promise> { const list = await DatabaseHelper.Query({ table: NOTIFICATIONS_TABLE, where: { dest_user_id: userID, seen: 0 }, order: "id DESC" }); return list.map((e) => this.DBToNotif(e)); } /** * Get information about a single notification * * @param notifID Target notification id */ public static async GetSingle(notifID: number) : Promise { const row = await DatabaseHelper.QueryRow({ table: NOTIFICATIONS_TABLE, where: { id: notifID } }); if(row == null) throw new Error("Could not find notification !"); return this.DBToNotif(row); } /** * Turn a notification into a database entry * * @param n The notification * @param allInfo True to return a full notification entry */ private static NotifToDB(n: Notif, allInfo = true) : any { let data: any = {} if(n.hasId) data.id = n.id if(n.hasSeen) data.seen = n.seen ? 1 : 0 if(n.hasFromUserID) data.from_user_id = n.fromUserID if(n.hasDestUserID) data.dest_user_id = n.destUserID if(n.hasType) data.type = n.type if(n.hasOnElemID) data.on_elem_id = n.onElemID if(n.hasOnElemType) data.on_elem_type = n.onElemType if(allInfo) { data.from_container_id = n.fromContainerID data.from_container_type = n.fromContainerType data.time_create = n.timeCreate data.visibility = n.eventVisibility } return data; } /** * Database entry to notification * * @param row Database entry */ private static DBToNotif(row: any) : Notif { return new Notif({ id: row.id, seen: row.seen == 1, timeCreate: row.time_create, fromUserID: row.from_user_id, destUserID: row.dest_user_id, onElemID: row.on_elem_id, onElemType: row.on_elem_type, type: row.type, eventVisibility: row.visibility, fromContainerID: row.from_container_id, fromContainerType: row.from_container_type }); } }