From 5ad8dbed875efb78a13eb5cda9fa7a86b80ab745 Mon Sep 17 00:00:00 2001 From: srgooglo Date: Fri, 9 Sep 2022 12:23:24 +0200 Subject: [PATCH] reimplement `PostController` --- .../src/controllers/PostsController/index.js | 333 +++--------------- .../PostsController/methods/createPost.js | 27 ++ .../PostsController/methods/deletePost.js | 41 +++ .../PostsController/methods/getPostData.js | 30 ++ .../PostsController/methods/getPostsFeed.js | 42 +++ .../PostsController/methods/index.js | 7 + .../PostsController/methods/modifyPostData.js | 23 ++ .../PostsController/methods/toogleLike.js | 41 +++ 8 files changed, 264 insertions(+), 280 deletions(-) create mode 100644 packages/server/src/controllers/PostsController/methods/createPost.js create mode 100644 packages/server/src/controllers/PostsController/methods/deletePost.js create mode 100644 packages/server/src/controllers/PostsController/methods/getPostData.js create mode 100644 packages/server/src/controllers/PostsController/methods/getPostsFeed.js create mode 100644 packages/server/src/controllers/PostsController/methods/index.js create mode 100644 packages/server/src/controllers/PostsController/methods/modifyPostData.js create mode 100644 packages/server/src/controllers/PostsController/methods/toogleLike.js diff --git a/packages/server/src/controllers/PostsController/index.js b/packages/server/src/controllers/PostsController/index.js index 7251083e..6eb91f8f 100644 --- a/packages/server/src/controllers/PostsController/index.js +++ b/packages/server/src/controllers/PostsController/index.js @@ -1,178 +1,24 @@ import { Controller } from "linebridge/dist/server" import { Schematized } from "../../lib" -import { Post, User } from "../../models" + +import { CreatePost, ToogleLike, GetPostsFeed, GetPostData } from "./methods" export default class PostsController extends Controller { static refName = "PostsController" //static useMiddlewares = ["withAuthentication"] - methods = { - createPost: async (payload) => { - const { user_id, message, additions } = payload - - const userData = await User.findById(user_id) - - const post = new Post({ - user_id: typeof user_id === "object" ? user_id.toString() : user_id, - message: String(message).toString(), - additions: additions ?? [], - created_at: new Date().getTime(), - }) - - await post.save() - - global.wsInterface.io.emit(`post.new`, { - ...post.toObject(), - user: userData.toObject(), - }) - global.wsInterface.io.emit(`post.new.${post.user_id}`, { - ...post.toObject(), - user: userData.toObject(), - }) - - return post - }, - toogleLike: async (payload) => { - const { post_id, user_id } = payload - - const post = await Post.findById(post_id) - - if (post.likes.includes(user_id)) { - return this.methods.unlikePost({ post_id, user_id }) - } else { - return this.methods.likePost({ post_id, user_id }) - } - }, - likePost: async (payload) => { - const { user_id, post_id } = payload - - const userData = await User.findById(user_id) - const postData = await Post.findById(post_id) - - if (postData.likes.includes(user_id)) { - postData.likes = postData.likes.filter(id => id !== user_id) - await postData.save() - - return false - } - - postData.likes.push(user_id) - - await this.savePostData(postData) - - global.wsInterface.io.emit(`post.like`, { - ...postData.toObject(), - user: userData.toObject(), - }) - global.wsInterface.io.emit(`post.like.${postData.user_id}`, { - ...postData.toObject(), - user: userData.toObject(), - }) - global.wsInterface.io.emit(`post.like.${post_id}`, postData.toObject().likes) - - return postData - }, - unlikePost: async (payload) => { - const { user_id, post_id } = payload - - const userData = await User.findById(user_id) - const postData = await Post.findById(post_id) - - postData.likes = postData.likes.filter(id => id !== user_id) - - await this.savePostData(postData) - - global.wsInterface.io.emit(`post.unlike`, { - ...postData.toObject(), - user: userData.toObject(), - }) - global.wsInterface.io.emit(`post.unlike.${postData.user_id}`, { - ...postData.toObject(), - user: userData.toObject(), - }) - global.wsInterface.io.emit(`post.unlike.${post_id}`, postData.toObject().likes) - - return postData - }, - deletePost: async (payload) => { - const { post_id, user_id } = payload - - if (!user_id) { - throw new Error("user_id not provided") - } - - const postData = await Post.findById(post_id) - - if (!postData) { - throw new Error("Post not found") - } - - const hasAdmin = await this.methods.hasAdmin({ user_id }) - - // check if user is the owner of the post - if (postData.user_id !== user_id && !hasAdmin) { - throw new Error("You are not allowed to delete this post") - } - - await postData.remove() - global.wsInterface.io.emit(`post.delete`, post_id) - }, - hasAdmin: async (payload) => { - const { user_id } = payload - - if (!user_id) { - return false - } - - const userData = await User.findById(user_id) - - if (!userData) { - return false - } - - return userData.roles.includes("admin") - } - } - - savePostData = async (post) => { - await post.save() - - global.wsInterface.io.emit(`post.dataUpdate`, post.toObject()) - global.wsInterface.io.emit(`post.dataUpdate.${post._id}`, post.toObject()) - } - get = { "/feed": { fn: Schematized({ select: ["user_id"] }, async (req, res) => { - const feedLimit = req.query?.limit ?? 20 - const feedTrimIndex = req.query?.trim ?? 0 - - // make sure that sort by date descending - // trim index is used to get the last n posts - let posts = await Post.find(req.selection) - .sort({ created_at: -1 }) - .skip(feedTrimIndex) - .limit(feedLimit) - - // fetch and add user data to each post - posts = posts.map(async (post, index) => { - const user = await User.findById(post.user_id) - - if (feedTrimIndex > 0) { - index = Number(feedTrimIndex) + Number(index) - } - - return { - ...post.toObject(), - user: user.toObject(), - key: index, - } + let posts = await GetPostsFeed({ + feedLimit: req.query?.limit, + feedTrimIndex: req.query?.trim, + from_user_id: req.query?.user_id, + for_user_id: req.user?._id, }) - posts = await Promise.all(posts) - return res.json(posts) }) }, @@ -181,38 +27,33 @@ export default class PostsController extends Controller { select: ["post_id"], required: ["post_id"] }, async (req, res) => { - if (typeof req.selection.post_id !== "string") { - return res.status(400).json({ - error: "post_id must be a string" - }) - } + let post = await GetPostData({ + post_id: req.query?.post_id, + }).catch((error) => { + res.status(404).json({ error: error.message }) - const post = await Post.findById(req.selection.post_id).catch(() => null) - - if (!post) { - return res.status(404).json({ - error: "Post not found" - }) - } - - const user = await User.findById(post.user_id) - - return res.json({ - ...post.toObject(), - user: user.toObject(), + return null }) + + if (!post) return + + return res.json(post) }) } } put = { + + } + + post = { "/post": { middlewares: ["withAuthentication"], fn: Schematized({ required: ["message"], select: ["message", "additions"], }, async (req, res) => { - const post = await this.methods.createPost({ + const post = await CreatePost({ user_id: req.user.id, message: req.selection.message, additions: req.selection.additions, @@ -225,21 +66,20 @@ export default class PostsController extends Controller { middlewares: ["withAuthentication"], fn: Schematized({ required: ["post_id"], - select: ["post_id"], + select: ["post_id", "to"], }, async (req, res) => { - const post = await this.methods.toogleLike({ + const post = await ToogleLike({ user_id: req.user._id.toString(), post_id: req.selection.post_id, + to: req.selection.to, }).catch((err) => { + res.status(400).json({ + error: err.message + }) return false }) - if (!post) { - return res.json({ - error: err.message, - success: false - }) - } + if (!post) return return res.json({ success: true, @@ -247,100 +87,30 @@ export default class PostsController extends Controller { }) }) }, - "/like": { + "/post/:post_id/toogle_like": { middlewares: ["withAuthentication"], fn: Schematized({ - required: ["post_id"], - select: ["post_id"], + select: ["to"], }, async (req, res) => { - const post = await this.methods.likePost({ + const post = await ToogleLike({ user_id: req.user._id.toString(), - post_id: req.selection.post_id, + post_id: req.params.post_id, + to: req.selection.to, }).catch((err) => { + res.status(400).json({ + error: err.message + }) return false }) - if (!post) { - return res.json({ - success: false, - }) - } + if (!post) return return res.json({ success: true, + post }) }) }, - "/unlike": { - middlewares: ["withAuthentication"], - fn: Schematized({ - required: ["post_id"], - select: ["post_id"], - }, async (req, res) => { - const post = await this.methods.unlikePost({ - user_id: req.user._id.toString(), - post_id: req.selection.post_id, - }).catch((err) => { - return false - }) - - if (!post) { - return res.json({ - success: false, - }) - } - - return res.json({ - success: true, - }) - }) - }, - } - - post = { - "/fix_posts_data": { - public: false, // This will be functional with next versions of linebridge (Will exclude this endpoint from the server endpoint map) - middlewares: ["withAuthentication", "onlyAdmin"], - fn: async (req, res) => { - const posts = await Post.find() - - for await (let post of posts) { - if (Array.isArray(post.additions) && post.additions.length > 0) { - post.additions = post.additions.map((addition) => { - // fix strings additions - if (typeof addition === "string") { - addition = { - url: addition, - } - } - - // replace insecure http urls with https - if (addition.url.startsWith("http://")) { - addition.url = addition.url.replace("http://", "https://") - } - - // fix old local path resolve (${host}/upload/id => ${host}/storage/id) - const hostnamePath = addition.url.replace(/^https?:\/\//, "") - if (hostnamePath.startsWith(`${global.publicHostname}/upload`)) { - addition.url = addition.url.replace(`${global.publicHostname}/upload`, `${global.publicHostname}/storage`) - } - - console.log(`Processed addition >`, addition) - - return addition - }) - } - - await Post.findByIdAndUpdate(post._id, { - additions: post.additions, - }) - } - - return res.json({ - success: true - }) - } - }, } delete = { @@ -350,20 +120,23 @@ export default class PostsController extends Controller { required: ["post_id"], select: ["post_id"], }, async (req, res) => { - await this.methods.deletePost({ + const post = await DeletePost({ post_id: req.selection.post_id, - user_id: req.user._id.toString(), + by_user_id: req.user._id.toString(), + }).catch((err) => { + res.status(400).json({ + error: err.message + }) + + return false + }) + + if (!post) return + + return res.json({ + success: true, + post }) - .then(() => { - return res.json({ - success: true, - }) - }) - .catch((err) => { - return res.status(500).json({ - message: err.message, - }) - }) }) }, } diff --git a/packages/server/src/controllers/PostsController/methods/createPost.js b/packages/server/src/controllers/PostsController/methods/createPost.js new file mode 100644 index 00000000..7c86f5b0 --- /dev/null +++ b/packages/server/src/controllers/PostsController/methods/createPost.js @@ -0,0 +1,27 @@ +import { Post, User } from "../../../models" + +export default async (payload) => { + const { user_id, message, additions } = payload + + const userData = await User.findById(user_id) + + const post = new Post({ + user_id: typeof user_id === "object" ? user_id.toString() : user_id, + message: String(message).toString(), + additions: additions ?? [], + created_at: new Date().getTime(), + }) + + await post.save() + + global.wsInterface.io.emit(`post.new`, { + ...post.toObject(), + user: userData.toObject(), + }) + global.wsInterface.io.emit(`post.new.${post.user_id}`, { + ...post.toObject(), + user: userData.toObject(), + }) + + return post +} \ No newline at end of file diff --git a/packages/server/src/controllers/PostsController/methods/deletePost.js b/packages/server/src/controllers/PostsController/methods/deletePost.js new file mode 100644 index 00000000..261e367c --- /dev/null +++ b/packages/server/src/controllers/PostsController/methods/deletePost.js @@ -0,0 +1,41 @@ +import { Post, User } from "../../../models" + +async function hasAdmin(user_id) { + if (!user_id) { + return false + } + + const userData = await User.findById(user_id) + + if (!userData) { + return false + } + + return userData.roles.includes("admin") +} + +export default async (payload) => { + const { post_id, by_user_id } = payload + + if (!by_user_id) { + throw new Error("by_user_id not provided") + } + + const post = await Post.findById(post_id) + + if (!post) { + throw new Error("Post not found") + } + + const hasAdmin = await hasAdmin(by_user_id) + + // check if user is the owner of the post + if (post.user_id !== by_user_id && !hasAdmin) { + throw new Error("You are not allowed to delete this post") + } + + await post.remove() + global.wsInterface.io.emit(`post.delete`, post_id) + + return post.toObject() +} \ No newline at end of file diff --git a/packages/server/src/controllers/PostsController/methods/getPostData.js b/packages/server/src/controllers/PostsController/methods/getPostData.js new file mode 100644 index 00000000..e92a7ef2 --- /dev/null +++ b/packages/server/src/controllers/PostsController/methods/getPostData.js @@ -0,0 +1,30 @@ +import { Post, User } from "../../../models" + +export default async (payload) => { + let { + post_id, + } = payload + + if (!post_id) { + throw new Error("post_id not provided") + } + + let post = await Post.findById(post_id).catch(() => false) + + if (!post) { + throw new Error("Post not found") + } + + let user = await User.findById(post.user_id).catch(() => false) + + if (!user) { + user = { + username: "Deleted user", + } + } + + return { + ...post.toObject(), + user, + } +} \ No newline at end of file diff --git a/packages/server/src/controllers/PostsController/methods/getPostsFeed.js b/packages/server/src/controllers/PostsController/methods/getPostsFeed.js new file mode 100644 index 00000000..667db398 --- /dev/null +++ b/packages/server/src/controllers/PostsController/methods/getPostsFeed.js @@ -0,0 +1,42 @@ +import { Post, User } from "../../../models" + +export default async (payload) => { + let { + from_user_id, + for_user_id, + feedTrimIndex = 0, + feedLimit = 20, + } = payload + + let query = {} + + if (from_user_id) { + query.user_id = from_user_id + } + + // make sure that sort by date descending + // trim index is used to get the last n posts + let posts = await Post.find(query) + .sort({ created_at: -1 }) + .skip(feedTrimIndex) + .limit(feedLimit) + + // fetch and add user data to each post + posts = posts.map(async (post, index) => { + const user = await User.findById(post.user_id) + + if (feedTrimIndex > 0) { + index = Number(feedTrimIndex) + Number(index) + } + + return { + ...post.toObject(), + user: user.toObject(), + key: index, + } + }) + + posts = await Promise.all(posts) + + return posts +} \ No newline at end of file diff --git a/packages/server/src/controllers/PostsController/methods/index.js b/packages/server/src/controllers/PostsController/methods/index.js new file mode 100644 index 00000000..10499070 --- /dev/null +++ b/packages/server/src/controllers/PostsController/methods/index.js @@ -0,0 +1,7 @@ +export { default as CreatePost } from "./createPost" +export { default as ToogleLike } from "./toogleLike" +export { default as GetPostsFeed } from "./getPostsFeed" +export { default as GetPostData } from "./getPostData" +export { default as DeletePost } from "./deletePost" + +export { default as ModifyPostData } from "./modifyPostData" \ No newline at end of file diff --git a/packages/server/src/controllers/PostsController/methods/modifyPostData.js b/packages/server/src/controllers/PostsController/methods/modifyPostData.js new file mode 100644 index 00000000..d6b94c89 --- /dev/null +++ b/packages/server/src/controllers/PostsController/methods/modifyPostData.js @@ -0,0 +1,23 @@ +import { Post } from "../../../models" +import lodash from "lodash" + +export default async (post, modification) => { + if (typeof post === "string") { + post = await Post.findById(post).catch(() => false) + } + + if (!post) { + throw new Error("Cannot modify post data: post not found") + } + + if (typeof modification === "object") { + post = lodash.merge(post, modification) + } + + await post.save() + + global.wsInterface.io.emit(`post.dataUpdate`, post.toObject()) + global.wsInterface.io.emit(`post.dataUpdate.${post._id}`, post.toObject()) + + return post +} \ No newline at end of file diff --git a/packages/server/src/controllers/PostsController/methods/toogleLike.js b/packages/server/src/controllers/PostsController/methods/toogleLike.js new file mode 100644 index 00000000..ab2869a7 --- /dev/null +++ b/packages/server/src/controllers/PostsController/methods/toogleLike.js @@ -0,0 +1,41 @@ +import { Post, User } from "../../../models" +import modifyPostData from "./modifyPostData" + +export default async (payload) => { + let { post_id, user_id, to } = payload + + const post = await Post.findById(post_id).catch(() => false) + const userData = await User.findById(user_id).catch(() => false) + + if (!post) { + throw new Error("Post not found") + } + + if (!userData) { + throw new Error("User not found") + } + + if (typeof to === "undefined") { + to = !post.likes.includes(user_id) + } + + if (to) { + post.likes.push(user_id) + } else { + post.likes = post.likes.filter((id) => id !== user_id) + } + + await modifyPostData(post, { likes: post.likes }) + + global.wsInterface.io.emit(`post.${to ? "like" : "unlike"}`, { + ...post.toObject(), + user: userData.toObject(), + }) + global.wsInterface.io.emit(`post.${to ? "like" : "unlike"}.${post.user_id}`, { + ...post.toObject(), + user: userData.toObject(), + }) + global.wsInterface.io.emit(`post.${to ? "like" : "unlike"}.${post_id}`, post.toObject().likes) + + return post.toObject() +} \ No newline at end of file