1
0
mirror of https://gitlab.com/comunic/comunicapiv2 synced 2024-11-24 06:19:22 +00:00
comunicapiv2/src/controllers/GroupsController.ts

629 lines
20 KiB
TypeScript

import { RequestHandler } from "../entities/RequestHandler";
import { GroupsHelper, PATH_GROUPS_LOGOS } from "../helpers/GroupsHelper";
import { GroupsAccessLevel, GroupInfo, GroupVisibilityLevel, GroupPostsCreationLevel, GroupRegistrationLevel } from "../entities/Group";
import { GroupMembershipLevels, GroupMember } from "../entities/GroupMember";
import { time } from "../utils/DateUtils";
import { LikesHelper, LikesType } from "../helpers/LikesHelper";
import { GroupSettings } from "../entities/GroupSettings";
import { removeHTMLNodes, checkURL } from "../utils/StringUtils";
import { findKey } from "../utils/ArrayUtils";
import { checkVirtualDirectoryAvailability, VirtualDirType } from "../utils/VirtualDirsUtils";
import { NotificationsUtils } from "../utils/NotificationsUtils";
import { NotifEventType } from "../entities/Notification";
/**
* Groups API controller
*
* @author Pierre HUBERT
*/
/**
* API groups registration levels
*/
const GROUPS_REGISTRATION_LEVELS = {};
GROUPS_REGISTRATION_LEVELS[GroupRegistrationLevel.OPEN_REGISTRATION] = "open";
GROUPS_REGISTRATION_LEVELS[GroupRegistrationLevel.MODERATED_REGISTRATION] = "moderated";
GROUPS_REGISTRATION_LEVELS[GroupRegistrationLevel.CLOSED_REGISTRATION] = "closed";
/**
* API groups membership levels
*/
const GROUPS_MEMBERSHIP_LEVELS = {};
GROUPS_MEMBERSHIP_LEVELS[GroupMembershipLevels.ADMINISTRATOR] = "administrator";
GROUPS_MEMBERSHIP_LEVELS[GroupMembershipLevels.MODERATOR] = "moderator";
GROUPS_MEMBERSHIP_LEVELS[GroupMembershipLevels.MEMBER] = "member";
GROUPS_MEMBERSHIP_LEVELS[GroupMembershipLevels.INVITED] = "invited";
GROUPS_MEMBERSHIP_LEVELS[GroupMembershipLevels.PENDING] = "pending";
GROUPS_MEMBERSHIP_LEVELS[GroupMembershipLevels.VISITOR] = "visitor";
/**
* API groups visibility levels
*/
const GROUPS_VISIBILITY_LEVELS = {};
GROUPS_VISIBILITY_LEVELS[GroupVisibilityLevel.OPEN_GROUP] = "open";
GROUPS_VISIBILITY_LEVELS[GroupVisibilityLevel.PRIVATE_GROUP] = "private";
GROUPS_VISIBILITY_LEVELS[GroupVisibilityLevel.SECRETE_GROUP] = "secrete";
/**
* API posts creation levels
*/
const GROUPS_POSTS_LEVELS = [];
GROUPS_POSTS_LEVELS[GroupPostsCreationLevel.POSTS_LEVEL_MODERATORS] = "moderators";
GROUPS_POSTS_LEVELS[GroupPostsCreationLevel.POSTS_LEVEL_ALL_MEMBERS] = "members";
export class GroupsController {
/**
* Create a new group
*
* @param h Request handler
*/
public static async Create(h: RequestHandler) {
const name = h.postString("name", 3);
const groupID = await GroupsHelper.Create({
name: name,
userID: h.getUserId(),
timeCreate: time()
});
h.send({
success: "The group has been successfully created!",
id: groupID
});
}
/**
* Get the list of groups of the user
*
* @param h Request handler
*/
public static async GetListUser(h: RequestHandler) {
h.send(await GroupsHelper.GetListUser(h.getUserId()));
}
/**
* Get information about a single group
*
* @param h Request handler
*/
public static async GetInfoSingle(h: RequestHandler) {
const groupID = await h.postGroupIDWithAccess("id", GroupsAccessLevel.LIMITED_ACCESS);
const groupInfo = await GroupsHelper.GetInfo(groupID);
h.send(await this.GroupInfoToAPI(groupInfo, h));
}
/**
* Get information about multiple users
*
* @param h Request handler
*/
public static async GetInfoMultiple(h: RequestHandler) {
const ids = h.postNumbersList("list");
const result = {};
for (const id of ids) {
// Check group existence & user authorization
if(!await GroupsHelper.Exists(id)
|| await GroupsHelper.GetAccessLevel(id, h.getUserId()) < GroupsAccessLevel.LIMITED_ACCESS)
h.error(404, "Group " + id + " not found");
const group = await GroupsHelper.GetInfo(id);
result[id] = await this.GroupInfoToAPI(group, h);
}
h.send(result);
}
/**
* Get advanced information about a group
*
* @param h Request handler
*/
public static async GetAdvancedInfo(h: RequestHandler) {
const groupID = await h.postGroupIDWithAccess("id", GroupsAccessLevel.VIEW_ACCESS);
const group = await GroupsHelper.GetInfo(groupID);
h.send(await this.GroupInfoToAPI(group, h, true));
}
/**
* Get group settings
*
* @param h Request handler
*/
public static async GetSettings(h: RequestHandler) {
const groupID = await h.postGroupIDWithAccess("id", GroupsAccessLevel.ADMIN_ACCESS);
// For now, this method is the same as the get advanced info methods,
// but this might change in the future...
const group = await GroupsHelper.GetInfo(groupID);
h.send(await this.GroupInfoToAPI(group, h, true));
}
/**
* Set (update) group settings
*
* @param h Request handler
*/
public static async SetSettings(h: RequestHandler) {
const groupID = await h.postGroupIDWithAccess("id", GroupsAccessLevel.ADMIN_ACCESS);
// Check group visibility
const visibilityKey = findKey(GROUPS_VISIBILITY_LEVELS, h.postString("visibility", 3));
if(visibilityKey == null)
h.error(400, "Group visibility level not recognized!");
// Check group registration level
const registrationKey = findKey(GROUPS_REGISTRATION_LEVELS, h.postString("registration_level", 3));
if(registrationKey == null)
h.error(400, "Group registration level not recognized!");
// Check post creation level
const postLevelKey = findKey(GROUPS_POSTS_LEVELS, h.postString("posts_level", 3));
if(postLevelKey == null)
h.error(400, "Group post creation level not recognized!");
// Check URL
const url = h.postString("url", 0);
if(url.length > 0 && ! checkURL(url))
h.error(401, "Invalid group URL!");
// Check virtual directory
let virtualDirectory = "";
if(h.hasPostString("virtual_directory", 1)) {
virtualDirectory = h.postVirtualDirectory("virtual_directory");
// Check out whether virtual directory is available or not
if(!await checkVirtualDirectoryAvailability(virtualDirectory, groupID, VirtualDirType.GROUP))
h.error(401, "Requested virtual directory is not available!");
}
const settings = new GroupSettings({
// Basic information
id: groupID,
name: removeHTMLNodes(h.postString("name", 3)),
visiblity: <GroupVisibilityLevel>Number(visibilityKey),
registrationLevel: <GroupRegistrationLevel>Number(registrationKey),
postsCreationLevel: <GroupPostsCreationLevel>Number(postLevelKey),
// Useless info
membersCount: -1,
timeCreate: -1,
// Optionnal
description: removeHTMLNodes(h.postString("description", 0)),
// Optionnal
url: url,
// Optionnal
virtualDirectory: virtualDirectory,
});
await GroupsHelper.SetSettings(settings);
h.success("Group settings have been successfully updated!");
}
/**
* Check the availability of a virtual directory for a given group
*
* @param h Request handler
*/
public static async CheckVirtualDirectory(h: RequestHandler) {
const groupID = await h.postGroupIDWithAccess("groupID", GroupsAccessLevel.ADMIN_ACCESS);
const virtualDirectory = h.postVirtualDirectory("directory");
if(!await checkVirtualDirectoryAvailability(virtualDirectory, groupID, VirtualDirType.GROUP))
h.error(401, "The requested virtual directory seems not to be available!");
h.success("Requested virtual directory seems to be available!");
}
/**
* Upload a new group logo
*
* @param h Request handler
*/
public static async UploadLogo(h: RequestHandler) {
const groupID = await h.postGroupIDWithAccess("id", GroupsAccessLevel.ADMIN_ACCESS);
if(!h.hasFile("logo"))
h.error(400, "An error occured while receiving logo !");
// Delete current logo (if any)
await GroupsHelper.DeleteLogo(groupID);
// Save the new group logo
const targetFilePath = await h.savePostImage("logo", PATH_GROUPS_LOGOS, 500, 500);
// Update the settings of the group
const settings = await GroupsHelper.GetInfo(groupID);
settings.logo = targetFilePath;
await GroupsHelper.SetSettings(settings);
h.send({
success: "Group logo has been successfully updated!",
url: settings.logoURL
});
}
/**
* Delete the current logo of a group
*
* @param h Request handler
*/
public static async DeleteLogo(h: RequestHandler) {
const groupID = await h.postGroupIDWithAccess("id", GroupsAccessLevel.ADMIN_ACCESS);
await GroupsHelper.DeleteLogo(groupID);
h.send({
success: "Group logo has been successfully deleted!",
url: (await GroupsHelper.GetInfo(groupID)).logoURL
})
}
/**
* Get the entire list of members of the group
*
* @param h Request handler
*/
public static async GetMembers(h: RequestHandler) {
const groupID = await h.postGroupIDWithAccess("id", GroupsAccessLevel.MODERATOR_ACCESS);
const members = await GroupsHelper.GetListMembers(groupID);
// Parse the list of members
h.send(members.map((m) => this.GroupMemberToAPI(m)));
}
/**
* Invite a user to join the network
*
* @param h Request handler
*/
public static async InviteUser(h: RequestHandler) {
const groupID = await h.postGroupIDWithAccess("group_id", GroupsAccessLevel.MODERATOR_ACCESS);
const userID = await h.postUserId("userID");
if(await GroupsHelper.GetMembershipLevel(groupID, userID) != GroupMembershipLevels.VISITOR)
h.error(401, "The user is not a visitor of the group!");
await GroupsHelper.SendInvitation(groupID, userID);
// Create a notification
await NotificationsUtils.CreateGroupMembershipNotification(
userID, h.getUserId(), groupID, NotifEventType.SENT_GROUP_MEMBERSHIP_INVITATION)
h.success("The user has been successfully invited to join the group!");
}
/**
* Respond to a user invitation
*
* @param h Request handler
*/
public static async RespondInvitation(h: RequestHandler) {
const groupID = await h.postGroupIDWithAccess("id", GroupsAccessLevel.LIMITED_ACCESS);
const accept = h.postBool("accept");
// Check if the user really received an invitation to join the group
if(!await GroupsHelper.ReceivedInvitation(groupID, h.getUserId()))
h.error(404, "Invitation not found!");
// Respond to the invitation
await GroupsHelper.RespondInvitation(groupID, h.getUserId(), accept);
// Create a notification
await NotificationsUtils.CreateGroupMembershipNotification(
h.getUserId(), 0, groupID,
accept ? NotifEventType.ACCEPTED_GROUP_MEMBERSHIP_INVITATION : NotifEventType.REJECTED_GROUP_MEMBERSHIP_INVITATION
)
h.success("Response to the invitation was successfully saved!");
}
/**
* Send a request to join a server
*
* @param h Request handler
*/
public static async SendRequest(h: RequestHandler) {
const groupID = await h.postGroupIDWithAccess("id", GroupsAccessLevel.LIMITED_ACCESS);
// Check the user is really a visitor of the group
if(await GroupsHelper.GetMembershipLevel(groupID, h.getUserId()) != GroupMembershipLevels.VISITOR)
h.error(401, "You are not currently a visitor of the group!");
// Check the user is allowed to send a request to join the group
const group = await GroupsHelper.GetInfo(groupID);
if(group.registrationLevel == GroupRegistrationLevel.CLOSED_REGISTRATION)
h.error(401, "You are not authorized to send a registration request for this group!");
// Create & insert membership
const member = new GroupMember({
id: -1,
userID: h.getUserId(),
timeCreate: time(),
groupID: groupID,
level: group.registrationLevel == GroupRegistrationLevel.MODERATED_REGISTRATION
? GroupMembershipLevels.PENDING : GroupMembershipLevels.MEMBER,
following: true,
});
await GroupsHelper.InsertMember(member);
if(group.registrationLevel == GroupRegistrationLevel.MODERATED_REGISTRATION) {
//TODO : Send a notification
}
h.success("The membership has been successfully saved!");
}
/**
* Cancel a membership request
*
* @param h Request handler
*/
public static async CancelRequest(h: RequestHandler) {
const groupID = await h.postGroupIDWithAccess("id", GroupsAccessLevel.LIMITED_ACCESS);
if(await GroupsHelper.GetMembershipLevel(groupID, h.getUserId()) != GroupMembershipLevels.PENDING)
h.error(401, "You did not send a membership request to this group!");
// Delete membership of the user
await GroupsHelper.DeleteMember(groupID, h.getUserId());
// TODO : delete any potential notificaton
h.success("The request has been successfully cancelled!");
}
/**
* Delete a member from a group (as a moderator or an admin)
*
* @param h Request handler
*/
public static async DeleteMember(h: RequestHandler) {
const groupID = await h.postGroupIDWithAccess("groupID", GroupsAccessLevel.MODERATOR_ACCESS);
// Get the membership of the user making the request
const currUserMembership = await GroupsHelper.GetMembershipInfo(groupID, h.getUserId());
// Get the ID of the member to delete
const userID = await h.postUserId("userID");
const membership = await GroupsHelper.GetMembershipInfo(groupID, userID);
if(membership == null)
h.error(404, "Membership not found!");
// If the user is an admin, he must not be the last admin of the group
if(userID == h.getUserId() && currUserMembership.level == GroupMembershipLevels.ADMINISTRATOR
&& await GroupsHelper.CountMembersAtLevel(groupID, GroupMembershipLevels.ADMINISTRATOR) == 1)
h.error(401, "You are the last administrator of this group!");
// Only administrator can delete members that are more than member (moderators & administrators)
if(membership.level < GroupMembershipLevels.MEMBER && currUserMembership.level != GroupMembershipLevels.ADMINISTRATOR)
h.error(401, "Only an administrator can delete this membership!");
// Delete the membership
await GroupsHelper.DeleteMember(groupID, userID);
// TODO : delete any group membership notifications
h.success("Membership of the user has been successfully deleted!");
}
/**
* Update a user membership
*
* @param h Request handler
*/
public static async UpdateMembership(h: RequestHandler) {
// Get information about the target
const groupID = await h.postGroupIDWithAccess("groupID", GroupsAccessLevel.ADMIN_ACCESS);
const userID = await h.postUserId("userID");
if(userID == h.getUserId())
h.error(400, "You can not update your own membership!");
// Get current user membership
const level = await GroupsHelper.GetMembershipLevel(groupID, userID);
// Check if user is at least a member of the group
if(level > GroupMembershipLevels.MEMBER)
h.error(401, "This user is not a member of the group!");
// Get the new membership of the user
const membershipKey = findKey(GROUPS_MEMBERSHIP_LEVELS, h.postString("level", 3));
if(membershipKey == null)
h.error(400, "New user membership level not recognized!");
const newLevel = <GroupMembershipLevels>Number(membershipKey);
if(newLevel > GroupMembershipLevels.MEMBER)
h.error(401, "You can not assign this visibility level to a group member!");
await GroupsHelper.UpdateMembershipLevel(groupID, userID, newLevel);
h.success("User membership has been successfully updated!");
}
/**
* Respond to a membership request
*
* @param h Request handler
*/
public static async RespondRequest(h: RequestHandler) {
const groupID = await h.postGroupIDWithAccess("groupID", GroupsAccessLevel.MODERATOR_ACCESS);
const userID = await h.postUserId("userID");
const accept = h.postBool("accept");
if(await GroupsHelper.GetMembershipLevel(groupID, userID) != GroupMembershipLevels.PENDING)
h.error(401, "This user has not requested a membership for this group!");
// Respond to the request
await GroupsHelper.RespondRequest(groupID, userID, accept);
// TODO : create a notification
h.success("The response to the request has been successfully saved!");
}
/**
* Get information about a single membership
*
* @param h Request handler
*/
public static async GetMembership(h: RequestHandler) {
const groupID = await h.postGroupIDWithAccess("groupID", GroupsAccessLevel.MODERATOR_ACCESS);
const userID = await h.postUserId("userID");
const membership = await GroupsHelper.GetMembershipInfo(groupID, userID);
if(membership == null)
h.error(404, "Specified user does not have any membership in this group!");
h.send(this.GroupMemberToAPI(membership));
}
/**
* Cancel a group membership invitation
*
* @param h Request handler
*/
public static async CancelInvitation(h: RequestHandler) {
const groupID = await h.postGroupIDWithAccess("groupID", GroupsAccessLevel.MODERATOR_ACCESS);
const userID = await h.postUserId("userID");
if(await GroupsHelper.GetMembershipLevel(groupID, userID) != GroupMembershipLevels.INVITED)
h.error(401, "This user has not been invited to join this group!");
// Cancel group invitation
await GroupsHelper.DeleteMember(groupID, userID);
// TODO : delete related notifications
h.success("Membership invitation has been cancelled!");
}
/**
* Delete the membership of a user to a group
*
* @param h Request handler
*/
public static async RemoveMembership(h: RequestHandler) {
const groupID = await h.postGroupIDWithAccess("id", GroupsAccessLevel.LIMITED_ACCESS);
const level = await GroupsHelper.GetMembershipLevel(groupID, h.getUserId());
if(level == GroupMembershipLevels.ADMINISTRATOR
&& await GroupsHelper.CountMembersAtLevel(groupID, GroupMembershipLevels.ADMINISTRATOR) == 1)
h.error(401, "You are the last administrator of the group!");
// Delete mebmership
await GroupsHelper.DeleteMember(groupID, h.getUserId());
// TODO : delete group membership notifications
h.success("Your membership has been successfully deleted!");
}
/**
* Update the following status of a user
*
* @param h Request handler
*/
public static async SetFollowing(h: RequestHandler) {
const groupID = await h.postGroupIDWithAccess("groupID", GroupsAccessLevel.MEMBER_ACCESS);
const following = h.postBool("follow");
await GroupsHelper.SetFollowing(groupID, h.getUserId(), following);
h.success("Follow status has been successfully updated!");
}
/**
* Delete a group
*
* @param h Request handler
*/
public static async DeleteGroup(h: RequestHandler) {
const groupID = await h.postGroupIDWithAccess("groupID", GroupsAccessLevel.ADMIN_ACCESS);
await h.needUserPostPassword("password");
// TODO : implement method
h.error(500, "Method not implemented yet!");
}
/**
* Turn a GroupInfo object into a valid API object
*
* @param info Information about the group
* @param h Request handler
* @param advanced Specify whether advanced information should be returned
* in the request or not
* @returns Generated object
*/
private static async GroupInfoToAPI(info: GroupInfo, h: RequestHandler, advanced: boolean = false) : Promise<any> {
const membership = await GroupsHelper.GetMembershipInfo(info.id, h.optionnalUserID)
const data = {
id: info.id,
name: info.name,
icon_url: info.logoURL,
number_members: info.membersCount,
visibility: GROUPS_VISIBILITY_LEVELS[info.visiblity],
registration_level: GROUPS_REGISTRATION_LEVELS[info.registrationLevel],
posts_level: GROUPS_POSTS_LEVELS[info.postsCreationLevel],
virtual_directory: info.virtualDirectory ? info.virtualDirectory : "null",
membership: GROUPS_MEMBERSHIP_LEVELS[membership ? membership.level : GroupMembershipLevels.VISITOR],
following: membership ? membership.following : false
}
if(advanced) {
data["time_create"] = info.timeCreate;
data["description"] = info.hasDescription ? info.description : "null";
data["url"] = info.hasURL ? info.url : "null";
data["number_likes"] = await LikesHelper.Count(info.id, LikesType.GROUP);
data["is_liking"] = h.signedIn ? await LikesHelper.IsLiking(h.getUserId(), info.id, LikesType.GROUP) : false;
}
return data;
}
/**
* Convert a {GroupMember} object into an API entry
*
* @param m Group Member to transform
*/
private static GroupMemberToAPI(m: GroupMember) : Object {
return {
user_id: m.userID,
group_id: m.groupID,
time_create: m.timeCreate,
level: GROUPS_MEMBERSHIP_LEVELS[m.level]
};
}
}