import { DatabaseHelper } from "./DatabaseHelper"; import { GroupsAccessLevel, GroupVisibilityLevel, GroupInfo, GroupPostsCreationLevel } from "../entities/Group"; import { GroupMembershipLevels, GroupMember } from "../entities/GroupMember"; import { NewGroup } from "../entities/NewGroup"; import { time } from "../utils/DateUtils"; import { GroupSettings } from "../entities/GroupSettings"; import { existsSync, unlinkSync } from "fs"; import { PostsHelper } from "./PostsHelper"; /** * Groups helper * * @author Pierre HUBERT */ const GROUPS_LIST_TABLE = "comunic_groups"; const GROUPS_MEMBERS_TABLE = "comunic_groups_members"; export const PATH_GROUPS_LOGOS = "groups_logo"; export class GroupsHelper { /** * Create a new group * * @param info Information about the new group to create * @throws {Error} In case of error */ public static async Create(info: NewGroup) : Promise { // Insert the group into the database const groupID = await DatabaseHelper.InsertRow(GROUPS_LIST_TABLE, { time_create: info.timeCreate, userid_create: info.userID, name: info.name }); if(groupID <= 0) throw Error("Could not create the group!"); await this.InsertMember({ id: 0, groupID: groupID, userID: info.userID, timeCreate: time(), level: GroupMembershipLevels.ADMINISTRATOR, following: true }); return groupID; } /** * Check out whether a group exists or not * * @param groupID Target group ID */ public static async Exists(groupID: number) : Promise { return await DatabaseHelper.Count({ table: GROUPS_LIST_TABLE, where: { id: groupID } }) > 0; } /** * Get the list of groups of a user * * @param userID Target user ID * @param onlyFollowed Include only the groups the user follow */ public static async GetListUser(userID: number, onlyFollowed: boolean = false) : Promise> { const list = await DatabaseHelper.Query({ table: GROUPS_MEMBERS_TABLE, where: { user_id: userID, }, customWhere: onlyFollowed ? "following = 1" : "", fields: ["groups_id"] }); return list.map(e => e.groups_id); } /** * Get the list of gruops of a user where the users can create * posts * * @param userID The ID of the target user */ public static async GetListUserWhereCanCreatePosts(userID: number) : Promise> { const list = await DatabaseHelper.Query({ // Members table table: GROUPS_MEMBERS_TABLE, tableAlias: "m", // Groups liist table joins: [ { table: GROUPS_LIST_TABLE, tableAlias: "g", condition: "m.groups_id = g.id" } ], where: { user_id: userID }, customWhere: "level = ? OR level = ? OR (level = ? AND posts_level = ?)", customWhereArgs: [ GroupMembershipLevels.ADMINISTRATOR.toString(), GroupMembershipLevels.MODERATOR.toString(), GroupMembershipLevels.MEMBER.toString(), GroupPostsCreationLevel.POSTS_LEVEL_ALL_MEMBERS.toString() ], fields: ["g.id"] }); return list.map((e) => e.id); } /** * Get information about a group * * @param groupID Target group ID * @returns Information about the group * @throws {Error} if the group was not found */ public static async GetInfo(groupID: number) : Promise { const row = await DatabaseHelper.QueryRow({ table: GROUPS_LIST_TABLE, where: { id: groupID.toString() } }); if(row == null || !row) throw new Error("Could not get information about the group!"); return this.DbToGroupInfo(row); } /** * Search for groups * * @param query The query * @param limit LImit for the search */ public static async SearchGroup(query: string, limit: number = 10) : Promise> { const results = await DatabaseHelper.Query({ table: GROUPS_LIST_TABLE, customWhere: "name LIKE ? AND visibility != ?", customWhereArgs: ["%"+query+"%", GroupVisibilityLevel.SECRETE_GROUP.toString()], limit: limit, fields: ["id"] }); return results.map((e) => e.id); } /** * Update (set) group settings * * @param settings Group settings */ public static async SetSettings(settings: GroupSettings) { const dbEntry = this.GroupSettingsToDB(settings); await DatabaseHelper.UpdateRows({ table: GROUPS_LIST_TABLE, where: { id: settings.id }, set: dbEntry }); } /** * Delete the logo of a group * * @param groupID Target group ID */ public static async DeleteLogo(groupID: number) { const currSettings = await this.GetInfo(groupID); if(!currSettings.hasLogo) return; if(existsSync(currSettings.logoSysPath)) unlinkSync(currSettings.logoSysPath); currSettings.logo = ""; await this.SetSettings(currSettings); } /** * Get the visibility level of a group * * @param groupID Target group ID */ public static async GetVisibility(groupID: number) : Promise { const result = await DatabaseHelper.QueryRow({ table: GROUPS_LIST_TABLE, where: { id: groupID }, fields: ["visibility"] }); if(result == null) throw Error("Group " + groupID + " does not exists!"); return result.visibility; } /** * Convenience function to check whether a group is open or not * * @param groupID Target group ID */ public static async IsOpen(groupID: number) : Promise { return (await this.GetVisibility(groupID)) == GroupVisibilityLevel.OPEN_GROUP; } /** * Invite a user to join a group * * @param groupID The ID of the target group * @param userID The ID of the target user */ public static async SendInvitation(groupID: number, userID: number) { await this.InsertMember(new GroupMember({ id: -1, userID: userID, groupID: groupID, timeCreate: time(), following: true, level: GroupMembershipLevels.INVITED })); } /** * Check if a user received an invitation to join a group or not * * @param groupID Target group ID * @param userID Target user ID */ public static async ReceivedInvitation(groupID: number, userID: number) : Promise { return await DatabaseHelper.Count({ table: GROUPS_MEMBERS_TABLE, where: { groups_id: groupID, user_ID: userID, level: GroupMembershipLevels.INVITED } }) > 0; } /** * Respond to a membership invitation * * @param groupID Target group ID * @param userID Target user ID * @param accept true to accept invitation / FALSE else */ public static async RespondInvitation(groupID: number, userID: number, accept: boolean) { if(!accept) await this.DeleteMember(groupID, userID); else await this.UpdateMembershipLevel(groupID, userID, GroupMembershipLevels.MEMBER); } /** * Respond to a group membership request * * @param groupID Target groupID * @param userID Target user ID * @param accept TRUE to accept / FALSE else */ public static async RespondRequest(groupID: number, userID: number, accept: boolean) { if(!accept) await this.DeleteMember(groupID, userID); else await this.UpdateMembershipLevel(groupID, userID, GroupMembershipLevels.MEMBER); } /** * Insert a new group member * * @param member Information about the group member to add */ public static async InsertMember(member: GroupMember) { await DatabaseHelper.InsertRow(GROUPS_MEMBERS_TABLE, { groups_id: member.groupID, user_id: member.userID, time_create: member.timeCreate, level: member.level }); } /** * Update a user membership level * * @param groupID Target group ID * @param userID Target user ID * @param level New membership level */ public static async UpdateMembershipLevel(groupID: number, userID: number, level: GroupMembershipLevels) { await DatabaseHelper.UpdateRows({ table: GROUPS_MEMBERS_TABLE, where: { user_id: userID, groups_id: groupID }, set: { level: level } }); } /** * Update following status of a user * * @param groupID Target group ID * @param userID Target user ID * @param follow true to follow / false else */ public static async SetFollowing(groupID: number, userID: number, follow: boolean) { await DatabaseHelper.UpdateRows({ table: GROUPS_MEMBERS_TABLE, where: { groups_id: groupID, user_id: userID }, set: { following: follow ? 1 : 0 } }); } /** * Delete completely a user membership * * @param groupID Target group ID * @param userID Target user ID */ public static async DeleteMember(groupID: number, userID: number) { await DatabaseHelper.DeleteRows(GROUPS_MEMBERS_TABLE, { groups_id: groupID, user_id: userID }); } /** * Get the membership level of a user for a group * * @param groupID The ID of target group * @param userID The ID of target user */ public static async GetMembershipLevel(groupID: number, userID: number) : Promise { // If the user is not signed in if(userID < 1) return GroupMembershipLevels.VISITOR; const result = await DatabaseHelper.Query({ table: GROUPS_MEMBERS_TABLE, where: { groups_id: groupID, user_id: userID }, fields: ["level"] }); // If user has no membership if(result.length == 0) return GroupMembershipLevels.VISITOR; return result[0].level; } /** * Get the current access of a user to a group * * @param groupID The ID of the target group * @param userID The ID of the target user */ public static async GetAccessLevel(groupID: number, userID: number) : Promise { const membershipLevel = userID > 0 ? await this.GetMembershipLevel(groupID, userID) : GroupMembershipLevels.VISITOR; //Check if the user is a confirmed member of group if(membershipLevel == GroupMembershipLevels.ADMINISTRATOR) return GroupsAccessLevel.ADMIN_ACCESS; if(membershipLevel == GroupMembershipLevels.MODERATOR) return GroupsAccessLevel.MODERATOR_ACCESS; if(membershipLevel == GroupMembershipLevels.MEMBER) return GroupsAccessLevel.MEMBER_ACCESS; const groupVisibilityLevel = await this.GetVisibility(groupID); //If the group is open, everyone has view access if(groupVisibilityLevel == GroupVisibilityLevel.OPEN_GROUP) return GroupsAccessLevel.VIEW_ACCESS; //Else, all pending and invited membership get limited access if(membershipLevel == GroupMembershipLevels.PENDING || membershipLevel == GroupMembershipLevels.INVITED) return GroupsAccessLevel.LIMITED_ACCESS; //Private groups gives limited access if(groupVisibilityLevel == GroupVisibilityLevel.PRIVATE_GROUP) return GroupsAccessLevel.LIMITED_ACCESS; // Else the user can not see the group // Especially in the case of secret groupe return GroupsAccessLevel.NO_ACCESS; } /** * Get a user membership information * * @param groupID Target group ID * @param userID Target user ID * @returns Membership info / null if none found */ public static async GetMembershipInfo(groupID: number, userID: number): Promise { if(userID == 0) return null; const data = await DatabaseHelper.QueryRow({ table: GROUPS_MEMBERS_TABLE, where: { groups_id: groupID, user_id: userID } }); if(data == null) return null; return this.DbToGroupMember(data); } /** * Find a group by its virtual directory * * @param dir Target directory * @returns The ID of the target directory / -1 if none found */ public static async FindByVirtualDirectory(dir: string) : Promise { const result = await DatabaseHelper.QueryRow({ table: GROUPS_LIST_TABLE, where: { virtual_directory: dir }, fields: ["id"] }); return result == null ? -1 : result.id; } /** * Check out whether a virtual directory is available or not * * @param dir The directory to check * @param groupID The ID of the group making the request */ public static async CheckDirectoryAvailability(dir: string, groupID: number = -1) : Promise { const currID = await this.FindByVirtualDirectory(dir); return currID < 1 || currID == groupID; } /** * Get the entire list of members of a group * * @param groupID Target Group ID */ public static async GetListMembers(groupID: number) : Promise> { const members = await DatabaseHelper.Query({ table: GROUPS_MEMBERS_TABLE, where: { groups_id: groupID } }); return members.map((row) => this.DbToGroupMember(row)); } /** * Get the list of followers of a group * * @param groupID Target Group ID */ public static async GetListFollowers(groupID: number) : Promise> { const members = await DatabaseHelper.Query({ table: GROUPS_MEMBERS_TABLE, where: { groups_id: groupID, following: 1 }, fields: ["user_id"] }); return members.map((row) => row.user_id); } /** * Count the number of members of a group * * @param groupID Target group ID */ private static async CountMembers(groupID: number) : Promise { return await DatabaseHelper.Count({ table: GROUPS_MEMBERS_TABLE, where: { groups_id: groupID }, customWhere: "level <= ?", customWhereArgs: [GroupMembershipLevels.MEMBER.toString()] }); } /** * Count the number of members of a group at a specific member * @param groupID Target group ID * @param level The level to check */ public static async CountMembersAtLevel(groupID: number, level: GroupMembershipLevels) : Promise { return await DatabaseHelper.Count({ table: GROUPS_MEMBERS_TABLE, where: { groups_id: groupID, level: level } }); } /** * Get the last activity time of a group * * @param groupID Target group ID */ public static async GetLastActivity(userID: number, groupID: number) : Promise { const lastPost = await PostsHelper.GetGroupPosts(userID, groupID, 0, 1); return lastPost.length == 0 ? 0 : lastPost[0].timeCreate; } /** * Check out whether a user can create posts on a group or not * * @param groupID Target group ID * @param userID Target user ID */ public static async CanUserCreatePosts(groupID: number, userID: number) : Promise { const membershipLevel = await this.GetMembershipLevel(groupID, userID); // Moderators + administrators => can always create posts if(membershipLevel == GroupMembershipLevels.MODERATOR || membershipLevel == GroupMembershipLevels.ADMINISTRATOR) return true; // Simple members => check authorization if(membershipLevel == GroupMembershipLevels.MEMBER) { return (await this.GetInfo(groupID)).postsCreationLevel == GroupPostsCreationLevel.POSTS_LEVEL_ALL_MEMBERS; } return false; } /** * Turn a database row into a {GroupInfo} object * * @param row Database entry * @returns Generated object */ private static async DbToGroupInfo(row: any) : Promise { return new GroupInfo({ id: row.id, name: row.name, membersCount: await this.CountMembers(row.id), visiblity: row.visibility, registrationLevel: row.registration_level, postsCreationLevel: row.posts_level, logo: (row.path_logo != null && row.path_logo && row.path_logo != "null" ? row.path_logo : undefined), virtualDirectory: (row.virtual_directory != null && row.virtual_directory && row.virtual_directory != "null" ? row.virtual_directory : undefined), timeCreate: row.time_create, description: (row.description != null && row.description && row.description != "null" ? row.description : undefined), url: (row.url != null && row.url && row.url != "null" ? row.url : undefined) }); } /** * Turn a database entry into a group membership information object * * @param row Database entry * @returns Generated object */ private static DbToGroupMember(row: any) : GroupMember { return new GroupMember({ id: row.id, groupID: row.groups_id, userID: Number(row.user_id), timeCreate: Number(row.time_create), level: row.level, following: row.following == 1 }); } /** * Turn a GroupSettings object into a database entry object * * @param settings Group settings object to transform * @return Generated database entry */ private static GroupSettingsToDB(settings: GroupSettings) : Object { let data = {}; if(settings.name != null) data['name'] = settings.name; if(settings.logo != null) data["path_logo"] = settings.logo; if(settings.visiblity != null) data["visibility"] = settings.visiblity; if(settings.registrationLevel != null) data["registration_level"] = settings.registrationLevel; if(settings.postsCreationLevel != null) data["posts_level"] = settings.postsCreationLevel; if(settings.virtualDirectory != null) data["virtual_directory"] = settings.virtualDirectory; if(settings.description != null) data["description"] = settings.description; if(settings.url != null) data["url"] = settings.url; return data; } }