mirror of
https://github.com/ragestudio/comty.git
synced 2025-07-08 16:54:15 +00:00
Refactor users service routes and add websocket support
- Standardize middleware naming to useMiddlewares - Add websocket support and handleWsUpgrade to users service - Update getFollowers to support pagination and return metadata - Emit user update events via websockets - Add linebridge^1.0.0 dependency - Minor code style and consistency improvements across routes
This commit is contained in:
parent
35df3a421f
commit
33fe2044c4
@ -1,35 +1,44 @@
|
|||||||
import { User, UserFollow } from "@db_models"
|
import { User, UserFollow } from "@db_models"
|
||||||
|
|
||||||
export default async (payload = {}) => {
|
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) {
|
if (!user_id) {
|
||||||
throw new OperationError(400, "Missing 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({
|
let followers = await UserFollow.find({
|
||||||
to: user_id,
|
to: user_id,
|
||||||
})
|
})
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
.skip(offset)
|
.skip(limit * page)
|
||||||
|
.lean()
|
||||||
|
|
||||||
|
followers = followers.map((follow) => {
|
||||||
|
return follow.user_id
|
||||||
|
})
|
||||||
|
|
||||||
const followersData = await User.find({
|
const followersData = await User.find({
|
||||||
_id: {
|
_id: {
|
||||||
$in: followers.map((follow) => {
|
$in: followers,
|
||||||
return follow.user_id
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return followersData
|
const nextPage = page + 1
|
||||||
} else {
|
|
||||||
const count = await UserFollow.countDocuments({
|
|
||||||
to: user_id,
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
count,
|
items: followersData,
|
||||||
|
total_items: total_followers,
|
||||||
|
has_more: total_followers > limit * nextPage,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
count: total_followers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,5 +25,13 @@ export default async (user_id, update) => {
|
|||||||
|
|
||||||
user = user.toObject()
|
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
|
return user
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "users"
|
"name": "users",
|
||||||
|
"dependencies": {
|
||||||
|
"linebridge": "^1.0.0-alpha.4"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
import { User, Badge } from "@db_models"
|
import { User, Badge } from "@db_models"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const { user_id } = req.params
|
const { user_id } = req.params
|
||||||
|
|
||||||
const user = await User.findOne({
|
const user = await User.findOne({
|
||||||
_id: user_id
|
_id: user_id,
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
const badges = await Badge.find({
|
const badges = await Badge.find({
|
||||||
name: {
|
name: {
|
||||||
$in: user.badges
|
$in: user.badges,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
return badges
|
return badges
|
||||||
}
|
},
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import Users from "@classes/users"
|
import Users from "@classes/users"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withOptionalAuthentication"],
|
useMiddlewares: ["withOptionalAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const { user_id } = req.params
|
const { user_id } = req.params
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ export default {
|
|||||||
return await Users.data({
|
return await Users.data({
|
||||||
user_id: ids.length > 1 ? ids : user_id,
|
user_id: ids.length > 1 ? ids : user_id,
|
||||||
from_user_id: req.auth?.session.user_id,
|
from_user_id: req.auth?.session.user_id,
|
||||||
basic: req.query?.basic,
|
basic: ToBoolean(req.query?.basic),
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import User from "@classes/users"
|
import User from "@classes/users"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
return await User.toggleFollow({
|
return await User.toggleFollow({
|
||||||
user_id: req.params.user_id,
|
user_id: req.params.user_id,
|
||||||
from_user_id: req.auth.session.user_id,
|
from_user_id: req.auth.session.user_id,
|
||||||
to: req.body?.to,
|
to: req.body?.to,
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
}
|
}
|
@ -1,12 +1,12 @@
|
|||||||
import User from "@classes/users"
|
import User from "@classes/users"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
return await User.getFollowers({
|
return await User.getFollowers({
|
||||||
user_id: req.params.user_id,
|
user_id: req.params.user_id,
|
||||||
data: ToBoolean(req.query.fetchData),
|
data: ToBoolean(req.query.fetchData),
|
||||||
limit: req.query.limit,
|
limit: parseInt(req.query.limit),
|
||||||
offset: req.query.offset,
|
page: parseInt(req.query.page),
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { UserPublicKey } from "@db_models"
|
import { UserPublicKey } from "@db_models"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const targetUserId = req.params.user_id
|
const targetUserId = req.params.user_id
|
||||||
|
|
||||||
|
@ -2,9 +2,9 @@ import Users from "@classes/users"
|
|||||||
|
|
||||||
// resolve user id from a username (passed from params)
|
// resolve user id from a username (passed from params)
|
||||||
export default {
|
export default {
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
return await Users.resolveUserId({
|
return await Users.resolveUserId({
|
||||||
username: req.params.user_id,
|
username: req.params.user_id,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}
|
}
|
@ -1,12 +1,12 @@
|
|||||||
import Users from "@classes/users"
|
import Users from "@classes/users"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withOptionalAuthentication"],
|
useMiddlewares: ["withOptionalAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const data = await Users.data({
|
const data = await Users.data({
|
||||||
user_id: req.auth.session.user_id,
|
user_id: req.auth.session.user_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
return data.roles
|
return data.roles
|
||||||
}
|
},
|
||||||
}
|
}
|
@ -1,25 +1,25 @@
|
|||||||
import { UserConfig } from "@db_models"
|
import { UserConfig } from "@db_models"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const key = req.query.key
|
const key = req.query.key
|
||||||
|
|
||||||
let config = await UserConfig.findOne({
|
let config = await UserConfig.findOne({
|
||||||
user_id: req.auth.session.user_id
|
user_id: req.auth.session.user_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
config = await UserConfig.create({
|
config = await UserConfig.create({
|
||||||
user_id: req.auth.session.user_id,
|
user_id: req.auth.session.user_id,
|
||||||
values: {}
|
values: {},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key) {
|
if (key) {
|
||||||
return config.values?.[key]
|
return config.values?.[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
return config.values
|
return config.values
|
||||||
}
|
},
|
||||||
}
|
}
|
@ -2,60 +2,65 @@ import { UserConfig } from "@db_models"
|
|||||||
import lodash from "lodash"
|
import lodash from "lodash"
|
||||||
|
|
||||||
const baseConfig = [
|
const baseConfig = [
|
||||||
{
|
{
|
||||||
key: "app:language",
|
key: "app:language",
|
||||||
type: "string",
|
type: "string",
|
||||||
value: "en-us"
|
value: "en-us",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "auth:mfa",
|
key: "auth:mfa",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
value: false
|
value: false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
let config = await UserConfig.findOne({
|
let config = await UserConfig.findOne({
|
||||||
user_id: req.auth.session.user_id
|
user_id: req.auth.session.user_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
const values = {}
|
const values = {}
|
||||||
|
|
||||||
baseConfig.forEach((config) => {
|
baseConfig.forEach((config) => {
|
||||||
const fromBody = req.body[config.key]
|
const fromBody = req.body[config.key]
|
||||||
if (typeof fromBody !== "undefined") {
|
if (typeof fromBody !== "undefined") {
|
||||||
if (typeof fromBody === config.type) {
|
if (typeof fromBody === config.type) {
|
||||||
values[config.key] = req.body[config.key]
|
values[config.key] = req.body[config.key]
|
||||||
} else {
|
} else {
|
||||||
throw new OperationError(400, `Invalid type for ${config.key}`)
|
throw new OperationError(
|
||||||
}
|
400,
|
||||||
} else {
|
`Invalid type for ${config.key}`,
|
||||||
values[config.key] = config.value
|
)
|
||||||
}
|
}
|
||||||
})
|
} 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.updateOne(
|
||||||
config = await UserConfig.create({
|
{
|
||||||
user_id: req.auth.session.user_id,
|
user_id: req.auth.session.user_id,
|
||||||
values
|
},
|
||||||
})
|
{
|
||||||
} else {
|
values: newValues,
|
||||||
const newValues = lodash.merge(config.values, values)
|
},
|
||||||
|
)
|
||||||
|
|
||||||
config = await UserConfig.updateOne({
|
config = await UserConfig.findOne({
|
||||||
user_id: req.auth.session.user_id
|
user_id: req.auth.session.user_id,
|
||||||
}, {
|
})
|
||||||
values: newValues
|
}
|
||||||
})
|
|
||||||
|
|
||||||
config = await UserConfig.findOne({
|
return config.values
|
||||||
user_id: req.auth.session.user_id
|
},
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return config.values
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import Users from "@classes/users"
|
import Users from "@classes/users"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
return await Users.data({
|
return await Users.data({
|
||||||
user_id: req.auth.session.user_id,
|
user_id: req.auth.session.user_id,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { UserDHKeyPair } from "@db_models"
|
import { UserDHKeyPair } from "@db_models"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const userId = req.auth.session.user_id
|
const userId = req.auth.session.user_id
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { UserDHKeyPair } from "@db_models"
|
import { UserDHKeyPair } from "@db_models"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const userId = req.auth.session.user_id
|
const userId = req.auth.session.user_id
|
||||||
const { str } = req.body
|
const { str } = req.body
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { UserPublicKey } from "@db_models"
|
import { UserPublicKey } from "@db_models"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const userId = req.auth.session.user_id
|
const userId = req.auth.session.user_id
|
||||||
const { public_key } = req.body
|
const { public_key } = req.body
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import Users from "@classes/users"
|
import Users from "@classes/users"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const data = await Users.data({
|
const data = await Users.data({
|
||||||
user_id: user_id,
|
user_id: user_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
throw new OperationError(404, "User not found")
|
throw new OperationError(404, "User not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.roles
|
return data.roles
|
||||||
}
|
},
|
||||||
}
|
}
|
@ -2,56 +2,62 @@ import UserClass from "@classes/users"
|
|||||||
import { User } from "@db_models"
|
import { User } from "@db_models"
|
||||||
|
|
||||||
const AllowedPublicUpdateFields = [
|
const AllowedPublicUpdateFields = [
|
||||||
"public_name",
|
"public_name",
|
||||||
"avatar",
|
"avatar",
|
||||||
"email",
|
"email",
|
||||||
"cover",
|
"cover",
|
||||||
"description",
|
"description",
|
||||||
"location",
|
"location",
|
||||||
"links",
|
"links",
|
||||||
"birthday",
|
"birthday",
|
||||||
]
|
]
|
||||||
|
|
||||||
const MaxStringsLengths = {
|
const MaxStringsLengths = {
|
||||||
public_name: 120,
|
public_name: 120,
|
||||||
email: 320,
|
email: 320,
|
||||||
description: 320,
|
description: 320,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
middlewares: ["withAuthentication"],
|
useMiddlewares: ["withAuthentication"],
|
||||||
fn: async (req) => {
|
fn: async (req) => {
|
||||||
const update = {}
|
const update = {}
|
||||||
|
|
||||||
// sanitize update
|
// sanitize update
|
||||||
AllowedPublicUpdateFields.forEach((key) => {
|
AllowedPublicUpdateFields.forEach((key) => {
|
||||||
if (typeof req.body[key] !== "undefined") {
|
if (typeof req.body[key] !== "undefined") {
|
||||||
// check maximung strings length
|
// check maximung strings length
|
||||||
if (typeof req.body[key] === "string" && MaxStringsLengths[key]) {
|
if (
|
||||||
if (req.body[key].length > MaxStringsLengths[key]) {
|
typeof req.body[key] === "string" &&
|
||||||
// create a substring
|
MaxStringsLengths[key]
|
||||||
update[key] = req.body[key].substring(0, MaxStringsLengths[key])
|
) {
|
||||||
} else {
|
if (req.body[key].length > MaxStringsLengths[key]) {
|
||||||
update[key] = req.body[key]
|
// create a substring
|
||||||
}
|
update[key] = req.body[key].substring(
|
||||||
} else {
|
0,
|
||||||
update[key] = req.body[key]
|
MaxStringsLengths[key],
|
||||||
}
|
)
|
||||||
}
|
} else {
|
||||||
})
|
update[key] = req.body[key]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
update[key] = req.body[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if (typeof update.email !== "undefined") {
|
if (typeof update.email !== "undefined") {
|
||||||
const user = await User.findOne({
|
const user = await User.findOne({
|
||||||
email: update.email,
|
email: update.email,
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
throw new OperationError(400, "Email is already in use")
|
throw new OperationError(400, "Email is already in use")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await UserClass.update(req.auth.session.user_id, update)
|
return await UserClass.update(req.auth.session.user_id, update)
|
||||||
}
|
},
|
||||||
}
|
}
|
@ -4,12 +4,19 @@ import DbManager from "@shared-classes/DbManager"
|
|||||||
import RedisClient from "@shared-classes/RedisClient"
|
import RedisClient from "@shared-classes/RedisClient"
|
||||||
|
|
||||||
import SharedMiddlewares from "@shared-middlewares"
|
import SharedMiddlewares from "@shared-middlewares"
|
||||||
|
import InjectedAuth from "@shared-lib/injectedAuth"
|
||||||
|
|
||||||
export default class API extends Server {
|
export default class API extends Server {
|
||||||
static refName = "users"
|
static refName = "users"
|
||||||
static useEngine = "hyper-express"
|
|
||||||
static routesPath = `${__dirname}/routes`
|
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 = {
|
middlewares = {
|
||||||
...SharedMiddlewares,
|
...SharedMiddlewares,
|
||||||
@ -20,6 +27,24 @@ export default class API extends Server {
|
|||||||
redis: RedisClient(),
|
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() {
|
async onInitialize() {
|
||||||
await this.contexts.db.initialize()
|
await this.contexts.db.initialize()
|
||||||
await this.contexts.redis.initialize()
|
await this.contexts.redis.initialize()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user