import { DatabaseHelper } from "./DatabaseHelper"; import { Notif, NotifElemType, NotifEventVisibility } 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"; /** * Notifications helper * * @author Pierre HUBERT */ const NOTIFICATIONS_TABLE = "comunic_notifications"; export class NotificationsHelper { /** * 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) ) } /** * Delete notifications * * @param n Notification */ public static async Delete(n: Notif) { // Delete a specific notification if(n.hasId) throw new Error("Please implement notification deletion with known ID!"); // Delete wider range of notifications else await DatabaseHelper.DeleteRows(NOTIFICATIONS_TABLE, this.NotifToDB(n, false)) } /** * 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 }); } }