mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 10:34:17 +00:00
merge from local
This commit is contained in:
parent
6aba03e310
commit
24d6203d74
@ -8,7 +8,7 @@
|
||||
3005 -> marketplace
|
||||
3006 -> sync
|
||||
3007 -> ems (External Messaging Service)
|
||||
3008 -> unallocated
|
||||
3008 -> users
|
||||
3009 -> unallocated
|
||||
3010 -> unallocated
|
||||
3011 -> unallocated
|
||||
|
@ -5,7 +5,7 @@ export default class Token {
|
||||
static get strategy() {
|
||||
return {
|
||||
secret: process.env.JWT_SECRET,
|
||||
expiresIn: process.env.JWT_EXPIRES_IN ?? "1h",
|
||||
expiresIn: process.env.JWT_EXPIRES_IN ?? "24h",
|
||||
algorithm: process.env.JWT_ALGORITHM ?? "HS256",
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,7 @@ export default {
|
||||
collection: "posts",
|
||||
schema: {
|
||||
user_id: { type: String, required: true },
|
||||
timestamp: { type: String, required: true },
|
||||
created_at: { type: Date, default: Date.now, required: true },
|
||||
created_at: { type: String, required: true },
|
||||
message: { type: String },
|
||||
attachments: { type: Array, default: [] },
|
||||
flags: { type: Array, default: [] },
|
||||
|
@ -4,18 +4,16 @@ export default {
|
||||
schema: {
|
||||
username: { type: String, required: true },
|
||||
password: { type: String, required: true, select: false },
|
||||
email: { type: String, required: true },
|
||||
email: { type: String, required: true, select: false },
|
||||
description: { type: String, default: null },
|
||||
created_at: { type: String },
|
||||
public_name: { type: String, default: null },
|
||||
fullName: { type: String, default: null },
|
||||
cover: { type: String, default: null },
|
||||
avatar: { type: String, default: null },
|
||||
roles: { type: Array, default: [] },
|
||||
verified: { type: Boolean, default: false },
|
||||
birthday: { type: Date, default: null },
|
||||
badges: { type: Array, default: [] },
|
||||
links: { type: Array, default: [] },
|
||||
createdAt: { type: String },
|
||||
created_at: { type: String },
|
||||
birthday: { type: Date, default: null },
|
||||
}
|
||||
}
|
@ -1,9 +1,7 @@
|
||||
import withAuthentication from "../withAuthentication"
|
||||
|
||||
export default (req, res, next) => {
|
||||
export default async (req, res, next) => {
|
||||
if (req.headers?.authorization) {
|
||||
withAuthentication(req, res, next)
|
||||
} else {
|
||||
next()
|
||||
await withAuthentication(req, res, next)
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ import Account from "@classes/account"
|
||||
export default async (payload) => {
|
||||
requiredFields(["username", "password", "email"], payload)
|
||||
|
||||
let { username, password, email, fullName, roles, avatar, acceptTos } = payload
|
||||
let { username, password, email, public_name, roles, avatar, acceptTos } = payload
|
||||
|
||||
if (ToBoolean(acceptTos) !== true) {
|
||||
throw new OperationError(400, "You must accept the terms of service in order to create an account.")
|
||||
@ -15,14 +15,17 @@ export default async (payload) => {
|
||||
await Account.usernameMeetPolicy(username)
|
||||
|
||||
// check if username is already taken
|
||||
const existentUser = await User.findOne({ username: username })
|
||||
const existentUser = await User
|
||||
.findOne({ username: username })
|
||||
|
||||
if (existentUser) {
|
||||
throw new OperationError(400, "User already exists")
|
||||
}
|
||||
|
||||
// check if the email is already in use
|
||||
const existentEmail = await User.findOne({ email: email })
|
||||
const existentEmail = await User
|
||||
.findOne({ email: email })
|
||||
.select("+email")
|
||||
|
||||
if (existentEmail) {
|
||||
throw new OperationError(400, "Email already in use")
|
||||
@ -37,10 +40,10 @@ export default async (payload) => {
|
||||
username: username,
|
||||
password: hash,
|
||||
email: email,
|
||||
fullName: fullName,
|
||||
public_name: public_name,
|
||||
avatar: avatar ?? `https://api.dicebear.com/7.x/thumbs/svg?seed=${username}`,
|
||||
roles: roles,
|
||||
createdAt: new Date().getTime(),
|
||||
created_at: new Date().getTime(),
|
||||
acceptTos: acceptTos,
|
||||
})
|
||||
|
||||
|
@ -7,7 +7,7 @@ export default async ({ username, password, hash }, user) => {
|
||||
|
||||
let query = isEmail ? { email: username } : { username: username }
|
||||
|
||||
user = await User.findOne(query).select("+password")
|
||||
user = await User.findOne(query).select("+email").select("+password")
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
|
@ -7,14 +7,17 @@ export default async (req) => {
|
||||
throw new OperationError(400, "Missing username or email")
|
||||
}
|
||||
|
||||
const user = await User.findOne({
|
||||
$or: [
|
||||
{ username: username },
|
||||
{ email: email },
|
||||
]
|
||||
}).catch((error) => {
|
||||
return false
|
||||
})
|
||||
const user = await User
|
||||
.findOne({
|
||||
$or: [
|
||||
{ username: username },
|
||||
{ email: email },
|
||||
]
|
||||
})
|
||||
.select("+email")
|
||||
.catch((error) => {
|
||||
return false
|
||||
})
|
||||
|
||||
if (user) {
|
||||
return {
|
||||
|
@ -6,7 +6,9 @@ export default async (req) => {
|
||||
|
||||
const { email } = req.body
|
||||
|
||||
const user = await User.findOne({ email })
|
||||
const user = await User
|
||||
.findOne({ email })
|
||||
.select("+email")
|
||||
|
||||
if (!user) {
|
||||
throw new OperationError(400, "User not found")
|
||||
|
@ -1,140 +0,0 @@
|
||||
import { Controller } from "linebridge/dist/server"
|
||||
|
||||
import pmap from "p-map"
|
||||
|
||||
import getPosts from "./services/getPosts"
|
||||
|
||||
import getGlobalReleases from "./services/getGlobalReleases"
|
||||
import getReleasesFromFollowing from "./services/getReleasesFromFollowing"
|
||||
import getPlaylistsFromFollowing from "./services/getPlaylistsFromFollowing"
|
||||
|
||||
export default class FeedController extends Controller {
|
||||
static refName = "FeedController"
|
||||
static useRoute = "/feed"
|
||||
|
||||
httpEndpoints = {
|
||||
get: {
|
||||
"/timeline": {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: async (req, res) => {
|
||||
const for_user_id = req.user?._id.toString()
|
||||
|
||||
if (!for_user_id) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid user id"
|
||||
})
|
||||
}
|
||||
|
||||
// fetch posts
|
||||
let posts = await getPosts({
|
||||
for_user_id,
|
||||
limit: req.query?.limit,
|
||||
skip: req.query?.trim,
|
||||
})
|
||||
|
||||
// add type to posts and playlists
|
||||
posts = posts.map((data) => {
|
||||
data.type = "post"
|
||||
|
||||
return data
|
||||
})
|
||||
|
||||
let feed = [
|
||||
...posts,
|
||||
]
|
||||
|
||||
// sort feed
|
||||
feed.sort((a, b) => {
|
||||
return new Date(b.created_at) - new Date(a.created_at)
|
||||
})
|
||||
|
||||
return res.json(feed)
|
||||
}
|
||||
},
|
||||
"/music/global": {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: async (req, res) => {
|
||||
const for_user_id = req.user?._id.toString()
|
||||
|
||||
if (!for_user_id) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid user id"
|
||||
})
|
||||
}
|
||||
|
||||
// fetch playlists from global
|
||||
const result = await getGlobalReleases({
|
||||
for_user_id,
|
||||
limit: req.query?.limit,
|
||||
skip: req.query?.trim,
|
||||
})
|
||||
|
||||
return res.json(result)
|
||||
}
|
||||
},
|
||||
"/music": {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: async (req, res) => {
|
||||
const for_user_id = req.user?._id.toString()
|
||||
|
||||
if (!for_user_id) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid user id"
|
||||
})
|
||||
}
|
||||
|
||||
const searchers = [
|
||||
getGlobalReleases,
|
||||
//getReleasesFromFollowing,
|
||||
//getPlaylistsFromFollowing,
|
||||
]
|
||||
|
||||
let result = await pmap(
|
||||
searchers,
|
||||
async (fn, index) => {
|
||||
const data = await fn({
|
||||
for_user_id,
|
||||
limit: req.query?.limit,
|
||||
skip: req.query?.trim,
|
||||
})
|
||||
|
||||
return data
|
||||
}, {
|
||||
concurrency: 3,
|
||||
},)
|
||||
|
||||
result = result.reduce((acc, cur) => {
|
||||
return [...acc, ...cur]
|
||||
}, [])
|
||||
|
||||
return res.json(result)
|
||||
}
|
||||
},
|
||||
"/posts": {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: async (req, res) => {
|
||||
const for_user_id = req.user?._id.toString()
|
||||
|
||||
if (!for_user_id) {
|
||||
return res.status(400).json({
|
||||
error: "Invalid user id"
|
||||
})
|
||||
}
|
||||
|
||||
let feed = []
|
||||
|
||||
// fetch posts
|
||||
const posts = await getPosts({
|
||||
for_user_id,
|
||||
limit: req.query?.limit,
|
||||
skip: req.query?.trim,
|
||||
})
|
||||
|
||||
feed = feed.concat(posts)
|
||||
|
||||
return res.json(feed)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
import { Release } from "@db_models"
|
||||
|
||||
export default async (payload) => {
|
||||
const {
|
||||
limit = 20,
|
||||
skip = 0,
|
||||
} = payload
|
||||
|
||||
let releases = await Release.find({
|
||||
$or: [
|
||||
{ public: true },
|
||||
]
|
||||
})
|
||||
.sort({ created_at: -1 })
|
||||
.limit(limit)
|
||||
.skip(skip)
|
||||
|
||||
releases = Promise.all(releases.map(async (release) => {
|
||||
release = release.toObject()
|
||||
|
||||
return release
|
||||
}))
|
||||
|
||||
return releases
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
import { Playlist, User, UserFollow } from "@db_models"
|
||||
|
||||
export default async (payload) => {
|
||||
const {
|
||||
for_user_id,
|
||||
limit = 20,
|
||||
skip = 0,
|
||||
} = payload
|
||||
|
||||
// get post from users that the user follows
|
||||
const followingUsers = await UserFollow.find({
|
||||
user_id: for_user_id
|
||||
})
|
||||
|
||||
const followingUserIds = followingUsers.map((followingUser) => followingUser.to)
|
||||
|
||||
const fetchFromUserIds = [
|
||||
for_user_id,
|
||||
...followingUserIds,
|
||||
]
|
||||
|
||||
// firter out the playlists that are not public
|
||||
let playlists = await Playlist.find({
|
||||
user_id: { $in: fetchFromUserIds },
|
||||
$or: [
|
||||
{ public: true },
|
||||
]
|
||||
})
|
||||
.sort({ created_at: -1 })
|
||||
.limit(limit)
|
||||
.skip(skip)
|
||||
|
||||
playlists = Promise.all(playlists.map(async (playlist) => {
|
||||
playlist = playlist.toObject()
|
||||
|
||||
playlist.type = "playlist"
|
||||
|
||||
return playlist
|
||||
}))
|
||||
|
||||
return playlists
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
import { Post, UserFollow } from "@db_models"
|
||||
|
||||
import fullfillPostsData from "@utils/fullfillPostsData"
|
||||
|
||||
export default async (payload) => {
|
||||
const {
|
||||
for_user_id,
|
||||
limit = 20,
|
||||
skip = 0,
|
||||
} = payload
|
||||
|
||||
// get post from users that the user follows
|
||||
const followingUsers = await UserFollow.find({
|
||||
user_id: for_user_id
|
||||
})
|
||||
|
||||
const followingUserIds = followingUsers.map((followingUser) => followingUser.to)
|
||||
|
||||
const fetchPostsFromIds = [
|
||||
for_user_id,
|
||||
...followingUserIds,
|
||||
]
|
||||
|
||||
let posts = await Post.find({
|
||||
user_id: { $in: fetchPostsFromIds }
|
||||
})
|
||||
.sort({ created_at: -1 })
|
||||
.limit(limit)
|
||||
.skip(skip)
|
||||
|
||||
// fullfill data
|
||||
posts = await fullfillPostsData({
|
||||
posts,
|
||||
for_user_id,
|
||||
skip,
|
||||
})
|
||||
|
||||
return posts
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
import { Release, UserFollow } from "@db_models"
|
||||
|
||||
export default async (payload) => {
|
||||
const {
|
||||
for_user_id,
|
||||
limit = 20,
|
||||
skip = 0,
|
||||
} = payload
|
||||
|
||||
// get post from users that the user follows
|
||||
const followingUsers = await UserFollow.find({
|
||||
user_id: for_user_id
|
||||
})
|
||||
|
||||
const followingUserIds = followingUsers.map((followingUser) => followingUser.to)
|
||||
|
||||
const fetchFromUserIds = [
|
||||
for_user_id,
|
||||
...followingUserIds,
|
||||
]
|
||||
|
||||
// firter out the releases that are not public
|
||||
let releases = await Release.find({
|
||||
user_id: { $in: fetchFromUserIds },
|
||||
$or: [
|
||||
{ public: true },
|
||||
]
|
||||
})
|
||||
.sort({ created_at: -1 })
|
||||
.limit(limit)
|
||||
.skip(skip)
|
||||
|
||||
releases = Promise.all(releases.map(async (release) => {
|
||||
release = release.toObject()
|
||||
|
||||
return release
|
||||
}))
|
||||
|
||||
return releases
|
||||
}
|
@ -1,7 +1,4 @@
|
||||
export { default as PublicController } from "./PublicController"
|
||||
|
||||
export { default as UserController } from "./UserController"
|
||||
export { default as AuthController } from "./AuthController"
|
||||
export { default as SessionController } from "./SessionController"
|
||||
|
||||
export { default as StatusController } from "./StatusController"
|
||||
|
@ -6,6 +6,8 @@ import StorageClient from "@shared-classes/StorageClient"
|
||||
|
||||
import Token from "@lib/token"
|
||||
|
||||
import SharedMiddlewares from "@shared-middlewares"
|
||||
|
||||
export default class API extends Server {
|
||||
static refName = "main"
|
||||
static useEngine = "hyper-express"
|
||||
@ -22,7 +24,11 @@ export default class API extends Server {
|
||||
}
|
||||
}
|
||||
|
||||
middlewares = require("@middlewares")
|
||||
middlewares = {
|
||||
...require("@middlewares").default,
|
||||
...SharedMiddlewares
|
||||
}
|
||||
|
||||
controllers = require("@controllers")
|
||||
events = require("./events")
|
||||
|
||||
|
@ -1,6 +1 @@
|
||||
export { default as withAuthentication } from "./withAuthentication"
|
||||
export { default as withOptionalAuthentication } from "./withOptionalAuthentication"
|
||||
|
||||
export { default as roles } from "./roles"
|
||||
export { default as onlyAdmin } from "./onlyAdmin"
|
||||
export { default as permissions } from "./permissions"
|
@ -1,7 +0,0 @@
|
||||
export default (req, res, next) => {
|
||||
if (!req.user.roles.includes("admin")) {
|
||||
return res.status(403).json({ error: "To make this request it is necessary to have administrator permissions" })
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
export default (req, res, next) => {
|
||||
req.isAdmin = () => {
|
||||
if (req.user.roles.includes("admin")) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
req.hasRole = (role) => {
|
||||
if (req.user.roles.includes(role)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
import { Session, User, authorizedServerTokens } from "@db_models"
|
||||
|
||||
import createTokenRegeneration from "@utils/createTokenRegeneration"
|
||||
|
||||
import SecureEntry from "@lib/secureEntry"
|
||||
|
||||
import jwt from "jsonwebtoken"
|
||||
|
||||
export default async (req, res, next) => {
|
||||
function reject(description) {
|
||||
return res.status(401).json({ error: `${description ?? "Invalid session"}` })
|
||||
}
|
||||
|
||||
try {
|
||||
const tokenAuthHeader = req.headers?.authorization?.split(" ")
|
||||
|
||||
if (!tokenAuthHeader) {
|
||||
return reject("Missing token header")
|
||||
}
|
||||
|
||||
if (!tokenAuthHeader[1]) {
|
||||
return reject("Recived header, missing token")
|
||||
}
|
||||
|
||||
switch (tokenAuthHeader[0]) {
|
||||
case "Bearer": {
|
||||
const token = tokenAuthHeader[1]
|
||||
let decoded = null
|
||||
|
||||
try {
|
||||
decoded = jwt.decode(token)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
if (!decoded) {
|
||||
return reject("Cannot decode token")
|
||||
}
|
||||
|
||||
jwt.verify(token, global.jwtStrategy.secretOrKey, async (err) => {
|
||||
const sessions = await Session.find({ user_id: decoded.user_id })
|
||||
const currentSession = sessions.find((session) => session.token === token)
|
||||
|
||||
if (!currentSession) {
|
||||
return reject("Cannot find session")
|
||||
}
|
||||
|
||||
const userData = await User.findOne({ _id: currentSession.user_id }).select("+refreshToken")
|
||||
|
||||
if (!userData) {
|
||||
return reject("Cannot find user")
|
||||
}
|
||||
|
||||
// if cannot verify token, start regeneration process
|
||||
if (err) {
|
||||
// first check if token is only expired, if is corrupted, reject
|
||||
if (err.name !== "TokenExpiredError") {
|
||||
return reject("Invalid token, cannot regenerate")
|
||||
}
|
||||
|
||||
const refreshToken = await createTokenRegeneration(token)
|
||||
|
||||
// now send the regeneration token to the client (start Expired Exception Event [EEE])
|
||||
return res.status(401).json({
|
||||
error: "Token expired",
|
||||
refreshToken: refreshToken,
|
||||
})
|
||||
}
|
||||
|
||||
req.user = userData
|
||||
req.jwtToken = token
|
||||
req.decodedToken = decoded
|
||||
req.currentSession = currentSession
|
||||
|
||||
return next()
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
case "Server": {
|
||||
const [client_id, token] = tokenAuthHeader[1].split(":")
|
||||
|
||||
if (client_id === "undefined" || token === "undefined") {
|
||||
return reject("Invalid server token")
|
||||
}
|
||||
|
||||
const secureEntries = new SecureEntry(authorizedServerTokens)
|
||||
|
||||
const serverTokenEntry = await secureEntries.get(client_id, undefined, {
|
||||
keyName: "client_id",
|
||||
valueName: "token",
|
||||
})
|
||||
|
||||
if (!serverTokenEntry) {
|
||||
return reject("Invalid server token")
|
||||
}
|
||||
|
||||
if (serverTokenEntry !== token) {
|
||||
return reject("Missmatching server token")
|
||||
}
|
||||
|
||||
req.user = {
|
||||
__server: true,
|
||||
_id: client_id,
|
||||
roles: ["server"],
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
default: {
|
||||
return reject("Invalid token type")
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return res.status(500).json({ error: "This endpoint needs authentication, but an error occurred." })
|
||||
}
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
import withAuthentication from "../withAuthentication"
|
||||
|
||||
export default (req, res, next) => {
|
||||
if (req.headers?.authorization) {
|
||||
withAuthentication(req, res, next)
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
}
|
@ -1,7 +1,13 @@
|
||||
export default class Posts {
|
||||
static create = require("./methods/create").default
|
||||
|
||||
static feed = require("./methods/feed").default
|
||||
static data = require("./methods/data").default
|
||||
|
||||
static getLiked = require("./methods/getLiked").default
|
||||
static getSaved = require("./methods/getSaved").default
|
||||
static fromUserId = require("./methods/fromUserId").default
|
||||
static create = require("./methods/create").default
|
||||
static fullfillPost = require("./methods/fullfill").default
|
||||
static toggleSave = require("./methods/toggleSave").default
|
||||
static toggleLike = require("./methods/toggleLike").default
|
||||
static report = require("./methods/report").default
|
||||
static flag = require("./methods/flag").default
|
||||
}
|
@ -1,14 +1,10 @@
|
||||
import momentTimezone from "moment-timezone"
|
||||
import requiredFields from "@shared-utils/requiredFields"
|
||||
import { DateTime } from "luxon"
|
||||
|
||||
import { Post } from "@db_models"
|
||||
|
||||
export default async (payload) => {
|
||||
if (!payload) {
|
||||
throw new Error("Payload is required")
|
||||
}
|
||||
|
||||
await requiredFields(["user_id", "timestamp"], payload)
|
||||
export default async (payload = {}) => {
|
||||
await requiredFields(["user_id"], payload)
|
||||
|
||||
let { user_id, message, attachments, timestamp, reply_to } = payload
|
||||
|
||||
@ -16,18 +12,18 @@ export default async (payload) => {
|
||||
const isAttachmentsValid = Array.isArray(attachments) && attachments.length > 0
|
||||
|
||||
if (!isAttachmentsValid && !message) {
|
||||
throw new Error("Cannot create a post without message or attachments")
|
||||
throw new OperationError(400, "Cannot create a post without message or attachments")
|
||||
}
|
||||
|
||||
const current_timezone = momentTimezone.tz.guess()
|
||||
const created_at = momentTimezone.tz(Date.now(), current_timezone).format()
|
||||
if (!timestamp) {
|
||||
timestamp = DateTime.local().toISO()
|
||||
}
|
||||
|
||||
let post = new Post({
|
||||
created_at: created_at,
|
||||
created_at: timestamp,
|
||||
user_id: typeof user_id === "object" ? user_id.toString() : user_id,
|
||||
message: message,
|
||||
attachments: attachments ?? [],
|
||||
timestamp: timestamp,
|
||||
reply_to: reply_to,
|
||||
flags: [],
|
||||
})
|
||||
|
@ -1,38 +1,17 @@
|
||||
import { Post, SavedPost } from "@db_models"
|
||||
import { Post } from "@db_models"
|
||||
import fullfillPostsData from "./fullfill"
|
||||
|
||||
export default async (payload) => {
|
||||
export default async (payload = {}) => {
|
||||
let {
|
||||
from_user_id,
|
||||
for_user_id,
|
||||
post_id,
|
||||
query = {},
|
||||
skip = 0,
|
||||
limit = 20,
|
||||
sort = { created_at: -1 },
|
||||
savedOnly = false,
|
||||
} = payload
|
||||
|
||||
let posts = []
|
||||
let savedPostsIds = []
|
||||
|
||||
// if for_user_id is provided, get saved posts
|
||||
if (for_user_id) {
|
||||
const savedPosts = await SavedPost.find({ user_id: for_user_id })
|
||||
.sort({ saved_at: -1 })
|
||||
|
||||
savedPostsIds = savedPosts.map((savedPost) => savedPost.post_id)
|
||||
}
|
||||
|
||||
// if from_user_id is provided, get posts from that user
|
||||
if (from_user_id) {
|
||||
query.user_id = from_user_id
|
||||
}
|
||||
|
||||
// if savedOnly is true,set to query to get only saved posts
|
||||
if (savedOnly) {
|
||||
query._id = { $in: savedPostsIds }
|
||||
}
|
||||
|
||||
if (post_id) {
|
||||
try {
|
||||
@ -49,16 +28,6 @@ export default async (payload) => {
|
||||
.limit(limit)
|
||||
}
|
||||
|
||||
// short posts if is savedOnly argument
|
||||
if (savedOnly) {
|
||||
posts.sort((a, b) => {
|
||||
return (
|
||||
savedPostsIds.indexOf(a._id.toString()) -
|
||||
savedPostsIds.indexOf(b._id.toString())
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
// fullfill data
|
||||
posts = await fullfillPostsData({
|
||||
posts,
|
||||
@ -68,6 +37,10 @@ export default async (payload) => {
|
||||
|
||||
// if post_id is specified, return only one post
|
||||
if (post_id) {
|
||||
if (posts.length === 0) {
|
||||
throw new OperationError(404, "Post not found")
|
||||
}
|
||||
|
||||
return posts[0]
|
||||
}
|
||||
|
||||
|
43
packages/server/services/posts/classes/posts/methods/feed.js
Normal file
43
packages/server/services/posts/classes/posts/methods/feed.js
Normal file
@ -0,0 +1,43 @@
|
||||
import { UserFollow } from "@db_models"
|
||||
|
||||
import GetPostData from "./data"
|
||||
|
||||
export default async (payload = {}) => {
|
||||
let {
|
||||
user_id,
|
||||
skip,
|
||||
limit,
|
||||
} = payload
|
||||
|
||||
let query = {}
|
||||
|
||||
//TODO: include posts from groups
|
||||
//TODO: include promotional posts
|
||||
if (user_id) {
|
||||
const from_users = []
|
||||
|
||||
from_users.push(user_id)
|
||||
|
||||
// get post from users that the user follows
|
||||
const followingUsers = await UserFollow.find({
|
||||
user_id: user_id
|
||||
})
|
||||
|
||||
const followingUserIds = followingUsers.map((followingUser) => followingUser.to)
|
||||
|
||||
from_users.push(...followingUserIds)
|
||||
|
||||
query.user_id = {
|
||||
$in: from_users
|
||||
}
|
||||
}
|
||||
|
||||
const posts = await GetPostData({
|
||||
for_user_id: user_id,
|
||||
skip,
|
||||
limit,
|
||||
query: query,
|
||||
})
|
||||
|
||||
return posts
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
export default async () => {
|
||||
throw new OperationError(501, "Not implemented")
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import GetData from "./data"
|
||||
|
||||
export default async (payload = {}) => {
|
||||
const {
|
||||
for_user_id,
|
||||
user_id,
|
||||
skip,
|
||||
limit,
|
||||
} = payload
|
||||
|
||||
if (!user_id) {
|
||||
throw new OperationError(400, "Missing user_id")
|
||||
}
|
||||
|
||||
return await GetData({
|
||||
for_user_id: for_user_id,
|
||||
skip,
|
||||
limit,
|
||||
query: {
|
||||
user_id: {
|
||||
$in: user_id
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { User, Comment, PostLike, SavedPost } from "@db_models"
|
||||
|
||||
export default async (payload) => {
|
||||
export default async (payload = {}) => {
|
||||
let {
|
||||
posts,
|
||||
for_user_id,
|
||||
@ -28,7 +28,9 @@ export default async (payload) => {
|
||||
_id: {
|
||||
$in: posts.map((post) => post.user_id)
|
||||
}
|
||||
}),
|
||||
})
|
||||
.select("-email")
|
||||
.select("-birthday"),
|
||||
PostLike.find({
|
||||
post_id: {
|
||||
$in: posts.map((post) => post._id)
|
||||
|
@ -0,0 +1,24 @@
|
||||
import { PostLike } from "@db_models"
|
||||
import GetData from "./data"
|
||||
|
||||
export default async (payload = {}) => {
|
||||
let { user_id } = payload
|
||||
|
||||
if (!user_id) {
|
||||
throw new OperationError(400, "Missing user_id")
|
||||
}
|
||||
|
||||
let ids = await PostLike.find({ user_id })
|
||||
|
||||
ids = ids.map((item) => item.post_id)
|
||||
|
||||
return await GetData({
|
||||
for_user_id: user_id,
|
||||
query: {
|
||||
_id: {
|
||||
$in: ids
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -0,0 +1,24 @@
|
||||
import { SavedPost } from "@db_models"
|
||||
import GetData from "./data"
|
||||
|
||||
export default async (payload = {}) => {
|
||||
let { user_id } = payload
|
||||
|
||||
if (!user_id) {
|
||||
throw new OperationError(400, "Missing user_id")
|
||||
}
|
||||
|
||||
let ids = await SavedPost.find({ user_id })
|
||||
|
||||
ids = ids.map((item) => item.post_id)
|
||||
|
||||
return await GetData({
|
||||
for_user_id: user_id,
|
||||
query: {
|
||||
_id: {
|
||||
$in: ids
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -0,0 +1,3 @@
|
||||
export default async () => {
|
||||
throw new OperationError(501, "Not implemented")
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
import { Post, PostLike } from "@db_models"
|
||||
|
||||
export default async (payload = {}) => {
|
||||
let { post_id, user_id, to } = payload
|
||||
|
||||
if (!post_id || !user_id) {
|
||||
throw new OperationError(400, "Missing post_id or user_id")
|
||||
}
|
||||
|
||||
// check if post exist
|
||||
let existPost = await Post.findOne({
|
||||
post_id,
|
||||
})
|
||||
|
||||
if (!existPost) {
|
||||
throw new OperationError(404, "Post not found")
|
||||
}
|
||||
|
||||
let likeObj = await PostLike.findOne({
|
||||
post_id,
|
||||
user_id,
|
||||
})
|
||||
|
||||
if (typeof to === "undefined") {
|
||||
if (likeObj) {
|
||||
to = false
|
||||
} else {
|
||||
to = true
|
||||
}
|
||||
}
|
||||
|
||||
if (to) {
|
||||
likeObj = new PostLike({
|
||||
post_id,
|
||||
user_id,
|
||||
})
|
||||
|
||||
await likeObj.save()
|
||||
} else {
|
||||
await PostLike.findByIdAndDelete(likeObj._id)
|
||||
}
|
||||
|
||||
// global.engine.ws.io.of("/").emit(`post.${post_id}.likes.update`, {
|
||||
// to,
|
||||
// post_id,
|
||||
// user_id,
|
||||
// })
|
||||
|
||||
return {
|
||||
liked: to
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import { Post, SavedPost } from "@db_models"
|
||||
|
||||
export default async (payload = {}) => {
|
||||
let { post_id, user_id } = payload
|
||||
|
||||
if (!post_id || !user_id) {
|
||||
throw new OperationError(400, "Missing post_id or user_id")
|
||||
}
|
||||
|
||||
// check if post exist
|
||||
let existPost = await Post.findOne({
|
||||
post_id,
|
||||
})
|
||||
|
||||
if (!existPost) {
|
||||
throw new OperationError(404, "Post not found")
|
||||
}
|
||||
|
||||
let post = await SavedPost.findOne({ post_id, user_id })
|
||||
|
||||
if (post) {
|
||||
await SavedPost.findByIdAndDelete(post._id).catch((err) => {
|
||||
throw new OperationError(500, `An error has occurred: ${err.message}`)
|
||||
})
|
||||
|
||||
post = null
|
||||
} else {
|
||||
post = new SavedPost({
|
||||
post_id,
|
||||
user_id,
|
||||
})
|
||||
|
||||
await post.save().catch((err) => {
|
||||
throw new OperationError(500, `An error has occurred: ${err.message}`)
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
saved: !!post,
|
||||
}
|
||||
}
|
19
packages/server/services/posts/routes/feed/get.js
Normal file
19
packages/server/services/posts/routes/feed/get.js
Normal file
@ -0,0 +1,19 @@
|
||||
import Posts from "@classes/posts"
|
||||
|
||||
export default {
|
||||
middlewares: ["withOptionalAuthentication"],
|
||||
fn: async (req, res) => {
|
||||
const payload = {
|
||||
limit: req.query?.limit,
|
||||
skip: req.query?.skip,
|
||||
}
|
||||
|
||||
if (req.auth) {
|
||||
payload.user_id = req.auth.session.user_id
|
||||
}
|
||||
|
||||
const result = await Posts.feed(payload)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import Posts from "@classes/posts"
|
||||
|
||||
export default {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: async (req, res) => {
|
||||
const result = await Posts.toggleLike({
|
||||
post_id: req.params.post_id,
|
||||
user_id: req.auth.session.user_id
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import Posts from "@classes/posts"
|
||||
|
||||
export default {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: async (req, res) => {
|
||||
const result = await Posts.toggleSave({
|
||||
post_id: req.params.post_id,
|
||||
user_id: req.auth.session.user_id
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
10
packages/server/services/posts/routes/posts/liked/get.js
Normal file
10
packages/server/services/posts/routes/posts/liked/get.js
Normal file
@ -0,0 +1,10 @@
|
||||
import Posts from "@classes/posts"
|
||||
|
||||
export default {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: async (req) => {
|
||||
return await Posts.getLiked({
|
||||
user_id: req.auth.session.user_id
|
||||
})
|
||||
}
|
||||
}
|
@ -1,6 +1,13 @@
|
||||
export default async (req, res) => {
|
||||
return res.json({
|
||||
code: 0,
|
||||
message: "success",
|
||||
})
|
||||
import Posts from "@classes/posts"
|
||||
|
||||
export default {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: async (req, res) => {
|
||||
const result = await Posts.create({
|
||||
...req.body,
|
||||
user_id: req.auth.session.user_id,
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
10
packages/server/services/posts/routes/posts/saved/get.js
Normal file
10
packages/server/services/posts/routes/posts/saved/get.js
Normal file
@ -0,0 +1,10 @@
|
||||
import Posts from "@classes/posts"
|
||||
|
||||
export default {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: async (req) => {
|
||||
return await Posts.getSaved({
|
||||
user_id: req.auth.session.user_id
|
||||
})
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
export default async (req, res) => {
|
||||
await new Promise((r) => {
|
||||
setTimeout(r, 1000)
|
||||
})
|
||||
|
||||
return {
|
||||
code: 0,
|
||||
message: "success",
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import Posts from "@classes/posts"
|
||||
|
||||
export default {
|
||||
middlewares: ["withOptionalAuthentication"],
|
||||
fn: async (req, res) => {
|
||||
return await Posts.fromUserId({
|
||||
skip: req.query.skip,
|
||||
limit: req.query.limit,
|
||||
user_id: req.params.user_id,
|
||||
for_user_id: req.auth?.session?.user_id,
|
||||
})
|
||||
}
|
||||
}
|
3
packages/server/services/users/classes/users/index.js
Normal file
3
packages/server/services/users/classes/users/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
export default class Users {
|
||||
static data = require("./method/data").default
|
||||
}
|
21
packages/server/services/users/classes/users/method/data.js
Normal file
21
packages/server/services/users/classes/users/method/data.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { User } from "@db_models"
|
||||
|
||||
export default async (payload = {}) => {
|
||||
const { user_id, from_user_id } = payload
|
||||
|
||||
if (!user_id) {
|
||||
throw new OperationError(400, "Missing user_id")
|
||||
}
|
||||
|
||||
const user = await User.findOne({
|
||||
_id: user_id,
|
||||
}).catch((err) => {
|
||||
return false
|
||||
})
|
||||
|
||||
if (!user) {
|
||||
throw new OperationError(404, "User not found")
|
||||
}
|
||||
|
||||
return user
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "feed",
|
||||
"name": "users",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT"
|
@ -0,0 +1,13 @@
|
||||
import Users from "@classes/users"
|
||||
|
||||
export default {
|
||||
middlewares: ["withOptionalAuthentication"],
|
||||
fn: async (req) => {
|
||||
const { user_id } = req.params
|
||||
|
||||
return await Users.data({
|
||||
user_id: user_id,
|
||||
from_user_id: req.auth?.session.user_id,
|
||||
})
|
||||
}
|
||||
}
|
@ -1,13 +1,15 @@
|
||||
import { Server } from "linebridge/src/server"
|
||||
|
||||
import DbManager from "@shared-classes/DbManager"
|
||||
import RedisClient from "@shared-classes/RedisClient"
|
||||
|
||||
import SharedMiddlewares from "@shared-middlewares"
|
||||
|
||||
export default class API extends Server {
|
||||
static refName = "feed"
|
||||
static refName = "users"
|
||||
static useEngine = "hyper-express"
|
||||
static routesPath = `${__dirname}/routes`
|
||||
static listen_port = process.env.HTTP_LISTEN_PORT ?? 3007
|
||||
static listen_port = process.env.HTTP_LISTEN_PORT ?? 3008
|
||||
|
||||
middlewares = {
|
||||
...SharedMiddlewares
|
||||
@ -15,10 +17,12 @@ export default class API extends Server {
|
||||
|
||||
contexts = {
|
||||
db: new DbManager(),
|
||||
redis: RedisClient()
|
||||
}
|
||||
|
||||
async onInitialize() {
|
||||
await this.contexts.db.initialize()
|
||||
await this.contexts.redis.initialize()
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user