import { RequestHandler } from "../entities/RequestHandler"; import { UserHelper } from "../helpers/UserHelper"; import { PostsHelper } from "../helpers/PostsHelper"; import { Post, PostVisibilityLevel, PostKind, PostAccessLevel, PostPageKind, PostFile, PostLink } from "../entities/Post"; import { MoviesController } from "./MoviesController"; import { MoviesHelper } from "../helpers/MoviesHelper"; import { SurveyHelper } from "../helpers/SurveyHelper"; import { SurveyController } from "./SurveyController"; import { LikesHelper, LikesType } from "../helpers/LikesHelper"; import { CommentsHelper } from "../helpers/CommentsHelper"; import { CommentsController } from "./CommentsController"; import { GroupsAccessLevel } from "../entities/Group"; import { GroupsHelper } from "../helpers/GroupsHelper"; import { time } from "../utils/DateUtils"; import { findKey } from "../utils/ArrayUtils"; import { check_string_before_insert, check_youtube_id, checkURL } from "../utils/StringUtils"; import { pathUserData } from "../utils/UserDataUtils"; import { statSync } from "fs"; import { lookup } from "mime-types"; import { NewSurvey } from "../entities/NewSurvey"; import { FriendsHelper } from "../helpers/FriendsHelper"; import { NotifEventType } from "../entities/Notification"; import { NotificationsHelper } from "../helpers/NotificationsHelper"; /** * Posts controller * * @author Pierre HUBERT */ const VISIBILITY_LEVELS_API = {}; VISIBILITY_LEVELS_API[PostVisibilityLevel.VISIBILITY_PUBLIC] = "public"; VISIBILITY_LEVELS_API[PostVisibilityLevel.VISIBILITY_FRIENDS] = "friends"; VISIBILITY_LEVELS_API[PostVisibilityLevel.VISIBILITY_USER] = "private"; VISIBILITY_LEVELS_API[PostVisibilityLevel.VISIBILITY_GROUP_MEMBERS] = "members"; const ACCESS_LEVELS_API = {}; ACCESS_LEVELS_API[PostAccessLevel.NO_ACCESS] = "no-access"; ACCESS_LEVELS_API[PostAccessLevel.BASIC_ACCESS] = "basic"; ACCESS_LEVELS_API[PostAccessLevel.INTERMEDIATE_ACCESS] = "intermediate"; ACCESS_LEVELS_API[PostAccessLevel.FULL_ACCESS] = "full"; export class PostsController { /** * Get the posts of a user * * @param h Request handler */ public static async GetListUser(h: RequestHandler) { const userID = await h.postUserId("userID"); const startFrom = h.postInt("startFrom", 0); if(!await UserHelper.CanSeeUserPage(h.optionnalUserID, userID)) h.error(401, "You are not allowed to access this user posts !"); const posts = await PostsHelper.GetUserPosts(h.optionnalUserID, userID, startFrom); await this.SendMultiplePosts(h, posts); } /** * Get the list of posts of a group * * @param h Request handler */ public static async GetListGroup(h: RequestHandler) { const groupID = await h.postGroupIDWithAccess("groupID", GroupsAccessLevel.VIEW_ACCESS); const startFrom = h.postInt("startFrom", 0); const posts = await PostsHelper.GetGroupPosts(h.optionnalUserID, groupID, startFrom); await this.SendMultiplePosts(h, posts); } /** * Get the list of latest posts * * @param h Request handler */ public static async GetLatest(h: RequestHandler) { const startFrom = h.postInt("startFrom", 0); const includeGroups = h.postBool('include_groups', false); const posts = await PostsHelper.GetLatest(h.getUserId(), startFrom, 10, includeGroups); await this.SendMultiplePosts(h, posts); } /** * Get information about a single post * * @param h Request handler */ public static async GetSingle(h: RequestHandler) { const postID = await h.postPostIDWithAccess("postID"); const post = await PostsHelper.GetSingle(postID); h.send(await this.PostToAPI(h, post)); } /** * Create a new post * * @param h Request handler */ public static async CreatePost(h: RequestHandler) { // Determine the target for the new post let kindPage: PostPageKind; let pageID: number; let survey : NewSurvey | undefined = undefined; switch(h.postString("kind-page")) { // If the post is targetting a user case "user": kindPage = PostPageKind.PAGE_KIND_USER; pageID = await h.postUserId("kind-id"); if(!await UserHelper.CanCreatePosts(h.getUserId(), pageID)) h.error(401, "You are not allowed to create posts on this page!"); break; // For groups case "group": kindPage = PostPageKind.PAGE_KIND_GROUP; pageID = await h.postGroupIDWithAccess("kind-id", GroupsAccessLevel.MEMBER_ACCESS); // Check if the user can create posts on this group if(!await GroupsHelper.CanUserCreatePosts(pageID, h.getUserId())) h.error(401, "You are not allowed to create posts on this group!"); break; default: h.error(500, "Unsupported kind of page!"); } // Initialize new post information const newPost = new Post({ // Basic information about the post id: -1, userID: h.getUserId(), timeCreate: time(), kind: h.postString("kind"), content: h.postContent("content", 0), visibilityLevel: this.PostVisibilityLevel(h, "visibility"), // Post target kindPage: kindPage, pageID: pageID, }); // Process each kind of post switch(newPost.kind) { // Text posts case PostKind.POST_KIND_TEXT: // Check the string if(!check_string_before_insert(newPost.content)) h.error(400, "Specified post content is invalid!"); break; // Image posts case PostKind.POST_KIND_IMAGE: if(!h.hasFile("image")) h.error(400, "An error occured while receiving the image!"); // Save image const path = await h.savePostImage("image", "imgpost", 2000, 2000); newPost.file = new PostFile({ path: path, type: lookup(pathUserData(path, true)), size: statSync(pathUserData(path, true)).size }); break; // YouTube posts case PostKind.POST_KIND_YOUTUBE: const youtubeID = h.postString("youtube_id"); if(!check_youtube_id(youtubeID)) h.error(400, "Invalid YouTube ID!"); newPost.file = new PostFile({ path: youtubeID, size: 0, type: "youtube" }) break; // Personnal movies posts case PostKind.POST_KIND_MOVIE: const movieID = h.postInt("movieID"); if(!await MoviesHelper.DoesUserHas(h.getUserId(), movieID)) h.error(401, "You are not authorized to use this movie!"); newPost.movieID = movieID; break; // Web links posts case PostKind.POST_KIND_WEBLINK: const url = h.postURL("url"); // For now, for safety, we do not fetch page content newPost.link = new PostLink({ url: url, title: undefined, description: undefined, image: undefined }); break; // PDF posts case PostKind.POST_KIND_PDF: if(!h.hasFile("pdf")) h.error(401, "Missing PDF in 'pdf'!"); const pdf_path = await h.savePostFile("pdf", "post_pdf", "pdf", "application/pdf"); newPost.file = new PostFile({ path: pdf_path, type: lookup(pathUserData(pdf_path, true)), size: statSync(pathUserData(pdf_path, true)).size }); break; // Countdown timers case PostKind.POST_KIND_COUNTDOWN: newPost.timeEnd = h.postInt("time-end"); break; // Survey controller case PostKind.POST_KIND_SURVEY: // Create the survey survey = new NewSurvey({ question: h.postString("question"), userID: h.getUserId(), choices: h.postString("answers").split("<>") }) if(survey.choices.length < 2) h.error(401, "Survey must have at least two answers!"); break; default: h.error(500, "Unsupported kind of post!"); } // Create the post const postID = await PostsHelper.Create(newPost); // Create associated survey (if any) if(survey != undefined) { survey.postID = postID; SurveyHelper.Create(survey); } // Create a notification await NotificationsHelper.CreatePostNotification( h.getUserId(), postID, NotifEventType.ELEM_CREATED); h.send({ success: "The post has been created!", postID: postID }); } /** * Change the visiblity level of a post * * @param h Request handler */ public static async SetVisibilityLevel(h: RequestHandler) { const postID = await h.postPostIDWithAccess("postID", PostAccessLevel.FULL_ACCESS); const newVisibility = this.PostVisibilityLevel(h, "new_level"); await PostsHelper.SetLevel(postID, newVisibility); // TODO : depending on new level, delete (or not) notifications about the post h.success(""); } /** * Update the content of a post * * @param h Request handler */ public static async UpdateContent(h: RequestHandler) { const postID = await h.postPostIDWithAccess("postID", PostAccessLevel.FULL_ACCESS); const content = h.postContent("new_content"); if(!check_string_before_insert(content)) h.error(401, "Given content is invalid!"); await PostsHelper.SetContent(postID, content); // TODO : delete notifications targetting current user about the post h.success(); } /** * Delete a post * * @param h Request handler */ public static async DeletePost(h: RequestHandler) { const postID = await h.postPostIDWithAccess("postID", PostAccessLevel.INTERMEDIATE_ACCESS); await PostsHelper.Delete(postID); h.success(); } /** * Get the lists of targets where the current user can create posts * * @param h Request handler */ public static async GetTargets(h: RequestHandler) { const friends = await FriendsHelper.GetListThatAllowsPostsFromUser(h.getUserId()); const groups = await GroupsHelper.GetListUserWhereCanCreatePosts(h.getUserId()); h.send({ friends: friends, groups: groups }); } /** * Send multiple posts to the API * * @param h Request handler * @param posts The list of post to send */ private static async SendMultiplePosts(h: RequestHandler, posts: Array) { let list = []; for (const p of posts) { list.push(await this.PostToAPI(h, p)); } h.send(list); } /** * Turn a post object into an API entry * * @param h Request handler * @param p The post */ public static async PostToAPI(h: RequestHandler, p: Post) : Promise { let data : any = { ID: p.id, userID: p.userID, user_page_id: p.userPageID, group_id: p.groupID, post_time: p.timeCreate, content: p.hasContent ? p.content : null, visibility_level: VISIBILITY_LEVELS_API[p.visibilityLevel], kind: p.kind, // File specific file_size: !p.hasFile ? null : p.file.size, file_type: !p.hasFile ? null : p.file.type, file_path: !p.hasFile ? null : p.file.path, file_path_url: !p.hasFile ? null : p.file.url, // Movie specific video_id: p.hasMovie ? p.movieID : null, video_info: p.hasMovie ? MoviesController.MovieToAPI(await MoviesHelper.GetInfo(p.movieID)) : null, // Countdown timer specific time_end: p.hasTimeEnd ? p.timeEnd : null, // Weblink specific link_url: !p.hasLink ? null : p.link.url, link_title: !p.hasLink ? null : p.link.title, link_description: !p.hasLink ? null : p.link.description, link_image: !p.hasLink ? null : p.link.image, // Survey specific data_survey: !p.hasSurvey ? null : await SurveyController.SurveyToAPI(h, await SurveyHelper.GetInfo(p.id)), // Likes information likes: await LikesHelper.Count(p.id, LikesType.POST), userlike: h.signedIn ? await LikesHelper.IsLiking(h.getUserId(), p.id, LikesType.POST) : false, // Determine user access level user_access: ACCESS_LEVELS_API[await PostsHelper.GetAccessLevel(h.optionnalUserID, p)], // Load comments (if possible) comments: await PostsHelper.AllowCommentsOnPost(p) ? await CommentsController.CommentsToAPI(h, await CommentsHelper.Get(p.id)) : null, }; return data; } /** * Get the visibility level for a POST included in a request * * @param h Request handler * @param name The name of the POST field containing the visibility level of the user */ private static PostVisibilityLevel(h: RequestHandler, name: string) : PostVisibilityLevel { const levelKey = findKey(VISIBILITY_LEVELS_API, h.postString(name, 3)); if(levelKey == null) h.error(400, "Post visibility level level not recognized!"); return Number(levelKey); } }