diff --git a/.gitignore b/.gitignore index f2818846..2f22ed6f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ src/config/*.env # compiled output /dist +/lib /node_modules #migration diff --git a/src/database/connection.ts b/src/database/connection.ts index 575da232..a112bf63 100644 --- a/src/database/connection.ts +++ b/src/database/connection.ts @@ -1,19 +1,46 @@ -import { Connection } from 'mongoose'; -import parentLogger from '../config/logger'; +import mongoose, { Connection } from 'mongoose'; +import { + heatMapSchema, + rawInfoSchema, + MemberActivitySchema, + guildMemberSchema, + channelSchema, + roleSchema, + type IHeatMap, + type IRawInfo, + type IMemberActivity, + type IGuildMember, + type IChannel, + type IRole, +} from '@togethercrew.dev/db'; +import { type Snowflake } from 'discord.js'; -const logger = parentLogger.child({ module: 'Connection' }); +export default class DatabaseManager { + private static instance: DatabaseManager; + private modelCache: Record = {}; + public static getInstance(): DatabaseManager { + if (typeof DatabaseManager.instance === 'undefined') { + DatabaseManager.instance = new DatabaseManager(); + } + return DatabaseManager.instance; + } + + public getTenantDb(tenantId: Snowflake): Connection { + const dbName = tenantId; + const db = mongoose.connection.useDb(dbName, { useCache: true }); + this.setupModels(db); + return db; + } -/** - * Closes a given Mongoose connection. - * @param {Connection} connection - The Mongoose connection object to be closed. - * @returns {Promise} - A promise that resolves when the connection has been successfully closed. - * @throws {MongooseError} - If there is an error closing the connection, it is logged to the console and the error is thrown. - */ -export async function closeConnection(connection: Connection) { - try { - await connection.close(); - logger.info({ database: connection.name }, 'The connection to database has been successfully closed'); - } catch (error) { - logger.fatal({ database: connection.name, error }, 'Failed to close the connection to the database'); + private setupModels(db: Connection): void { + if (!this.modelCache[db.name]) { + db.model('HeatMap', heatMapSchema); + db.model('RawInfo', rawInfoSchema); + db.model('MemberActivity', MemberActivitySchema); + db.model('GuildMember', guildMemberSchema); + db.model('Channel', channelSchema); + db.model('Role', roleSchema); + this.modelCache[db.name] = true; + } } } diff --git a/src/events/channel/channelCreate.ts b/src/events/channel/channelCreate.ts index 9d2db07f..e86dcf2d 100644 --- a/src/events/channel/channelCreate.ts +++ b/src/events/channel/channelCreate.ts @@ -1,8 +1,6 @@ import { Events, Channel, TextChannel, VoiceChannel, CategoryChannel } from 'discord.js'; import { channelService } from '../../database/services'; -import { databaseService } from '@togethercrew.dev/db'; -import config from '../../config'; -import { closeConnection } from '../../database/connection'; +import DatabaseManager from '../../database/connection'; import parentLogger from '../../config/logger'; const logger = parentLogger.child({ event: 'ChannelCreate' }); @@ -14,14 +12,12 @@ export default { if (channel instanceof TextChannel || channel instanceof VoiceChannel || channel instanceof CategoryChannel) { const logFields = { guild_id: channel.guild.id, channel_id: channel.id }; logger.info(logFields, 'event is running'); - const connection = databaseService.connectionFactory(channel.guild.id, config.mongoose.dbURL); + const connection = DatabaseManager.getInstance().getTenantDb(channel.guild.id); try { await channelService.handelChannelChanges(connection, channel); + logger.info(logFields, 'event is done'); } catch (err) { logger.error({ ...logFields, err }, 'Failed to handle channel changes'); - } finally { - await closeConnection(connection); - logger.info(logFields, 'event is done'); } } }, diff --git a/src/events/channel/channelDelete.ts b/src/events/channel/channelDelete.ts index e6494d81..ee0f99c9 100644 --- a/src/events/channel/channelDelete.ts +++ b/src/events/channel/channelDelete.ts @@ -1,8 +1,6 @@ import { Events, Channel, TextChannel, VoiceChannel, CategoryChannel } from 'discord.js'; import { channelService, guildService } from '../../database/services'; -import { databaseService } from '@togethercrew.dev/db'; -import config from '../../config'; -import { closeConnection } from '../../database/connection'; +import DatabaseManager from '../../database/connection'; import parentLogger from '../../config/logger'; const logger = parentLogger.child({ event: 'ChannelDelete' }); @@ -14,7 +12,7 @@ export default { if (channel instanceof TextChannel || channel instanceof VoiceChannel || channel instanceof CategoryChannel) { const logFields = { guild_id: channel.guild.id, channel_id: channel.id }; logger.info(logFields, 'event is running'); - const connection = databaseService.connectionFactory(channel.guild.id, config.mongoose.dbURL); + const connection = DatabaseManager.getInstance().getTenantDb(channel.guild.id); try { const channelDoc = await channelService.getChannel(connection, { channelId: channel.id }); await channelDoc?.softDelete(); @@ -23,11 +21,10 @@ export default { selectedChannel => selectedChannel.channelId !== channel.id ); await guildService.updateGuild({ guildId: channel.guild.id }, { selectedChannels: updatedSelecetdChannels }); + logger.info(logFields, 'event is done'); + } catch (err) { logger.error({ ...logFields, err }, 'Failed to soft delete the channel'); - } finally { - await closeConnection(connection); - logger.info(logFields, 'event is done'); } } }, diff --git a/src/events/channel/channelUpdate.ts b/src/events/channel/channelUpdate.ts index 6c9332b5..43a94d36 100644 --- a/src/events/channel/channelUpdate.ts +++ b/src/events/channel/channelUpdate.ts @@ -1,8 +1,6 @@ import { Events, Channel, TextChannel, VoiceChannel, CategoryChannel } from 'discord.js'; import { channelService } from '../../database/services'; -import { databaseService } from '@togethercrew.dev/db'; -import config from '../../config'; -import { closeConnection } from '../../database/connection'; +import DatabaseManager from '../../database/connection'; import parentLogger from '../../config/logger'; const logger = parentLogger.child({ event: 'ChannelUpdate' }); @@ -18,14 +16,13 @@ export default { ) { const logFields = { guild_id: newChannel.guild.id, channel_id: newChannel.id }; logger.info(logFields, 'event is running'); - const connection = databaseService.connectionFactory(newChannel.guild.id, config.mongoose.dbURL); + const connection = DatabaseManager.getInstance().getTenantDb(newChannel.guild.id); try { await channelService.handelChannelChanges(connection, newChannel); + logger.info(logFields, 'event is done'); + } catch (err) { logger.error({ ...logFields, err }, 'Failed to handle channel changes'); - } finally { - await closeConnection(connection); - logger.info(logFields, 'event is done'); } } }, diff --git a/src/events/client/ready.ts b/src/events/client/ready.ts index e97b669f..b7cf0b6a 100644 --- a/src/events/client/ready.ts +++ b/src/events/client/ready.ts @@ -1,12 +1,10 @@ import { Events, Client } from 'discord.js'; -import { databaseService } from '@togethercrew.dev/db'; import { guildService } from '../../database/services'; import fetchMembers from '../../functions/fetchMembers'; import fetchChannels from '../../functions/fetchChannels'; import fetchRoles from '../../functions/fetchRoles'; -import { closeConnection } from '../../database/connection'; +import DatabaseManager from '../../database/connection'; import parentLogger from '../../config/logger'; -import config from '../../config'; const logger = parentLogger.child({ event: 'ClientReady' }); @@ -17,7 +15,7 @@ export default { logger.info('event is running'); const guilds = await guildService.getGuilds({ isDisconnected: false }); for (let i = 0; i < guilds.length; i++) { - const connection = databaseService.connectionFactory(guilds[i].guildId, config.mongoose.dbURL); + const connection = DatabaseManager.getInstance().getTenantDb(guilds[i].guildId); try { logger.info({ guild_id: guilds[i].guildId }, 'Fetching guild members, roles,and channels'); await fetchMembers(connection, client, guilds[i].guildId); @@ -26,8 +24,7 @@ export default { logger.info({ guild_id: guilds[i].guildId }, 'Fetching guild members, roles, channels is done'); } catch (err) { logger.error({ guild_id: guilds[i].guildId, err }, 'Fetching guild members, roles,and channels failed'); - } finally { - await closeConnection(connection); + logger.info('event is done'); } } logger.info('event is done'); diff --git a/src/events/member/guildMemberAdd.ts b/src/events/member/guildMemberAdd.ts index 6b9baee7..4004c55f 100644 --- a/src/events/member/guildMemberAdd.ts +++ b/src/events/member/guildMemberAdd.ts @@ -1,8 +1,6 @@ import { Events, GuildMember } from 'discord.js'; import { guildMemberService } from '../../database/services'; -import { databaseService } from '@togethercrew.dev/db'; -import config from '../../config'; -import { closeConnection } from '../../database/connection'; +import DatabaseManager from '../../database/connection'; import parentLogger from '../../config/logger'; const logger = parentLogger.child({ event: 'GuildMemberAdd' }); @@ -13,14 +11,12 @@ export default { async execute(member: GuildMember) { const logFields = { guild_id: member.guild.id, guild_member_id: member.user.id }; logger.info(logFields, 'event is running'); - const connection = databaseService.connectionFactory(member.guild.id, config.mongoose.dbURL); + const connection = DatabaseManager.getInstance().getTenantDb(member.guild.id); try { await guildMemberService.handelGuildMemberChanges(connection, member); + logger.info(logFields, 'event is done'); } catch (err) { logger.error({ ...logFields, err }, 'Failed to handle guild member changes'); - } finally { - await closeConnection(connection); - logger.info(logFields, 'event is done'); } }, }; diff --git a/src/events/member/guildMemberRemove.ts b/src/events/member/guildMemberRemove.ts index 68b829bd..a84f5cd1 100644 --- a/src/events/member/guildMemberRemove.ts +++ b/src/events/member/guildMemberRemove.ts @@ -1,8 +1,6 @@ import { Events, GuildMember } from 'discord.js'; import { guildMemberService } from '../../database/services'; -import { databaseService } from '@togethercrew.dev/db'; -import config from '../../config'; -import { closeConnection } from '../../database/connection'; +import DatabaseManager from '../../database/connection'; import parentLogger from '../../config/logger'; const logger = parentLogger.child({ event: 'GuildMemberRemove' }); @@ -13,15 +11,13 @@ export default { async execute(member: GuildMember) { const logFields = { guild_id: member.guild.id, guild_member_id: member.user.id }; logger.info(logFields, 'event is running'); - const connection = databaseService.connectionFactory(member.guild.id, config.mongoose.dbURL); + const connection = DatabaseManager.getInstance().getTenantDb(member.guild.id); try { const guildMemberDoc = await guildMemberService.getGuildMember(connection, { discordId: member.user.id }); await guildMemberDoc?.softDelete(); + logger.info(logFields, 'event is done'); } catch (err) { logger.error({ ...logFields, err }, 'Failed to soft delete the guild member'); - } finally { - await closeConnection(connection); - logger.info(logFields, 'event is done'); } }, }; diff --git a/src/events/member/guildMemberUpdate.ts b/src/events/member/guildMemberUpdate.ts index feea86ee..e90f7213 100644 --- a/src/events/member/guildMemberUpdate.ts +++ b/src/events/member/guildMemberUpdate.ts @@ -1,8 +1,7 @@ import { Events, GuildMember } from 'discord.js'; import { guildMemberService } from '../../database/services'; -import { databaseService } from '@togethercrew.dev/db'; -import config from '../../config'; -import { closeConnection } from '../../database/connection'; +import DatabaseManager from '../../database/connection'; + import parentLogger from '../../config/logger'; const logger = parentLogger.child({ event: 'GuildMemberUpdate' }); @@ -13,14 +12,12 @@ export default { async execute(oldMember: GuildMember, newMember: GuildMember) { const logFields = { guild_id: newMember.guild.id, guild_member_id: newMember.user.id }; logger.info(logFields, 'event is running'); - const connection = databaseService.connectionFactory(newMember.guild.id, config.mongoose.dbURL); + const connection = DatabaseManager.getInstance().getTenantDb(newMember.guild.id); try { await guildMemberService.handelGuildMemberChanges(connection, newMember); + logger.info(logFields, 'event is done'); } catch (err) { logger.error({ ...logFields, err }, 'Failed to handle guild member changes'); - } finally { - await closeConnection(connection); - logger.info(logFields, 'event is done'); } }, }; diff --git a/src/events/role/roleCreate.ts b/src/events/role/roleCreate.ts index 355fff43..5c2ffc94 100644 --- a/src/events/role/roleCreate.ts +++ b/src/events/role/roleCreate.ts @@ -1,8 +1,6 @@ import { Events, Role } from 'discord.js'; import { roleService } from '../../database/services'; -import { databaseService } from '@togethercrew.dev/db'; -import config from '../../config'; -import { closeConnection } from '../../database/connection'; +import DatabaseManager from '../../database/connection'; import parentLogger from '../../config/logger'; const logger = parentLogger.child({ event: 'GuildRoleCreate' }); @@ -13,14 +11,12 @@ export default { async execute(role: Role) { const logFields = { guild_id: role.guild.id, role_id: role.id }; logger.info(logFields, 'event is running'); - const connection = databaseService.connectionFactory(role.guild.id, config.mongoose.dbURL); + const connection = DatabaseManager.getInstance().getTenantDb(role.guild.id); try { await roleService.handelRoleChanges(connection, role); + logger.info(logFields, 'event is done'); } catch (err) { logger.error({ ...logFields, err }, 'Failed to handle role changes'); - } finally { - await closeConnection(connection); - logger.info(logFields, 'event is done'); } }, }; diff --git a/src/events/role/roleDelete.ts b/src/events/role/roleDelete.ts index 40ec8f61..386b3986 100644 --- a/src/events/role/roleDelete.ts +++ b/src/events/role/roleDelete.ts @@ -1,8 +1,7 @@ import { Events, Role } from 'discord.js'; import { roleService } from '../../database/services'; -import { databaseService } from '@togethercrew.dev/db'; -import config from '../../config'; -import { closeConnection } from '../../database/connection'; +import DatabaseManager from '../../database/connection'; + import parentLogger from '../../config/logger'; const logger = parentLogger.child({ event: 'GuildRoleDelete' }); @@ -13,15 +12,13 @@ export default { async execute(role: Role) { const logFields = { guild_id: role.guild.id, role_id: role.id }; logger.info(logFields, 'event is running'); - const connection = databaseService.connectionFactory(role.guild.id, config.mongoose.dbURL); + const connection = DatabaseManager.getInstance().getTenantDb(role.guild.id); try { const roleDoc = await roleService.getRole(connection, { roleId: role.id }); await roleDoc?.softDelete(); + logger.info(logFields, 'event is done'); } catch (err) { logger.error({ ...logFields, err }, 'Failed to soft delete the role'); - } finally { - await closeConnection(connection); - logger.info(logFields, 'event is done'); } }, }; diff --git a/src/events/role/roleUpdate.ts b/src/events/role/roleUpdate.ts index c62918d3..40012a8e 100644 --- a/src/events/role/roleUpdate.ts +++ b/src/events/role/roleUpdate.ts @@ -1,8 +1,6 @@ import { Events, Role } from 'discord.js'; import { roleService } from '../../database/services'; -import { databaseService } from '@togethercrew.dev/db'; -import config from '../../config'; -import { closeConnection } from '../../database/connection'; +import DatabaseManager from '../../database/connection'; import parentLogger from '../../config/logger'; const logger = parentLogger.child({ event: 'GuildRoleUpdate' }); @@ -13,14 +11,12 @@ export default { async execute(oldRole: Role, newRole: Role) { const logFields = { guild_id: newRole.guild.id, role_id: newRole.id }; logger.info(logFields, 'event is running'); - const connection = databaseService.connectionFactory(newRole.guild.id, config.mongoose.dbURL); + const connection = DatabaseManager.getInstance().getTenantDb(newRole.guild.id); try { await roleService.handelRoleChanges(connection, newRole); + logger.info(logFields, 'event is done'); } catch (err) { logger.error({ ...logFields, err }, 'Failed to handle role changes'); - } finally { - await closeConnection(connection); - logger.info(logFields, 'event is done'); } }, }; diff --git a/src/events/user/userUpdate.ts b/src/events/user/userUpdate.ts index aedb3c1e..b59fa7f0 100644 --- a/src/events/user/userUpdate.ts +++ b/src/events/user/userUpdate.ts @@ -1,8 +1,6 @@ import { Events, User } from 'discord.js'; import { guildMemberService, guildService } from '../../database/services'; -import { databaseService } from '@togethercrew.dev/db'; -import config from '../../config'; -import { closeConnection } from '../../database/connection'; +import DatabaseManager from '../../database/connection'; import parentLogger from '../../config/logger'; const logger = parentLogger.child({ event: 'UserUpdate' }); @@ -15,7 +13,7 @@ export default { try { const guilds = await guildService.getGuilds({}); for (let i = 0; i < guilds.length; i++) { - const connection = databaseService.connectionFactory(guilds[i].guildId, config.mongoose.dbURL); + const connection = DatabaseManager.getInstance().getTenantDb(guilds[i].guildId); await guildMemberService.updateGuildMember( connection, { discordId: newUser.id }, @@ -24,11 +22,10 @@ export default { globalName: newUser.globalName, } ); - await closeConnection(connection); + logger.info(logFields, 'event is done'); } } catch (err) { logger.error({ ...logFields, err }, 'Failed to handle user changes'); } - logger.info(logFields, 'event is done'); }, }; diff --git a/src/functions/cronJon.ts b/src/functions/cronJon.ts index 9aa350e5..3e21bfea 100644 --- a/src/functions/cronJon.ts +++ b/src/functions/cronJon.ts @@ -1,11 +1,9 @@ import { Client, Snowflake } from 'discord.js'; import { guildService } from '../database/services'; -import { databaseService } from '@togethercrew.dev/db'; import { ChoreographyDict, MBConnection, Status } from '@togethercrew.dev/tc-messagebroker'; -import config from '../config'; import guildExtraction from './guildExtraction'; -import { closeConnection } from '../database/connection'; import parentLogger from '../config/logger'; +import DatabaseManager from '../database/connection'; const logger = parentLogger.child({ event: 'CronJob' }); @@ -30,7 +28,7 @@ export default async function cronJob(client: Client) { logger.info('event is running'); const guilds = await guildService.getGuilds({ isDisconnected: false }); for (let i = 0; i < guilds.length; i++) { - const connection = databaseService.connectionFactory(guilds[i].guildId, config.mongoose.dbURL); + const connection = DatabaseManager.getInstance().getTenantDb(guilds[i].guildId); try { logger.info({ guild_id: guilds[i].guildId }, 'is running cronJob for guild'); await guildExtraction(connection, client, guilds[i].guildId); @@ -38,9 +36,8 @@ export default async function cronJob(client: Client) { logger.info({ guild_id: guilds[i].guildId }, 'cronJob is done for guild'); } catch (err) { logger.error({ guild_id: guilds[i].guildId, err }, 'CronJob faield for guild'); - } finally { - await closeConnection(connection); } + } logger.info('event is done'); } diff --git a/src/index.ts b/src/index.ts index 8b2e6c1b..a152c7b8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,8 +14,9 @@ import { createPrivateThreadAndSendMessage } from './functions/thread'; import fetchMembers from './functions/fetchMembers'; import fetchChannels from './functions/fetchChannels'; import fetchRoles from './functions/fetchRoles'; -import { closeConnection } from './database/connection'; import parentLogger from './config/logger'; +import DatabaseManager from './database/connection'; + const logger = parentLogger.child({ module: 'App' }); @@ -48,7 +49,7 @@ const fetchMethod = async (msg: any) => { logger.info({ saga: saga.data }, 'the saga info'); const guildId = saga.data['guildId']; const isGuildCreated = saga.data['created']; - const connection = await databaseService.connectionFactory(guildId, config.mongoose.dbURL); + const connection = DatabaseManager.getInstance().getTenantDb(guildId); if (isGuildCreated) { await fetchMembers(connection, client, guildId); await fetchRoles(connection, client, guildId); @@ -56,7 +57,6 @@ const fetchMethod = async (msg: any) => { } else { await guildExtraction(connection, client, guildId); } - await closeConnection(connection); logger.info({ msg }, 'fetchMethod is done'); }; @@ -91,11 +91,10 @@ const notifyUserAboutAnalysisFinish = async ( }; const fetchInitialData = async (guildId: Snowflake) => { - const connection = await databaseService.connectionFactory(guildId, config.mongoose.dbURL); + const connection = DatabaseManager.getInstance().getTenantDb(guildId); await fetchRoles(connection, client, guildId); await fetchChannels(connection, client, guildId); await fetchMembers(connection, client, guildId); - await closeConnection(connection); }; // APP diff --git a/src/migrations/db/1695210587863-add-isgeneratedbyweebhook-to-rawinfo-schema.ts b/src/migrations/db/1695210587863-add-isgeneratedbyweebhook-to-rawinfo-schema.ts index f54c567e..54176114 100644 --- a/src/migrations/db/1695210587863-add-isgeneratedbyweebhook-to-rawinfo-schema.ts +++ b/src/migrations/db/1695210587863-add-isgeneratedbyweebhook-to-rawinfo-schema.ts @@ -2,10 +2,9 @@ import 'dotenv/config'; import { Client, GatewayIntentBits, } from 'discord.js'; import { guildService } from '../../database/services'; import { connectDB } from '../../database'; -import { databaseService } from '@togethercrew.dev/db'; import config from '../../config'; -import { closeConnection } from '../../database/connection'; import webhookLogic from '../utils/webhookLogic'; +import DatabaseManager from '../../database/connection'; const { Guilds, @@ -25,9 +24,8 @@ export const up = async () => { await connectDB(); const guilds = await guildService.getGuilds({}); for (let i = 0; i < guilds.length; i++) { - const connection = databaseService.connectionFactory(guilds[i].guildId, config.mongoose.dbURL); + const connection = DatabaseManager.getInstance().getTenantDb(guilds[i].guildId); await webhookLogic(connection, client, guilds[i].guildId); - await closeConnection(connection); } }; diff --git a/src/migrations/utils/template.ts b/src/migrations/utils/template.ts index 1bb0f4e2..4b0954ee 100644 --- a/src/migrations/utils/template.ts +++ b/src/migrations/utils/template.ts @@ -1,11 +1,11 @@ import { connectDB } from '../../database'; -import { databaseService } from '@togethercrew.dev/db'; import 'dotenv/config'; -import config from '../../config'; +import DatabaseManager from '../../database/connection'; export const up = async () => { await connectDB(); - const connection = databaseService.connectionFactory("681946187490000803", config.mongoose.dbURL); + const connection = DatabaseManager.getInstance().getTenantDb("681946187490000803"); + await connection.createCollection('my_collection'); };