diff --git a/packages/server/services/users/classes/users/method/getFollowers.js b/packages/server/services/users/classes/users/method/getFollowers.js index 2310a154..0cecdad9 100644 --- a/packages/server/services/users/classes/users/method/getFollowers.js +++ b/packages/server/services/users/classes/users/method/getFollowers.js @@ -1,35 +1,44 @@ import { User, UserFollow } from "@db_models" export default async (payload = {}) => { - const { user_id, data = false, limit = 50, offset = 0 } = payload + const { user_id, data = false, limit = 50, page = 0 } = payload if (!user_id) { throw new OperationError(400, "Missing user_id") } - if (data) { + const total_followers = await UserFollow.countDocuments({ + to: user_id, + }) + + if (data === true) { let followers = await UserFollow.find({ to: user_id, }) .limit(limit) - .skip(offset) + .skip(limit * page) + .lean() + + followers = followers.map((follow) => { + return follow.user_id + }) const followersData = await User.find({ _id: { - $in: followers.map((follow) => { - return follow.user_id - }), + $in: followers, }, }) - return followersData - } else { - const count = await UserFollow.countDocuments({ - to: user_id, - }) + const nextPage = page + 1 return { - count, + items: followersData, + total_items: total_followers, + has_more: total_followers > limit * nextPage, + } + } else { + return { + count: total_followers, } } } diff --git a/packages/server/services/users/classes/users/method/update.js b/packages/server/services/users/classes/users/method/update.js index 17a2da4c..2ebd952a 100644 --- a/packages/server/services/users/classes/users/method/update.js +++ b/packages/server/services/users/classes/users/method/update.js @@ -25,5 +25,13 @@ export default async (user_id, update) => { user = user.toObject() + const userSockets = await global.websockets.find.clientsByUserId( + user._id.toString(), + ) + + for (const userSocket of userSockets) { + userSocket.emit(`self:user:update`, user) + } + return user } diff --git a/packages/server/services/users/package.json b/packages/server/services/users/package.json index c6a050d5..0fc4260e 100644 --- a/packages/server/services/users/package.json +++ b/packages/server/services/users/package.json @@ -1,3 +1,6 @@ { - "name": "users" + "name": "users", + "dependencies": { + "linebridge": "^1.0.0-alpha.4" + } } diff --git a/packages/server/services/users/routes/users/[user_id]/badges/get.js b/packages/server/services/users/routes/users/[user_id]/badges/get.js index c12b9e4d..16fa58e8 100644 --- a/packages/server/services/users/routes/users/[user_id]/badges/get.js +++ b/packages/server/services/users/routes/users/[user_id]/badges/get.js @@ -1,21 +1,21 @@ import { User, Badge } from "@db_models" export default { - fn: async (req) => { - const { user_id } = req.params + fn: async (req) => { + const { user_id } = req.params - const user = await User.findOne({ - _id: user_id - }).catch((err) => { - return false - }) + const user = await User.findOne({ + _id: user_id, + }).catch((err) => { + return false + }) - const badges = await Badge.find({ - name: { - $in: user.badges - } - }) + const badges = await Badge.find({ + name: { + $in: user.badges, + }, + }) - return badges - } -} \ No newline at end of file + return badges + }, +} diff --git a/packages/server/services/users/routes/users/[user_id]/data/get.js b/packages/server/services/users/routes/users/[user_id]/data/get.js index ad9ae002..d2506728 100644 --- a/packages/server/services/users/routes/users/[user_id]/data/get.js +++ b/packages/server/services/users/routes/users/[user_id]/data/get.js @@ -1,7 +1,7 @@ import Users from "@classes/users" export default { - middlewares: ["withOptionalAuthentication"], + useMiddlewares: ["withOptionalAuthentication"], fn: async (req) => { const { user_id } = req.params @@ -10,7 +10,7 @@ export default { return await Users.data({ user_id: ids.length > 1 ? ids : user_id, from_user_id: req.auth?.session.user_id, - basic: req.query?.basic, + basic: ToBoolean(req.query?.basic), }) }, } diff --git a/packages/server/services/users/routes/users/[user_id]/follow/post.js b/packages/server/services/users/routes/users/[user_id]/follow/post.js index 1c8cda09..41c35777 100644 --- a/packages/server/services/users/routes/users/[user_id]/follow/post.js +++ b/packages/server/services/users/routes/users/[user_id]/follow/post.js @@ -1,12 +1,12 @@ import User from "@classes/users" export default { - middlewares: ["withAuthentication"], - fn: async (req) => { - return await User.toggleFollow({ - user_id: req.params.user_id, - from_user_id: req.auth.session.user_id, - to: req.body?.to, - }) - } -} \ No newline at end of file + useMiddlewares: ["withAuthentication"], + fn: async (req) => { + return await User.toggleFollow({ + user_id: req.params.user_id, + from_user_id: req.auth.session.user_id, + to: req.body?.to, + }) + }, +} diff --git a/packages/server/services/users/routes/users/[user_id]/followers/get.js b/packages/server/services/users/routes/users/[user_id]/followers/get.js index 53768f0e..04f325c4 100644 --- a/packages/server/services/users/routes/users/[user_id]/followers/get.js +++ b/packages/server/services/users/routes/users/[user_id]/followers/get.js @@ -1,12 +1,12 @@ import User from "@classes/users" export default { - fn: async (req) => { - return await User.getFollowers({ - user_id: req.params.user_id, - data: ToBoolean(req.query.fetchData), - limit: req.query.limit, - offset: req.query.offset, - }) - } -} \ No newline at end of file + fn: async (req) => { + return await User.getFollowers({ + user_id: req.params.user_id, + data: ToBoolean(req.query.fetchData), + limit: parseInt(req.query.limit), + page: parseInt(req.query.page), + }) + }, +} diff --git a/packages/server/services/users/routes/users/[user_id]/public-key/get.js b/packages/server/services/users/routes/users/[user_id]/public-key/get.js index 15d50b99..eb01da40 100644 --- a/packages/server/services/users/routes/users/[user_id]/public-key/get.js +++ b/packages/server/services/users/routes/users/[user_id]/public-key/get.js @@ -1,7 +1,7 @@ import { UserPublicKey } from "@db_models" export default { - middlewares: ["withAuthentication"], + useMiddlewares: ["withAuthentication"], fn: async (req) => { const targetUserId = req.params.user_id diff --git a/packages/server/services/users/routes/users/[user_id]/resolve-user_id/get.js b/packages/server/services/users/routes/users/[user_id]/resolve-user_id/get.js index 68fe7803..bd449a66 100644 --- a/packages/server/services/users/routes/users/[user_id]/resolve-user_id/get.js +++ b/packages/server/services/users/routes/users/[user_id]/resolve-user_id/get.js @@ -2,9 +2,9 @@ import Users from "@classes/users" // resolve user id from a username (passed from params) export default { - fn: async (req) => { - return await Users.resolveUserId({ - username: req.params.user_id, - }) - }, -} \ No newline at end of file + fn: async (req) => { + return await Users.resolveUserId({ + username: req.params.user_id, + }) + }, +} diff --git a/packages/server/services/users/routes/users/[user_id]/roles/get.js b/packages/server/services/users/routes/users/[user_id]/roles/get.js index 908d7de2..b01b4181 100644 --- a/packages/server/services/users/routes/users/[user_id]/roles/get.js +++ b/packages/server/services/users/routes/users/[user_id]/roles/get.js @@ -1,12 +1,12 @@ import Users from "@classes/users" export default { - middlewares: ["withOptionalAuthentication"], - fn: async (req) => { - const data = await Users.data({ - user_id: req.auth.session.user_id, - }) + useMiddlewares: ["withOptionalAuthentication"], + fn: async (req) => { + const data = await Users.data({ + user_id: req.auth.session.user_id, + }) - return data.roles - } -} \ No newline at end of file + return data.roles + }, +} diff --git a/packages/server/services/users/routes/users/self/config/get.js b/packages/server/services/users/routes/users/self/config/get.js index 539da041..0b97547c 100644 --- a/packages/server/services/users/routes/users/self/config/get.js +++ b/packages/server/services/users/routes/users/self/config/get.js @@ -1,25 +1,25 @@ import { UserConfig } from "@db_models" export default { - middlewares: ["withAuthentication"], - fn: async (req) => { - const key = req.query.key + useMiddlewares: ["withAuthentication"], + fn: async (req) => { + const key = req.query.key - let config = await UserConfig.findOne({ - user_id: req.auth.session.user_id - }) + let config = await UserConfig.findOne({ + user_id: req.auth.session.user_id, + }) - if (!config) { - config = await UserConfig.create({ - user_id: req.auth.session.user_id, - values: {} - }) - } + if (!config) { + config = await UserConfig.create({ + user_id: req.auth.session.user_id, + values: {}, + }) + } - if (key) { - return config.values?.[key] - } + if (key) { + return config.values?.[key] + } - return config.values - } -} \ No newline at end of file + return config.values + }, +} diff --git a/packages/server/services/users/routes/users/self/config/put.js b/packages/server/services/users/routes/users/self/config/put.js index 88d264ca..c6641a12 100644 --- a/packages/server/services/users/routes/users/self/config/put.js +++ b/packages/server/services/users/routes/users/self/config/put.js @@ -2,60 +2,65 @@ import { UserConfig } from "@db_models" import lodash from "lodash" const baseConfig = [ - { - key: "app:language", - type: "string", - value: "en-us" - }, - { - key: "auth:mfa", - type: "boolean", - value: false - }, + { + key: "app:language", + type: "string", + value: "en-us", + }, + { + key: "auth:mfa", + type: "boolean", + value: false, + }, ] export default { - middlewares: ["withAuthentication"], - fn: async (req) => { - let config = await UserConfig.findOne({ - user_id: req.auth.session.user_id - }) + useMiddlewares: ["withAuthentication"], + fn: async (req) => { + let config = await UserConfig.findOne({ + user_id: req.auth.session.user_id, + }) - const values = {} + const values = {} - baseConfig.forEach((config) => { - const fromBody = req.body[config.key] - if (typeof fromBody !== "undefined") { - if (typeof fromBody === config.type) { - values[config.key] = req.body[config.key] - } else { - throw new OperationError(400, `Invalid type for ${config.key}`) - } - } else { - values[config.key] = config.value - } - }) + baseConfig.forEach((config) => { + const fromBody = req.body[config.key] + if (typeof fromBody !== "undefined") { + if (typeof fromBody === config.type) { + values[config.key] = req.body[config.key] + } else { + throw new OperationError( + 400, + `Invalid type for ${config.key}`, + ) + } + } else { + values[config.key] = config.value + } + }) + if (!config) { + config = await UserConfig.create({ + user_id: req.auth.session.user_id, + values, + }) + } else { + const newValues = lodash.merge(config.values, values) - if (!config) { - config = await UserConfig.create({ - user_id: req.auth.session.user_id, - values - }) - } else { - const newValues = lodash.merge(config.values, values) + config = await UserConfig.updateOne( + { + user_id: req.auth.session.user_id, + }, + { + values: newValues, + }, + ) - config = await UserConfig.updateOne({ - user_id: req.auth.session.user_id - }, { - values: newValues - }) + config = await UserConfig.findOne({ + user_id: req.auth.session.user_id, + }) + } - config = await UserConfig.findOne({ - user_id: req.auth.session.user_id - }) - } - - return config.values - } -} \ No newline at end of file + return config.values + }, +} diff --git a/packages/server/services/users/routes/users/self/get.js b/packages/server/services/users/routes/users/self/get.js index 5cb1f441..16456a95 100644 --- a/packages/server/services/users/routes/users/self/get.js +++ b/packages/server/services/users/routes/users/self/get.js @@ -1,7 +1,7 @@ import Users from "@classes/users" export default { - middlewares: ["withAuthentication"], + useMiddlewares: ["withAuthentication"], fn: async (req) => { return await Users.data({ user_id: req.auth.session.user_id, diff --git a/packages/server/services/users/routes/users/self/keypair/get.js b/packages/server/services/users/routes/users/self/keypair/get.js index 35416987..6553ec12 100644 --- a/packages/server/services/users/routes/users/self/keypair/get.js +++ b/packages/server/services/users/routes/users/self/keypair/get.js @@ -1,7 +1,7 @@ import { UserDHKeyPair } from "@db_models" export default { - middlewares: ["withAuthentication"], + useMiddlewares: ["withAuthentication"], fn: async (req) => { const userId = req.auth.session.user_id diff --git a/packages/server/services/users/routes/users/self/keypair/post.js b/packages/server/services/users/routes/users/self/keypair/post.js index 97e528d6..a1576e08 100644 --- a/packages/server/services/users/routes/users/self/keypair/post.js +++ b/packages/server/services/users/routes/users/self/keypair/post.js @@ -1,7 +1,7 @@ import { UserDHKeyPair } from "@db_models" export default { - middlewares: ["withAuthentication"], + useMiddlewares: ["withAuthentication"], fn: async (req) => { const userId = req.auth.session.user_id const { str } = req.body diff --git a/packages/server/services/users/routes/users/self/public-key/post.js b/packages/server/services/users/routes/users/self/public-key/post.js index 542a58fc..851c1c25 100644 --- a/packages/server/services/users/routes/users/self/public-key/post.js +++ b/packages/server/services/users/routes/users/self/public-key/post.js @@ -1,7 +1,7 @@ import { UserPublicKey } from "@db_models" export default { - middlewares: ["withAuthentication"], + useMiddlewares: ["withAuthentication"], fn: async (req) => { const userId = req.auth.session.user_id const { public_key } = req.body diff --git a/packages/server/services/users/routes/users/self/roles/get.js b/packages/server/services/users/routes/users/self/roles/get.js index 68dab220..2e129ee0 100644 --- a/packages/server/services/users/routes/users/self/roles/get.js +++ b/packages/server/services/users/routes/users/self/roles/get.js @@ -1,16 +1,16 @@ import Users from "@classes/users" export default { - middlewares: ["withAuthentication"], - fn: async (req) => { - const data = await Users.data({ - user_id: user_id, - }) + useMiddlewares: ["withAuthentication"], + fn: async (req) => { + const data = await Users.data({ + user_id: user_id, + }) - if (!data) { - throw new OperationError(404, "User not found") - } + if (!data) { + throw new OperationError(404, "User not found") + } - return data.roles - } -} \ No newline at end of file + return data.roles + }, +} diff --git a/packages/server/services/users/routes/users/self/update/post.js b/packages/server/services/users/routes/users/self/update/post.js index 4188455a..a58f0d65 100644 --- a/packages/server/services/users/routes/users/self/update/post.js +++ b/packages/server/services/users/routes/users/self/update/post.js @@ -2,56 +2,62 @@ import UserClass from "@classes/users" import { User } from "@db_models" const AllowedPublicUpdateFields = [ - "public_name", - "avatar", - "email", - "cover", - "description", - "location", - "links", - "birthday", + "public_name", + "avatar", + "email", + "cover", + "description", + "location", + "links", + "birthday", ] const MaxStringsLengths = { - public_name: 120, - email: 320, - description: 320, + public_name: 120, + email: 320, + description: 320, } export default { - middlewares: ["withAuthentication"], - fn: async (req) => { - const update = {} + useMiddlewares: ["withAuthentication"], + fn: async (req) => { + const update = {} - // sanitize update - AllowedPublicUpdateFields.forEach((key) => { - if (typeof req.body[key] !== "undefined") { - // check maximung strings length - if (typeof req.body[key] === "string" && MaxStringsLengths[key]) { - if (req.body[key].length > MaxStringsLengths[key]) { - // create a substring - update[key] = req.body[key].substring(0, MaxStringsLengths[key]) - } else { - update[key] = req.body[key] - } - } else { - update[key] = req.body[key] - } - } - }) + // sanitize update + AllowedPublicUpdateFields.forEach((key) => { + if (typeof req.body[key] !== "undefined") { + // check maximung strings length + if ( + typeof req.body[key] === "string" && + MaxStringsLengths[key] + ) { + if (req.body[key].length > MaxStringsLengths[key]) { + // create a substring + update[key] = req.body[key].substring( + 0, + MaxStringsLengths[key], + ) + } else { + update[key] = req.body[key] + } + } else { + update[key] = req.body[key] + } + } + }) - if (typeof update.email !== "undefined") { - const user = await User.findOne({ - email: update.email, - }).catch((err) => { - return false - }) + if (typeof update.email !== "undefined") { + const user = await User.findOne({ + email: update.email, + }).catch((err) => { + return false + }) - if (user) { - throw new OperationError(400, "Email is already in use") - } - } + if (user) { + throw new OperationError(400, "Email is already in use") + } + } - return await UserClass.update(req.auth.session.user_id, update) - } -} \ No newline at end of file + return await UserClass.update(req.auth.session.user_id, update) + }, +} diff --git a/packages/server/services/users/users.service.js b/packages/server/services/users/users.service.js index 3c58eee8..92471766 100644 --- a/packages/server/services/users/users.service.js +++ b/packages/server/services/users/users.service.js @@ -4,12 +4,19 @@ import DbManager from "@shared-classes/DbManager" import RedisClient from "@shared-classes/RedisClient" import SharedMiddlewares from "@shared-middlewares" +import InjectedAuth from "@shared-lib/injectedAuth" export default class API extends Server { static refName = "users" - static useEngine = "hyper-express" static routesPath = `${__dirname}/routes` - static listen_port = process.env.HTTP_LISTEN_PORT ?? 3008 + static listenPort = process.env.HTTP_LISTEN_PORT ?? 3008 + + static useMiddlewares = ["logs"] + + static websockets = { + enabled: true, + path: "/users", + } middlewares = { ...SharedMiddlewares, @@ -20,6 +27,24 @@ export default class API extends Server { redis: RedisClient(), } + handleWsUpgrade = async (context, token, res) => { + if (!token) { + return res.upgrade(context) + } + + context = await InjectedAuth(context, token, res).catch(() => { + res.close(401, "Failed to verify auth token") + return false + }) + + if (!context || !context.user) { + res.close(401, "Unauthorized or missing auth token") + return false + } + + return res.upgrade(context) + } + async onInitialize() { await this.contexts.db.initialize() await this.contexts.redis.initialize()