merge from local

This commit is contained in:
SrGooglo 2024-03-09 23:43:54 +00:00
parent 6aba03e310
commit 24d6203d74
47 changed files with 414 additions and 544 deletions

View File

@ -8,7 +8,7 @@
3005 -> marketplace
3006 -> sync
3007 -> ems (External Messaging Service)
3008 -> unallocated
3008 -> users
3009 -> unallocated
3010 -> unallocated
3011 -> unallocated

View File

@ -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",
}
}

View File

@ -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: [] },

View File

@ -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 },
}
}

View File

@ -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)
}
}

View File

@ -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,
})

View File

@ -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) {

View File

@ -7,12 +7,15 @@ export default async (req) => {
throw new OperationError(400, "Missing username or email")
}
const user = await User.findOne({
const user = await User
.findOne({
$or: [
{ username: username },
{ email: email },
]
}).catch((error) => {
})
.select("+email")
.catch((error) => {
return false
})

View File

@ -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")

View File

@ -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)
}
},
}
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"

View File

@ -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")

View File

@ -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"

View File

@ -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()
}

View File

@ -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()
}

View File

@ -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." })
}
}

View File

@ -1,9 +0,0 @@
import withAuthentication from "../withAuthentication"
export default (req, res, next) => {
if (req.headers?.authorization) {
withAuthentication(req, res, next)
} else {
next()
}
}

View File

@ -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
}

View File

@ -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: [],
})

View File

@ -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]
}

View 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
}

View File

@ -0,0 +1,3 @@
export default async () => {
throw new OperationError(501, "Not implemented")
}

View File

@ -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
}
}
})
}

View File

@ -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)

View File

@ -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
}
}
})
}

View File

@ -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
}
}
})
}

View File

@ -0,0 +1,3 @@
export default async () => {
throw new OperationError(501, "Not implemented")
}

View File

@ -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
}
}

View File

@ -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,
}
}

View 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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View 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
})
}
}

View File

@ -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
}
}

View 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
})
}
}

View File

@ -1,10 +0,0 @@
export default async (req, res) => {
await new Promise((r) => {
setTimeout(r, 1000)
})
return {
code: 0,
message: "success",
}
}

View File

@ -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,
})
}
}

View File

@ -0,0 +1,3 @@
export default class Users {
static data = require("./method/data").default
}

View 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
}

View File

@ -1,5 +1,5 @@
{
"name": "feed",
"name": "users",
"version": "1.0.0",
"main": "index.js",
"license": "MIT"

View File

@ -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,
})
}
}

View File

@ -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()
}
}