From d5a803c71868fefd9487bfcfc988dfd49289b940 Mon Sep 17 00:00:00 2001 From: Bartholome Gili Date: Thu, 22 Dec 2022 23:22:19 +0000 Subject: [PATCH] fix(#100): mikro-orm global identity issue --- src/api/server.ts | 19 +++++-- src/client.ts | 3 +- src/config/database.ts | 1 - src/events/ready.ts | 10 ++-- src/guards/index.ts | 3 +- src/guards/requestContextIsolator.ts | 13 +++++ src/main.ts | 83 +++++++++++++++------------- src/services/Database.ts | 1 + src/utils/decorators/Schedule.ts | 1 + 9 files changed, 82 insertions(+), 52 deletions(-) create mode 100644 src/guards/requestContextIsolator.ts diff --git a/src/api/server.ts b/src/api/server.ts index 7fa99eb7..25b4f678 100644 --- a/src/api/server.ts +++ b/src/api/server.ts @@ -5,16 +5,22 @@ import { singleton } from "tsyringe" import * as controllers from "@api/controllers" import { Log } from "@api/middlewares" -import { PluginsManager } from "@services" +import { MikroORM, UseRequestContext } from "@mikro-orm/core" +import { Database, PluginsManager } from "@services" @singleton() export class Server { @Inject() app: PlatformApplication + + orm: MikroORM constructor( - private readonly pluginsManager: PluginsManager - ) {} + private readonly pluginsManager: PluginsManager, + db: Database + ) { + this.orm = db.orm + } $beforeRoutesInit() { this.app @@ -24,7 +30,9 @@ export class Server { return null } - async start() { + @UseRequestContext() + async start(): Promise { + const platform = await PlatformExpress.bootstrap(Server, { rootDir: __dirname, httpPort: parseInt(process.env['API_PORT']) || 4000, @@ -46,6 +54,7 @@ export class Server { } }) - await platform.listen() + platform.listen() + } } \ No newline at end of file diff --git a/src/client.ts b/src/client.ts index 31d9bb8b..416c805a 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,7 +1,7 @@ import { GatewayIntentBits, Partials } from "discord.js" import { generalConfig, logsConfig } from "@config" -import { ExtractLocale, Maintenance, NotBot } from "@guards" +import { ExtractLocale, Maintenance, NotBot, RequestContextIsolator } from "@guards" export const clientConfig = { @@ -28,6 +28,7 @@ export const clientConfig = { silent: !logsConfig.debug, guards: [ + RequestContextIsolator, NotBot, Maintenance, ExtractLocale diff --git a/src/config/database.ts b/src/config/database.ts index 0eba1d34..dad207e1 100644 --- a/src/config/database.ts +++ b/src/config/database.ts @@ -61,7 +61,6 @@ const envMikroORMConfig: { production: Options, development?: Options } = { // password: process.env['DATABASE_PASSWORD'], highlighter: new SqlHighlighter(), - allowGlobalContext: true, debug: false, migrations: { diff --git a/src/events/ready.ts b/src/events/ready.ts index 527d09d0..63b28a14 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -62,10 +62,12 @@ export default class ReadyEvent { // synchronize guilds between discord and the database await syncAllGuilds(client) + } @Schedule('*/15 * * * * *') // each 15 seconds async changeActivity() { + const ActivityTypeEnumString = ["PLAYING", "STREAMING", "LISTENING", "WATCHING", "CUSTOM", "COMPETING"] // DO NOT CHANGE THE ORDER const client = await resolveDependency(Client) @@ -73,16 +75,15 @@ export default class ReadyEvent { activity.text = eval(`new String(\`${activity.text}\`).toString()`) - if (activity.type === 'STREAMING') { - //streaming activity + if (activity.type === 'STREAMING') { //streaming activity client.user?.setStatus('online') client.user?.setActivity(activity.text, { 'url': 'https://www.twitch.tv/discord', 'type': ActivityType.Streaming }) - } else { - //other activities + + } else { //other activities client.user?.setActivity(activity.text, { type: ActivityTypeEnumString.indexOf(activity.type) @@ -91,6 +92,5 @@ export default class ReadyEvent { this.activityIndex++ if (this.activityIndex === generalConfig.activities.length) this.activityIndex = 0 - } } \ No newline at end of file diff --git a/src/guards/index.ts b/src/guards/index.ts index fa742d87..7ed9c49e 100644 --- a/src/guards/index.ts +++ b/src/guards/index.ts @@ -7,4 +7,5 @@ export * from './maintenance' export * from './notBot' export * from './nsfw' export * from './match' -export * from './extractLocale' \ No newline at end of file +export * from './extractLocale' +export * from './requestContextIsolator' \ No newline at end of file diff --git a/src/guards/requestContextIsolator.ts b/src/guards/requestContextIsolator.ts new file mode 100644 index 00000000..684dea37 --- /dev/null +++ b/src/guards/requestContextIsolator.ts @@ -0,0 +1,13 @@ +import { RequestContext } from '@mikro-orm/core' +import { Database } from '@services' +import { resolveDependency } from '@utils/functions' +import type { ArgsOf, GuardFunction } from 'discordx' + +/** + * Isolate all the handling pipeline to prevent any MikrORM global identity map issues + */ +export const RequestContextIsolator: GuardFunction = async (_, client, next) => { + + const db = await resolveDependency(Database) + RequestContext.create(db.orm.em, next) +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 6567f817..9c1a7072 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,6 +12,7 @@ import { NoBotTokenError } from "@errors" import { Database, ErrorHandler, ImagesUpload, Logger, PluginsManager, WebSocket } from "@services" import { initDataTable, resolveDependency } from "@utils/functions" import { clientConfig } from "./client" +import { RequestContext } from '@mikro-orm/core' async function run() { @@ -35,56 +36,60 @@ async function run() { // init the sqlite database const db = await resolveDependency(Database) await db.initialize() - + // init the client DIService.engine = tsyringeDependencyRegistryEngine.setInjector(container) const client = new Client(clientConfig) - + // Load all new events discordLogs(client, { debug: false }) container.registerInstance(Client, client) - + // import all the commands and events await importx(__dirname + "/{events,commands}/**/*.{ts,js}") await pluginManager.importCommands() await pluginManager.importEvents() - - // init the data table if it doesn't exist - await initDataTable() - - // init plugins services - await pluginManager.initServices() - - // init the plugin main file - await pluginManager.execMains() - - // log in with the bot token - if (!process.env.BOT_TOKEN) throw new NoBotTokenError() - client.login(process.env.BOT_TOKEN) - .then(async () => { - - // start the api server - if (apiConfig.enabled) { - const server = await resolveDependency(Server) - await server.start() - } - - // connect to the dashboard websocket - if (websocketConfig.enabled) { - const webSocket = await resolveDependency(WebSocket) - await webSocket.init(client.user?.id || null) - } - - // upload images to imgur if configured - if (process.env.IMGUR_CLIENT_ID && generalConfig.automaticUploadImagesToImgur) { - const imagesUpload = await resolveDependency(ImagesUpload) - await imagesUpload.syncWithDatabase() - } - }) - .catch((err) => { - console.error(err) - process.exit(1) + + RequestContext.create(db.orm.em, async () => { + + // init the data table if it doesn't exist + await initDataTable() + + // init plugins services + await pluginManager.initServices() + + // init the plugin main file + await pluginManager.execMains() + + // log in with the bot token + if (!process.env.BOT_TOKEN) throw new NoBotTokenError() + client.login(process.env.BOT_TOKEN) + .then(async () => { + + // start the api server + if (apiConfig.enabled) { + const server = await resolveDependency(Server) + await server.start() + } + + // connect to the dashboard websocket + if (websocketConfig.enabled) { + const webSocket = await resolveDependency(WebSocket) + await webSocket.init(client.user?.id || null) + } + + // upload images to imgur if configured + if (process.env.IMGUR_CLIENT_ID && generalConfig.automaticUploadImagesToImgur) { + const imagesUpload = await resolveDependency(ImagesUpload) + await imagesUpload.syncWithDatabase() + } + }) + .catch((err) => { + console.error(err) + process.exit(1) + }) }) + } run() \ No newline at end of file diff --git a/src/services/Database.ts b/src/services/Database.ts index 22b50cac..6427e291 100644 --- a/src/services/Database.ts +++ b/src/services/Database.ts @@ -20,6 +20,7 @@ export class Database { ) { } async initialize() { + const pluginsManager = await resolveDependency(PluginsManager) // get config diff --git a/src/utils/decorators/Schedule.ts b/src/utils/decorators/Schedule.ts index 8c824b92..60ae7771 100644 --- a/src/utils/decorators/Schedule.ts +++ b/src/utils/decorators/Schedule.ts @@ -19,6 +19,7 @@ export const Schedule = (cronExpression: string, jobName?: string) => { propertyKey: string, descriptor: PropertyDescriptor ) => { + // associate the context to the function, with the injected dependencies defined const oldDescriptor = descriptor.value descriptor.value = function(...args: any[]) {