diff --git a/package-lock.json b/package-lock.json index ad2e0ee6..c0e35ac0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "ISC", "dependencies": { "@sentry/node": "^7.51.2", - "@togethercrew.dev/db": "^2.4.95", + "@togethercrew.dev/db": "^2.4.96", "@togethercrew.dev/tc-messagebroker": "^0.0.40", "babel-jest": "^29.5.0", "bullmq": "^3.14.0", @@ -20,6 +20,7 @@ "mongodb": "^5.4.0", "mongoose": "^6.11.1", "node-fetch": "^2.6.7", + "pino": "^8.15.0", "redis": "^4.6.6" }, "devDependencies": { @@ -2947,9 +2948,9 @@ } }, "node_modules/@togethercrew.dev/db": { - "version": "2.4.95", - "resolved": "https://registry.npmjs.org/@togethercrew.dev/db/-/db-2.4.95.tgz", - "integrity": "sha512-aMOFGnL0dK3YZU/Ha5FfyJvp25yexqWlUmS4v8CB+IzAq8V1RdtZK5vdVx+lDz9XUL40hlMLQzNRfDoJiqMv8w==", + "version": "2.4.96", + "resolved": "https://registry.npmjs.org/@togethercrew.dev/db/-/db-2.4.96.tgz", + "integrity": "sha512-DgM7H+IFXB1eZEXtBV1RZGMMZ7Ydgn4j8HgVstj+4YtQqbWKavNWFQsH0I4dkIreQFRvS106Erx0L9EKiyz7wg==", "dependencies": { "discord.js": "^14.7.1", "joi": "^17.7.0", @@ -3522,6 +3523,17 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -3795,6 +3807,14 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -5523,6 +5543,22 @@ "node": ">=0.10.0" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "engines": { + "node": ">=0.8.x" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -5622,6 +5658,14 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-redact": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.3.0.tgz", + "integrity": "sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/fast-safe-stringify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", @@ -9061,6 +9105,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.0.tgz", + "integrity": "sha512-VuCaZZAjReZ3vUwgOB8LxAosIurDiAW0s13rI1YwmaP++jvcxP77AWoQvenZebpCA2m8WC1/EosPYPMjnRAp/w==" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -9244,6 +9293,106 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pino": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-8.15.0.tgz", + "integrity": "sha512-olUADJByk4twxccmAxb1RiGKOSvddHugCV3wkqjyv+3Sooa2KLrmXrKEWOKi0XPCLasRR5jBXxioE1jxUa4KzQ==", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.1.1", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "v1.0.0", + "pino-std-serializers": "^6.0.0", + "process-warning": "^2.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^3.1.0", + "thread-stream": "^2.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.0.0.tgz", + "integrity": "sha512-c7vo5OpW4wIS42hUVcT5REsL8ZljsUfBjqV/e2sFxmFEFZiq1XLUp5EYLtuDH6PEHq9W1egWqRbnLUP5FuZmOA==", + "dependencies": { + "readable-stream": "^4.0.0", + "split2": "^4.0.0" + } + }, + "node_modules/pino-abstract-transport/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/pino-abstract-transport/node_modules/readable-stream": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.4.2.tgz", + "integrity": "sha512-Lk/fICSyIhodxy1IDK2HazkeGjSmezAWX2egdtJnYhtzKEsBPJowlI6F6LPb5tqIQILrMbx22S5o3GuJavPusA==", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/pino-abstract-transport/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/pino-abstract-transport/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz", + "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==" + }, "node_modules/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -9326,6 +9475,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/process-warning": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-2.2.0.tgz", + "integrity": "sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg==" + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -9409,6 +9571,11 @@ } ] }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==" + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -9447,6 +9614,14 @@ "node": ">=8.10.0" } }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/redis": { "version": "4.6.7", "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.7.tgz", @@ -9679,6 +9854,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, "node_modules/saslprep": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", @@ -9801,6 +9984,14 @@ "npm": ">= 3.0.0" } }, + "node_modules/sonic-boom": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.3.0.tgz", + "integrity": "sha512-LYxp34KlZ1a2Jb8ZQgFCK3niIHzibdwtwNUWKg0qQRzsDoJ3Gfgkf8KdBTFU3SkejDEIlWwnSnpVdOZIhFMl/g==", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -9829,6 +10020,14 @@ "memory-pager": "^1.0.2" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -10147,6 +10346,14 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/thread-stream": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.3.0.tgz", + "integrity": "sha512-kaDqm1DET9pp3NXwR8382WHbnpXnRkN9xGN9dQt3B2+dmXiW8X1SOwmFOxAErEQ47ObhZ96J6yhZNXuyCOL7KA==", + "dependencies": { + "real-require": "^0.2.0" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", diff --git a/package.json b/package.json index 87177060..034b3959 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "homepage": "https://github.com/Behzad-rabiei/tc-discordBot#readme", "dependencies": { "@sentry/node": "^7.51.2", - "@togethercrew.dev/db": "^2.4.95", + "@togethercrew.dev/db": "^2.4.96", "@togethercrew.dev/tc-messagebroker": "^0.0.40", "babel-jest": "^29.5.0", "bullmq": "^3.14.0", @@ -36,6 +36,7 @@ "mongodb": "^5.4.0", "mongoose": "^6.11.1", "node-fetch": "^2.6.7", + "pino": "^8.15.0", "redis": "^4.6.6" }, "devDependencies": { diff --git a/src/config/index.ts b/src/config/index.ts index def73ed7..0168e183 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -20,6 +20,7 @@ const envVarsSchema = Joi.object() REDIS_HOST: Joi.string().required().description('Reids host'), REDIS_PORT: Joi.string().required().description('Reids port'), REDIS_PASSWORD: Joi.string().required().description('Reids password').allow(''), + LOG_LEVEL: Joi.string().required().description('Min allowed log level'), }) .unknown(); @@ -51,4 +52,7 @@ export default { dsn: envVars.SENTRY_DSN, env: envVars.SENTRY_ENV, }, + logger: { + level: envVars.LOG_LEVEL, + }, }; diff --git a/src/config/logger.ts b/src/config/logger.ts new file mode 100644 index 00000000..cabd9b48 --- /dev/null +++ b/src/config/logger.ts @@ -0,0 +1,18 @@ +import pino, { Bindings } from 'pino'; +import config from './index'; + +export default pino({ + level: config.logger.level, + formatters: { + level: label => { + return { level: label.toUpperCase() }; + }, + }, + timestamp: () => `,"timestamp":"${new Date(Date.now()).toISOString()}"`, + bindings: (bindings: Bindings) => { + return { + pid: bindings.pid, + host: bindings.hostname, + }; + }, +}); diff --git a/src/database/connection.ts b/src/database/connection.ts index 84492961..a595f168 100644 --- a/src/database/connection.ts +++ b/src/database/connection.ts @@ -1,4 +1,8 @@ import { Connection } from 'mongoose'; +import parentLogger from '../config/logger'; + +const logger = parentLogger.child({ module: 'Connection' }); + /** * Closes a given Mongoose connection. * @param {Connection} connection - The Mongoose connection object to be closed. @@ -8,8 +12,8 @@ import { Connection } from 'mongoose'; export async function closeConnection(connection: Connection) { try { await connection.close(); - console.log('The connection to the database has been successfully closed.'); - } catch (err) { - console.log('Error closing connection to the database:', err); + logger.info({ database: connection.name }, 'The connection to database has been successfully closed'); + } catch (error) { + logger.error({ database: connection.name, error }, 'Failed to close the connection to the database'); } } diff --git a/src/database/index.ts b/src/database/index.ts index a3426f78..5eeb399b 100644 --- a/src/database/index.ts +++ b/src/database/index.ts @@ -1,10 +1,13 @@ import mongoose from 'mongoose'; import config from '../config'; +import logger from '../config/logger'; -// Connect to MongoDB export async function connectDB() { mongoose.set('strictQuery', false); - mongoose.connect(config.mongoose.serverURL).then(() => { - console.log('Connected to MongoDB!'); - }); + mongoose + .connect(config.mongoose.serverURL) + .then(() => { + logger.info({ url: config.mongoose.serverURL }, 'Connected to MongoDB!'); + }) + .catch(error => logger.error({ url: config.mongoose.serverURL, error }, 'Failed to connect to MongoDB!')); } diff --git a/src/database/services/channel.service.ts b/src/database/services/channel.service.ts index d1412cc9..9d401bbb 100644 --- a/src/database/services/channel.service.ts +++ b/src/database/services/channel.service.ts @@ -1,5 +1,9 @@ import { Connection } from 'mongoose'; import { IChannel, IChannelMethods, IChannelUpdateBody } from '@togethercrew.dev/db'; +import { VoiceChannel, TextChannel, CategoryChannel } from 'discord.js'; +import parentLogger from '../../config/logger'; + +const logger = parentLogger.child({ module: 'ChannelService' }); /** * Create a channel in the database. @@ -10,8 +14,13 @@ import { IChannel, IChannelMethods, IChannelUpdateBody } from '@togethercrew.dev async function createChannel(connection: Connection, channel: IChannel): Promise { try { return await connection.models.Channel.create(channel); - } catch (error) { - console.log('Failed to create channel', error); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + if (error.code == 11000) { + logger.warn({ database: connection.name, channel_id: channel.channelId }, 'Failed to create duplicate channel'); + return null; + } + logger.error({ database: connection.name, channel_id: channel.channelId, error }, 'Failed to create channel'); return null; } } @@ -25,8 +34,13 @@ async function createChannel(connection: Connection, channel: IChannel): Promise async function createChannels(connection: Connection, channels: IChannel[]): Promise { try { return await connection.models.Channel.insertMany(channels, { ordered: false }); - } catch (error) { - console.log('Failed to create channels', error); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + if (error.code == 11000) { + logger.warn({ database: connection.name }, 'Failed to create duplicate channels'); + return []; + } + logger.error({ database: connection.name, error }, 'Failed to create channels'); return []; } } @@ -38,12 +52,7 @@ async function createChannels(connection: Connection, channels: IChannel[]): Pro * @returns {Promise} - A promise that resolves to the matching channel object or null if not found. */ async function getChannel(connection: Connection, filter: object): Promise<(IChannel & IChannelMethods) | null> { - try { - return await connection.models.Channel.findOne(filter); - } catch (error) { - console.log('Failed to retrieve channel', error); - return null; - } + return await connection.models.Channel.findOne(filter); } /** @@ -53,35 +62,30 @@ async function getChannel(connection: Connection, filter: object): Promise<(ICha * @returns {Promise} - A promise that resolves to an array of the matching channel objects. */ async function getChannels(connection: Connection, filter: object): Promise { - try { - return await connection.models.Channel.find(filter); - } catch (error) { - console.log('Failed to retrieve channels', error); - return []; - } + return await connection.models.Channel.find(filter); } /** * Update a channel in the database based on the filter criteria. * @param {Connection} connection - Mongoose connection object for the database. * @param {object} filter - An object specifying the filter criteria to match the desired channel entry. - * @param {IChannelUpdateBody} UpdateBody - An object containing the updated channel data. + * @param {IChannelUpdateBody} updateBody - An object containing the updated channel data. * @returns {Promise} - A promise that resolves to the updated channel object or null if not found. */ async function updateChannel( connection: Connection, filter: object, - UpdateBody: IChannelUpdateBody + updateBody: IChannelUpdateBody ): Promise { try { const channel = await connection.models.Channel.findOne(filter); if (!channel) { return null; } - Object.assign(channel, UpdateBody); + Object.assign(channel, updateBody); return await channel.save(); } catch (error) { - console.log('Failed to update channel', error); + logger.error({ database: connection.name, filter, updateBody, error }, 'Failed to update channel'); return null; } } @@ -90,19 +94,60 @@ async function updateChannel( * Update multiple channels in the database based on the filter criteria. * @param {Connection} connection - Mongoose connection object for the database. * @param {object} filter - An object specifying the filter criteria to match multiple channel entries. - * @param {IChannelUpdateBody} UpdateBody - An object containing the updated channel data. + * @param {IChannelUpdateBody} updateBody - An object containing the updated channel data. * @returns {Promise} - A promise that resolves to the number of updated channel entries. */ -async function updateChannels(connection: Connection, filter: object, UpdateBody: IChannelUpdateBody): Promise { +async function updateChannels(connection: Connection, filter: object, updateBody: IChannelUpdateBody): Promise { try { - const updateResult = await connection.models.Channel.updateMany(filter, UpdateBody); + const updateResult = await connection.models.Channel.updateMany(filter, updateBody); return updateResult.modifiedCount || 0; } catch (error) { - console.log('Failed to update channels', error); + logger.error({ database: connection.name, filter, updateBody, error }, 'Failed to update channels'); return 0; } } +/** + * Handle the logic for creating or updating channels in the database. + * @param {Connection} connection - Mongoose connection object for the specific guild database. + * @param {TextChannel | VoiceChannel | CategoryChannel} channel - The Discord.js Channel object containing the full channel details. + * @returns {Promise} - A promise that resolves when the create or update operation is complete. + * + */ +async function handelChannelChanges( + connection: Connection, + channel: TextChannel | VoiceChannel | CategoryChannel +): Promise { + const commonFields = getNeededDateFromChannel(channel); + try { + const channelDoc = await updateChannel(connection, { channelId: channel.id }, commonFields); + if (!channelDoc) { + await createChannel(connection, commonFields); + } + } catch (error) { + logger.error({ guild_id: connection.name, channel_id: channel.id, error }, 'Failed to handle channel changes'); + } +} + +/** + * Extracts necessary fields from a Discord.js GuildMember object to form an IGuildMember object. + * @param {TextChannel | VoiceChannel | CategoryChannel} channel - The Discord.js Channel object containing the full channel details. + * @returns {IChannel} - The extracted data in the form of an IChannel object. + */ +function getNeededDateFromChannel(channel: TextChannel | VoiceChannel | CategoryChannel): IChannel { + return { + channelId: channel.id, + name: channel.name, // cast to TextChannel for 'name' + parentId: channel.parentId, + permissionOverwrites: Array.from(channel.permissionOverwrites.cache.values()).map(overwrite => ({ + id: overwrite.id, + type: overwrite.type, + allow: overwrite.allow.bitfield.toString(), + deny: overwrite.deny.bitfield.toString(), + })), + }; +} + export default { createChannel, createChannels, @@ -110,4 +155,6 @@ export default { getChannel, getChannels, updateChannels, + handelChannelChanges, + getNeededDateFromChannel, }; diff --git a/src/database/services/guild.service.ts b/src/database/services/guild.service.ts index b85cc94e..212628a8 100644 --- a/src/database/services/guild.service.ts +++ b/src/database/services/guild.service.ts @@ -1,4 +1,8 @@ import { Guild, IGuild, IGuildUpdateBody } from '@togethercrew.dev/db'; +import { Snowflake, Client } from 'discord.js'; +import parentLogger from '../../config/logger'; + +const logger = parentLogger.child({ module: 'GuildService' }); /** * get guild by query @@ -15,30 +19,25 @@ async function getGuild(filter: object): Promise { * @returns {Promise} - A promise that resolves to an array of matching guild entries. */ async function getGuilds(filter: object): Promise { - try { - return await Guild.find(filter); - } catch (error) { - console.log('Failed to retrieve guilds', error); - return []; - } + return await Guild.find(filter); } /** * Update the guild entry that matches the provided filter with the provided data. * @param {object} filter - Filter criteria to match the desired guild entry for update. - * @param {IGuildUpdateBody} UpdateBody - Updated information for the guild entry. + * @param {IGuildUpdateBody} updateBody - Updated information for the guild entry. * @returns {Promise} - A promise that resolves to the updated guild entry, or null if not found. */ -async function updateGuild(filter: object, UpdateBody: IGuildUpdateBody): Promise { +async function updateGuild(filter: object, updateBody: IGuildUpdateBody): Promise { try { const guild = await Guild.findOne(filter); if (!guild) { return null; } - Object.assign(guild, UpdateBody); + Object.assign(guild, updateBody); return await guild.save(); } catch (error) { - console.log('Failed to update guild', error); + logger.error({ database: 'RnDAO', filter, updateBody, error }, 'Failed to update guild'); return null; } } @@ -46,23 +45,32 @@ async function updateGuild(filter: object, UpdateBody: IGuildUpdateBody): Promis /** * Update multiple guild entries that match the provided filter with the provided data. * @param {object} filter - Filter criteria to match the desired guild entries for update. - * @param {IGuildUpdateBody} UpdateBody - Updated information for the guild entry. + * @param {IGuildUpdateBody} updateBody - Updated information for the guild entry. * @returns {Promise} - A promise that resolves to the number of guild entries updated. */ -async function updateManyGuilds(filter: object, UpdateBody: IGuildUpdateBody): Promise { +async function updateManyGuilds(filter: object, updateBody: IGuildUpdateBody): Promise { try { - const updateResult = await Guild.updateMany(filter, UpdateBody); + const updateResult = await Guild.updateMany(filter, updateBody); const modifiedCount = updateResult.modifiedCount; return modifiedCount; } catch (error) { - console.log('Failed to update guilds', error); + logger.error({ database: 'RnDAO', filter, updateBody, error }, 'Failed to update guilds'); return 0; } } +async function checkBotAccessToGuild(client: Client, guildId: Snowflake) { + if (!client.guilds.cache.has(guildId)) { + await updateGuild({ guildId }, { isDisconnected: false }); + return false; + } + return true; +} + export default { getGuild, getGuilds, updateGuild, updateManyGuilds, + checkBotAccessToGuild, }; diff --git a/src/database/services/guildMember.service.ts b/src/database/services/guildMember.service.ts index e6edc52c..dbbb2d85 100644 --- a/src/database/services/guildMember.service.ts +++ b/src/database/services/guildMember.service.ts @@ -1,6 +1,9 @@ import { Connection } from 'mongoose'; import { IGuildMember, IGuildMemberMethods, IGuildMemberUpdateBody } from '@togethercrew.dev/db'; +import { GuildMember } from 'discord.js'; +import parentLogger from '../../config/logger'; +const logger = parentLogger.child({ module: 'GuildMemberService' }); /** * Create a guild member in the database. * @param {Connection} connection - Mongoose connection object for the database. @@ -10,8 +13,19 @@ import { IGuildMember, IGuildMemberMethods, IGuildMemberUpdateBody } from '@toge async function createGuildMember(connection: Connection, guildMember: IGuildMember): Promise { try { return await connection.models.GuildMember.create(guildMember); - } catch (error) { - console.log('Failed to create guild member', error); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + if (error.code == 11000) { + logger.warn( + { database: connection.name, guild_member_id: guildMember.discordId }, + 'Failed to create duplicate guild member' + ); + return null; + } + logger.error( + { database: connection.name, guild_member_id: guildMember.discordId, error }, + 'Failed to create guild member' + ); return null; } } @@ -25,8 +39,13 @@ async function createGuildMember(connection: Connection, guildMember: IGuildMemb async function createGuildMembers(connection: Connection, guildMembers: IGuildMember[]): Promise { try { return await connection.models.GuildMember.insertMany(guildMembers, { ordered: false }); - } catch (error) { - console.log('Failed to create guild members', error); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + if (error.code == 11000) { + logger.warn({ database: connection.name }, 'Failed to create duplicate guild members'); + return []; + } + logger.error({ database: connection.name, error }, 'Failed to create guild members'); return []; } } @@ -41,12 +60,7 @@ async function getGuildMember( connection: Connection, filter: object ): Promise<(IGuildMember & IGuildMemberMethods) | null> { - try { - return await connection.models.GuildMember.findOne(filter); - } catch (error) { - console.log('Failed to retrieve guild member', error); - return null; - } + return await connection.models.GuildMember.findOne(filter); } /** @@ -56,35 +70,30 @@ async function getGuildMember( * @returns {Promise} - A promise that resolves to an array of the matching guild member objects. */ async function getGuildMembers(connection: Connection, filter: object): Promise { - try { - return await connection.models.GuildMember.find(filter); - } catch (error) { - console.log('Failed to retrieve guild members', error); - return []; - } + return await connection.models.GuildMember.find(filter); } /** * Update a guild member in the database based on the filter criteria. * @param {Connection} connection - Mongoose connection object for the database. * @param {object} filter - An object specifying the filter criteria to match the desired guild member entry. - * @param {IGuildMemberUpdateBody} UpdateBody - An object containing the updated guild member data. + * @param {IGuildMemberUpdateBody} updateBody - An object containing the updated guild member data. * @returns {Promise} - A promise that resolves to the updated guild member object or null if not found. */ async function updateGuildMember( connection: Connection, filter: object, - UpdateBody: IGuildMemberUpdateBody + updateBody: IGuildMemberUpdateBody ): Promise { try { const guildMember = await connection.models.GuildMember.findOne(filter); if (!guildMember) { return null; } - Object.assign(guildMember, UpdateBody); + Object.assign(guildMember, updateBody); return await guildMember.save(); } catch (error) { - console.log('Failed to update guild member', error); + logger.error({ database: connection.name, filter, updateBody, error }, 'Failed to update guild member'); return null; } } @@ -93,19 +102,19 @@ async function updateGuildMember( * Update multiple guild members in the database based on the filter criteria. * @param {Connection} connection - Mongoose connection object for the database. * @param {object} filter - An object specifying the filter criteria to match multiple guild member entries. - * @param {IGuildMemberUpdateBody} UpdateBody - An object containing the updated guild member data. + * @param {IGuildMemberUpdateBody} updateBody - An object containing the updated guild member data. * @returns {Promise} - A promise that resolves to the number of updated guild member entries. */ async function updateGuildMembers( connection: Connection, filter: object, - UpdateBody: IGuildMemberUpdateBody + updateBody: IGuildMemberUpdateBody ): Promise { try { - const updateResult = await connection.models.GuildMember.updateMany(filter, UpdateBody); + const updateResult = await connection.models.GuildMember.updateMany(filter, updateBody); return updateResult.modifiedCount || 0; } catch (error) { - console.log('Failed to update guild members', error); + logger.error({ database: connection.name, filter, updateBody, error }, 'Failed to update guild members'); return 0; } } @@ -121,7 +130,7 @@ async function deleteGuildMember(connection: Connection, filter: object): Promis const deleteResult = await connection.models.GuildMember.deleteOne(filter); return deleteResult.deletedCount === 1; } catch (error) { - console.log('Failed to delete guild member', error); + logger.error({ database: connection.name, filter, error }, 'Failed to delete guild member'); return false; } } @@ -137,11 +146,55 @@ async function deleteGuildMembers(connection: Connection, filter: object): Promi const deleteResult = await connection.models.GuildMember.deleteMany(filter); return deleteResult.deletedCount; } catch (error) { - console.log('Failed to delete guild members', error); + logger.error({ database: connection.name, filter, error }, 'Failed to delete guild members'); return 0; } } +/** + * Handle the logic for creating or updating guild members in the database. + * @param {Connection} connection - Mongoose connection object for the specific guild database. + * @param {GuildMember} guildMember - The Discord.js GuildMember object containing the member details. + * @returns {Promise} - A promise that resolves when the create or update operation is complete. + * + */ +async function handelGuildMemberChanges(connection: Connection, guildMember: GuildMember): Promise { + const commonFields = getNeededDateFromGuildMember(guildMember); + try { + const guildMemberDoc = await updateGuildMember(connection, { discordId: guildMember.user.id }, commonFields); + if (!guildMemberDoc) { + await createGuildMember(connection, { + ...commonFields, + isBot: guildMember.user.bot, + }); + } + } catch (error) { + logger.error( + { guild_id: connection.name, guild_member_id: guildMember.id, error }, + 'Failed to handle guild member changes' + ); + } +} + +/** + * Extracts necessary fields from a Discord.js GuildMember object to form an IGuildMember object. + * @param {GuildMember} guildMember - The Discord.js GuildMember object containing the full member details. + * @returns {IGuildMember} - An object that adheres to the IGuildMember interface, containing selected fields from the provided guild member. + */ +function getNeededDateFromGuildMember(guildMember: GuildMember): IGuildMember { + return { + discordId: guildMember.user.id, + username: guildMember.user.username, + avatar: guildMember.user.avatar, + joinedAt: guildMember.joinedAt, + roles: guildMember.roles.cache.map(role => role.id), + discriminator: guildMember.user.discriminator, + permissions: guildMember.permissions.bitfield.toString(), + nickname: guildMember.nickname, + globalName: guildMember.user.globalName, + }; +} + export default { createGuildMember, createGuildMembers, @@ -151,4 +204,6 @@ export default { updateGuildMembers, deleteGuildMember, deleteGuildMembers, + handelGuildMemberChanges, + getNeededDateFromGuildMember, }; diff --git a/src/database/services/rawInfo.service.ts b/src/database/services/rawInfo.service.ts index bf0af303..31d42b25 100644 --- a/src/database/services/rawInfo.service.ts +++ b/src/database/services/rawInfo.service.ts @@ -1,6 +1,8 @@ import { Connection } from 'mongoose'; import { IRawInfo, IRawInfoUpdateBody } from '@togethercrew.dev/db'; +import parentLogger from '../../config/logger'; +const logger = parentLogger.child({ module: 'rawInfoService' }); /** * Create a rawInfo entry in the database. * @param {Connection} connection - Mongoose connection object for the database. @@ -10,8 +12,19 @@ import { IRawInfo, IRawInfoUpdateBody } from '@togethercrew.dev/db'; async function createRawInfo(connection: Connection, rawInfo: IRawInfo): Promise { try { return await connection.models.RawInfo.create(rawInfo); - } catch (error) { - console.log('Failed to create rawInfo', error); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + if (error.code == 11000) { + logger.warn( + { database: connection.name, channel_id: rawInfo.channelId, message_id: rawInfo.messageId }, + 'Failed to create duplicate rawInfo' + ); + return null; + } + logger.error( + { database: connection.name, channel_id: rawInfo.channelId, message_id: rawInfo.messageId }, + 'Failed to create rawInfo' + ); return null; } } @@ -25,8 +38,13 @@ async function createRawInfo(connection: Connection, rawInfo: IRawInfo): Promise async function createRawInfos(connection: Connection, rawInfos: IRawInfo[]): Promise { try { return await connection.models.RawInfo.insertMany(rawInfos, { ordered: false }); - } catch (error) { - console.log('Failed to create rawInfos', error); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + if (error.code == 11000) { + logger.warn({ database: connection.name }, 'Failed to create duplicate rawInfos'); + return []; + } + logger.error({ database: connection.name, error }, 'Failed to create rawInfos'); return []; } } @@ -38,12 +56,7 @@ async function createRawInfos(connection: Connection, rawInfos: IRawInfo[]): Pro * @returns {Promise} - A promise that resolves to the matching rawInfo object or null if not found. */ async function getRawInfo(connection: Connection, filter: object): Promise { - try { - return await connection.models.RawInfo.findOne(filter); - } catch (error) { - console.log('Failed to retrieve rawInfo', error); - return null; - } + return await connection.models.RawInfo.findOne(filter); } /** @@ -53,35 +66,30 @@ async function getRawInfo(connection: Connection, filter: object): Promise} - A promise that resolves to an array of the matching rawInfo objects. */ async function getRawInfos(connection: Connection, filter: object): Promise { - try { - return await connection.models.RawInfo.find(filter); - } catch (error) { - console.log('Failed to retrieves rawInfo', error); - return []; - } + return await connection.models.RawInfo.find(filter); } /** * Update a rawInfo entry in the database based on the filter criteria. * @param {Connection} connection - Mongoose connection object for the database. * @param {object} filter - An object specifying the filter criteria to match the desired rawInfo entry. - * @param {IRawInfo} UpdateBody - An object containing the updated rawInfo data. + * @param {IRawInfo} updateBody - An object containing the updated rawInfo data. * @returns {Promise} - A promise that resolves to the updated rawInfo object or null if not found. */ async function updateRawInfo( connection: Connection, filter: object, - UpdateBody: IRawInfoUpdateBody + updateBody: IRawInfoUpdateBody ): Promise { try { const rawInfo = await connection.models.RawInfo.findOne(filter); if (!rawInfo) { return null; } - Object.assign(rawInfo, UpdateBody); + Object.assign(rawInfo, updateBody); return await rawInfo.save(); } catch (error) { - console.log('Failed to update rawInfo', error); + logger.error({ database: connection.name, filter, updateBody, error }, 'Failed to update rawInfo'); return null; } } @@ -90,19 +98,19 @@ async function updateRawInfo( * Update multiple rawInfo entries in the database based on the filter criteria. * @param {Connection} connection - Mongoose connection object for the database. * @param {object} filter - An object specifying the filter criteria to match multiple rawInfo entries. - * @param {IRawInfo} UpdateBody - An object containing the updated rawInfo data. + * @param {IRawInfo} updateBody - An object containing the updated rawInfo data. * @returns {Promise} - A promise that resolves to the number of updated rawInfo entries. */ async function updateManyRawInfo( connection: Connection, filter: object, - UpdateBody: IRawInfoUpdateBody + updateBody: IRawInfoUpdateBody ): Promise { try { - const updateResult = await connection.models.RawInfo.updateMany(filter, UpdateBody); + const updateResult = await connection.models.RawInfo.updateMany(filter, updateBody); return updateResult.modifiedCount || 0; } catch (error) { - console.log('Failed to update rawInfos', error); + logger.error({ database: connection.name, filter, updateBody, error }, 'Failed to update rawInfos'); return 0; } } @@ -118,7 +126,7 @@ async function deleteRawInfo(connection: Connection, filter: object): Promise} - A promise that resolves to the oldest rawInfo object for the channel, or null if not found. */ async function getNewestRawInfo(connection: Connection, filter: object): Promise { - try { - return await connection.models.RawInfo.findOne(filter).sort({ createdDate: -1 }); - } catch (error) { - console.log('Failed to retrieve NewestRawInfo', error); - return null; - } + return await connection.models.RawInfo.findOne(filter).sort({ createdDate: -1 }); } /** @@ -162,12 +165,7 @@ async function getNewestRawInfo(connection: Connection, filter: object): Promise * @returns {Promise} - A promise that resolves to the oldest rawInfo object for the channel, or null if not found. */ async function getOldestRawInfo(connection: Connection, filter: object): Promise { - try { - return await connection.models.RawInfo.findOne(filter).sort({ createdDate: 1 }); - } catch (error) { - console.log('Failed to retrieve OldestRawInfo', error); - return null; - } + return await connection.models.RawInfo.findOne(filter).sort({ createdDate: 1 }); } export default { diff --git a/src/database/services/role.service.ts b/src/database/services/role.service.ts index 2a204e18..50259d62 100644 --- a/src/database/services/role.service.ts +++ b/src/database/services/role.service.ts @@ -1,6 +1,9 @@ import { Connection } from 'mongoose'; import { IRole, IRoleMethods, IRoleUpdateBody } from '@togethercrew.dev/db'; +import { Role } from 'discord.js'; +import parentLogger from '../../config/logger'; +const logger = parentLogger.child({ module: 'roleService' }); /** * Create a role in the database. * @param {Connection} connection - Mongoose connection object for the database. @@ -10,8 +13,13 @@ import { IRole, IRoleMethods, IRoleUpdateBody } from '@togethercrew.dev/db'; async function createRole(connection: Connection, role: IRole): Promise { try { return await connection.models.Role.create(role); - } catch (error) { - console.log('Failed to create role', error); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + if (error.code == 11000) { + logger.warn({ database: connection.name, role_id: role.roleId }, 'Failed to create duplicate role'); + return null; + } + logger.error({ database: connection.name, role_id: role.roleId, error }, 'Failed to create role'); return null; } } @@ -25,8 +33,13 @@ async function createRole(connection: Connection, role: IRole): Promise { try { return await connection.models.Role.insertMany(roles, { ordered: false }); - } catch (error) { - console.log('Failed to create roles', error); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } catch (error: any) { + if (error.code == 11000) { + logger.warn({ database: connection.name }, 'Failed to create duplicate roles'); + return []; + } + logger.error({ database: connection.name, error }, 'Failed to create roles'); return []; } } @@ -38,12 +51,7 @@ async function createRoles(connection: Connection, roles: IRole[]): Promise} - A promise that resolves to the matching role object or null if not found. */ async function getRole(connection: Connection, filter: object): Promise<(IRole & IRoleMethods) | null> { - try { - return await connection.models.Role.findOne(filter); - } catch (error) { - console.log('Failed to retrieve role', error); - return null; - } + return await connection.models.Role.findOne(filter); } /** @@ -53,31 +61,26 @@ async function getRole(connection: Connection, filter: object): Promise<(IRole & * @returns {Promise} - A promise that resolves to an array of the matching role objects. */ async function getRoles(connection: Connection, filter: object): Promise { - try { - return await connection.models.Role.find(filter); - } catch (error) { - console.log('Failed to retrieve roles', error); - return []; - } + return await connection.models.Role.find(filter); } /** * Update a role in the database based on the filter criteria. * @param {Connection} connection - Mongoose connection object for the database. * @param {object} filter - An object specifying the filter criteria to match the desired role entry. - * @param {IRoleUpdateBody} UpdateBody - An object containing the updated role data. + * @param {IRoleUpdateBody} updateBody - An object containing the updated role data. * @returns {Promise} - A promise that resolves to the updated role object or null if not found. */ -async function updateRole(connection: Connection, filter: object, UpdateBody: IRoleUpdateBody): Promise { +async function updateRole(connection: Connection, filter: object, updateBody: IRoleUpdateBody): Promise { try { const role = await connection.models.Role.findOne(filter); if (!role) { return null; } - Object.assign(role, UpdateBody); + Object.assign(role, updateBody); return await role.save(); } catch (error) { - console.log('Failed to update role', error); + logger.error({ database: connection.name, filter, updateBody, error }, 'Failed to update role'); return null; } } @@ -86,19 +89,51 @@ async function updateRole(connection: Connection, filter: object, UpdateBody: IR * Update multiple roles in the database based on the filter criteria. * @param {Connection} connection - Mongoose connection object for the database. * @param {object} filter - An object specifying the filter criteria to match multiple role entries. - * @param {IRoleUpdateBody} UpdateBody - An object containing the updated role data. + * @param {IRoleUpdateBody} updateBody - An object containing the updated role data. * @returns {Promise} - A promise that resolves to the number of updated role entries. */ -async function updateRoles(connection: Connection, filter: object, UpdateBody: IRoleUpdateBody): Promise { +async function updateRoles(connection: Connection, filter: object, updateBody: IRoleUpdateBody): Promise { try { - const updateResult = await connection.models.Role.updateMany(filter, UpdateBody); + const updateResult = await connection.models.Role.updateMany(filter, updateBody); return updateResult.modifiedCount || 0; } catch (error) { - console.log('Failed to update roles', error); + logger.error({ database: connection.name, filter, updateBody, error }, 'Failed to update roles'); return 0; } } +/** + * Handle the logic for creating or updating roles in the database. + * @param {Connection} connection - Mongoose connection object for the specific guild database. + * @param {Role} role - The Discord.js Role object containing the role details. + * @returns {Promise} - A promise that resolves when the create or update operation is complete. + * + */ +async function handelRoleChanges(connection: Connection, role: Role): Promise { + const commonFields = getNeededDateFromRole(role); + try { + const roleDoc = await updateRole(connection, { roleId: role.id }, commonFields); + if (!roleDoc) { + await createRole(connection, commonFields); + } + } catch (error) { + logger.error({ guild_id: connection.name, role_id: role.id, error }, 'Failed to handle role changes'); + } +} + +/** + * Extracts necessary fields from a Discord.js Role object to form an IRole object. + * @param {Role} guildMember - The Discord.js Role object containing the full role details. + * @returns {IRole} - An object that adheres to the Role interface, containing selected fields from the provided role. + */ +function getNeededDateFromRole(role: Role): IRole { + return { + roleId: role.id, + name: role.name, + color: role.color, + }; +} + export default { createRole, createRoles, @@ -106,4 +141,6 @@ export default { getRole, getRoles, updateRoles, + handelRoleChanges, + getNeededDateFromRole, }; diff --git a/src/events/channel/channelCreate.ts b/src/events/channel/channelCreate.ts index 8f9ba152..9d2db07f 100644 --- a/src/events/channel/channelCreate.ts +++ b/src/events/channel/channelCreate.ts @@ -1,32 +1,28 @@ -import { Events, Channel, GuildChannel, TextChannel } from 'discord.js'; +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 parentLogger from '../../config/logger'; + +const logger = parentLogger.child({ event: 'ChannelCreate' }); export default { name: Events.ChannelCreate, once: false, async execute(channel: Channel) { - try { - if (channel instanceof GuildChannel && channel instanceof TextChannel) { - const connection = databaseService.connectionFactory(channel.guildId, config.mongoose.dbURL); - await channelService.createChannel(connection, { - channelId: channel.id, - name: channel.name, - parentId: channel.parentId, - permissionOverwrites: Array.from(channel.permissionOverwrites.cache.values()).map(overwrite => ({ - id: overwrite.id, - type: overwrite.type, - allow: overwrite.allow.bitfield.toString(), - deny: overwrite.deny.bitfield.toString(), - })), - }); + 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); + try { + await channelService.handelChannelChanges(connection, channel); + } catch (err) { + logger.error({ ...logFields, err }, 'Failed to handle channel changes'); + } finally { await closeConnection(connection); + logger.info(logFields, 'event is done'); } - } catch (err) { - // TODO: improve error handling - console.log(err); } }, }; diff --git a/src/events/channel/channelDelete.ts b/src/events/channel/channelDelete.ts index d7cc8260..e6494d81 100644 --- a/src/events/channel/channelDelete.ts +++ b/src/events/channel/channelDelete.ts @@ -1,23 +1,34 @@ -import { Events, GuildChannel, Channel, TextChannel } from 'discord.js'; -import { channelService } from '../../database/services'; +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 parentLogger from '../../config/logger'; + +const logger = parentLogger.child({ event: 'ChannelDelete' }); export default { name: Events.ChannelDelete, once: false, async execute(channel: Channel) { - try { - if (channel instanceof GuildChannel && channel instanceof TextChannel) { - const connection = databaseService.connectionFactory(channel.guildId, config.mongoose.dbURL); + 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); + try { const channelDoc = await channelService.getChannel(connection, { channelId: channel.id }); await channelDoc?.softDelete(); + const guildDoc = await guildService.getGuild({ guildId: channel.guild.id }); + const updatedSelecetdChannels = guildDoc?.selectedChannels?.filter( + selectedChannel => selectedChannel.channelId !== channel.id + ); + await guildService.updateGuild({ guildId: channel.guild.id }, { selectedChannels: updatedSelecetdChannels }); + } catch (err) { + logger.error({ ...logFields, err }, 'Failed to soft delete the channel'); + } finally { await closeConnection(connection); + logger.info(logFields, 'event is done'); } - } catch (err) { - // TODO: improve error handling - console.log(err); } }, }; diff --git a/src/events/channel/channelUpdate.ts b/src/events/channel/channelUpdate.ts index abbe2f8d..6c9332b5 100644 --- a/src/events/channel/channelUpdate.ts +++ b/src/events/channel/channelUpdate.ts @@ -1,53 +1,32 @@ -import { Events, Channel, GuildChannel, TextChannel } from 'discord.js'; +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 parentLogger from '../../config/logger'; + +const logger = parentLogger.child({ event: 'ChannelUpdate' }); export default { name: Events.ChannelUpdate, once: false, async execute(oldChannel: Channel, newChannel: Channel) { - try { - if ( - oldChannel instanceof GuildChannel && - oldChannel instanceof TextChannel && - newChannel instanceof GuildChannel && - newChannel instanceof TextChannel - ) { - const connection = databaseService.connectionFactory(oldChannel.guildId, config.mongoose.dbURL); - const channel = await channelService.updateChannel( - connection, - { channelId: oldChannel.id }, - { - name: newChannel.name, - parentId: newChannel.parentId, - permissionOverwrites: Array.from(newChannel.permissionOverwrites.cache.values()).map(overwrite => ({ - id: overwrite.id, - type: overwrite.type, - allow: overwrite.allow.bitfield.toString(), - deny: overwrite.deny.bitfield.toString(), - })), - } - ); - if (!channel) { - await channelService.createChannel(connection, { - channelId: newChannel.id, - name: newChannel.name, - parentId: newChannel.parentId, - permissionOverwrites: Array.from(newChannel.permissionOverwrites.cache.values()).map(overwrite => ({ - id: overwrite.id, - type: overwrite.type, - allow: overwrite.allow.bitfield.toString(), - deny: overwrite.deny.bitfield.toString(), - })), - }); - } + if ( + newChannel instanceof TextChannel || + newChannel instanceof VoiceChannel || + newChannel instanceof CategoryChannel + ) { + 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); + try { + await channelService.handelChannelChanges(connection, newChannel); + } catch (err) { + logger.error({ ...logFields, err }, 'Failed to handle channel changes'); + } finally { await closeConnection(connection); + logger.info(logFields, 'event is done'); } - } catch (err) { - // TODO: improve error handling - console.log(err); } }, }; diff --git a/src/events/client/ready.ts b/src/events/client/ready.ts index 0a7d5259..e97b669f 100644 --- a/src/events/client/ready.ts +++ b/src/events/client/ready.ts @@ -5,28 +5,31 @@ 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 config from '../../config'; +const logger = parentLogger.child({ event: 'ClientReady' }); + export default { name: Events.ClientReady, once: true, async execute(client: Client) { - console.log(`client READY event is running`); + 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); - console.log(`client READY: fetch members is running for ${guilds[i].guildId}:${guilds[i].name}`); - await fetchMembers(connection, client, guilds[i].guildId); - console.log(`client READY: fetch members is Done ${guilds[i].guildId}:${guilds[i].name}`); - - console.log(`client READY: fetch roles is running for ${guilds[i].guildId}:${guilds[i].name}`); - await fetchRoles(connection, client, guilds[i].guildId); - console.log(`client READY: fetch roles is Done ${guilds[i].guildId}:${guilds[i].name}`); - - console.log(`client READY: fetch channels is running for ${guilds[i].guildId}:${guilds[i].name}`); - await fetchChannels(connection, client, guilds[i].guildId); - console.log(`client READY: fetch channels is Done ${guilds[i].guildId}:${guilds[i].name}`); - await closeConnection(connection); + try { + logger.info({ guild_id: guilds[i].guildId }, 'Fetching guild members, roles,and channels'); + await fetchMembers(connection, client, guilds[i].guildId); + await fetchRoles(connection, client, guilds[i].guildId); + await fetchChannels(connection, client, guilds[i].guildId); + 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'); }, }; diff --git a/src/events/member/guildMemberAdd.ts b/src/events/member/guildMemberAdd.ts index cc184159..6b9baee7 100644 --- a/src/events/member/guildMemberAdd.ts +++ b/src/events/member/guildMemberAdd.ts @@ -3,46 +3,24 @@ import { guildMemberService } from '../../database/services'; import { databaseService } from '@togethercrew.dev/db'; import config from '../../config'; import { closeConnection } from '../../database/connection'; +import parentLogger from '../../config/logger'; + +const logger = parentLogger.child({ event: 'GuildMemberAdd' }); export default { name: Events.GuildMemberAdd, once: false, 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); try { - const connection = databaseService.connectionFactory(member.guild.id, config.mongoose.dbURL); - const guildMemberDoc = await guildMemberService.getGuildMember(connection, { discordId: member.user.id }); - if (guildMemberDoc) { - await guildMemberService.updateGuildMember( - connection, - { discordId: member.user.id }, - { - username: member.user.username, - avatar: member.user.avatar, - joinedAt: member.joinedAt, - roles: member.roles.cache.map(role => role.id), - discriminator: member.user.discriminator, - deletedAt: null, - permissions: member.permissions.bitfield.toString(), - nickname: member.nickname, - } - ); - } else { - await guildMemberService.createGuildMember(connection, { - discordId: member.user.id, - username: member.user.username, - avatar: member.user.avatar, - joinedAt: member.joinedAt, - roles: member.roles.cache.map(role => role.id), - isBot: member.user.bot, - discriminator: member.user.discriminator, - permissions: member.permissions.bitfield.toString(), - nickname: member.nickname, - }); - } - await closeConnection(connection); + await guildMemberService.handelGuildMemberChanges(connection, member); } catch (err) { - // TODO: improve error handling - console.log(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 beeb105a..68b829bd 100644 --- a/src/events/member/guildMemberRemove.ts +++ b/src/events/member/guildMemberRemove.ts @@ -3,19 +3,25 @@ import { guildMemberService } from '../../database/services'; import { databaseService } from '@togethercrew.dev/db'; import config from '../../config'; import { closeConnection } from '../../database/connection'; +import parentLogger from '../../config/logger'; + +const logger = parentLogger.child({ event: 'GuildMemberRemove' }); export default { name: Events.GuildMemberRemove, once: false, 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); try { - const connection = databaseService.connectionFactory(member.guild.id, config.mongoose.dbURL); const guildMemberDoc = await guildMemberService.getGuildMember(connection, { discordId: member.user.id }); await guildMemberDoc?.softDelete(); - await closeConnection(connection); } catch (err) { - // TODO: improve error handling - console.log(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 2665f9a2..feea86ee 100644 --- a/src/events/member/guildMemberUpdate.ts +++ b/src/events/member/guildMemberUpdate.ts @@ -3,45 +3,24 @@ import { guildMemberService } from '../../database/services'; import { databaseService } from '@togethercrew.dev/db'; import config from '../../config'; import { closeConnection } from '../../database/connection'; +import parentLogger from '../../config/logger'; + +const logger = parentLogger.child({ event: 'GuildMemberUpdate' }); export default { name: Events.GuildMemberUpdate, once: false, 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); try { - const connection = databaseService.connectionFactory(oldMember.guild.id, config.mongoose.dbURL); - const guildMember = await guildMemberService.updateGuildMember( - connection, - { discordId: oldMember.user.id }, - { - username: newMember.user.username, - avatar: newMember.user.avatar, - joinedAt: newMember.joinedAt, - roles: newMember.roles.cache.map(role => role.id), - discriminator: newMember.user.discriminator, - nickname: newMember.nickname, - permissions: newMember.permissions.bitfield.toString(), - globalName: newMember.user.globalName, - } - ); - if (!guildMember) { - await guildMemberService.createGuildMember(connection, { - discordId: newMember.user.id, - username: newMember.user.username, - avatar: newMember.user.avatar, - joinedAt: newMember.joinedAt, - roles: newMember.roles.cache.map(role => role.id), - isBot: newMember.user.bot, - discriminator: newMember.user.discriminator, - nickname: newMember.nickname, - permissions: newMember.permissions.bitfield.toString(), - globalName: newMember.user.globalName, - }); - } - await closeConnection(connection); + await guildMemberService.handelGuildMemberChanges(connection, newMember); } catch (err) { - // TODO: improve error handling - console.log(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 2d37133a..355fff43 100644 --- a/src/events/role/roleCreate.ts +++ b/src/events/role/roleCreate.ts @@ -3,22 +3,24 @@ import { roleService } from '../../database/services'; import { databaseService } from '@togethercrew.dev/db'; import config from '../../config'; import { closeConnection } from '../../database/connection'; +import parentLogger from '../../config/logger'; + +const logger = parentLogger.child({ event: 'GuildRoleCreate' }); export default { name: Events.GuildRoleCreate, once: false, 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); try { - const connection = databaseService.connectionFactory(role.guild.id, config.mongoose.dbURL); - await roleService.createRole(connection, { - roleId: role.id, - name: role.name, - color: role.color, - }); - await closeConnection(connection); + await roleService.handelRoleChanges(connection, role); } catch (err) { - // TODO: improve error handling - console.log(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 3101f8f8..40ec8f61 100644 --- a/src/events/role/roleDelete.ts +++ b/src/events/role/roleDelete.ts @@ -3,19 +3,25 @@ import { roleService } from '../../database/services'; import { databaseService } from '@togethercrew.dev/db'; import config from '../../config'; import { closeConnection } from '../../database/connection'; +import parentLogger from '../../config/logger'; + +const logger = parentLogger.child({ event: 'GuildRoleDelete' }); export default { name: Events.GuildRoleDelete, once: false, 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); try { - const connection = databaseService.connectionFactory(role.guild.id, config.mongoose.dbURL); const roleDoc = await roleService.getRole(connection, { roleId: role.id }); await roleDoc?.softDelete(); - await closeConnection(connection); } catch (err) { - // TODO: improve error handling - console.log(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 b14336c5..c62918d3 100644 --- a/src/events/role/roleUpdate.ts +++ b/src/events/role/roleUpdate.ts @@ -3,29 +3,24 @@ import { roleService } from '../../database/services'; import { databaseService } from '@togethercrew.dev/db'; import config from '../../config'; import { closeConnection } from '../../database/connection'; +import parentLogger from '../../config/logger'; + +const logger = parentLogger.child({ event: 'GuildRoleUpdate' }); export default { name: Events.GuildRoleUpdate, once: false, 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); try { - const connection = databaseService.connectionFactory(oldRole.guild.id, config.mongoose.dbURL); - const role = await roleService.updateRole( - connection, - { roleId: oldRole.id }, - { name: newRole.name, color: newRole.color } - ); - if (!role) { - await roleService.createRole(connection, { - roleId: newRole.id, - name: newRole.name, - color: newRole.color, - }); - } - await closeConnection(connection); + await roleService.handelRoleChanges(connection, newRole); } catch (err) { - // TODO: improve error handling - console.log(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 10c3fe43..aedb3c1e 100644 --- a/src/events/user/userUpdate.ts +++ b/src/events/user/userUpdate.ts @@ -3,11 +3,15 @@ import { guildMemberService, guildService } from '../../database/services'; import { databaseService } from '@togethercrew.dev/db'; import config from '../../config'; import { closeConnection } from '../../database/connection'; +import parentLogger from '../../config/logger'; +const logger = parentLogger.child({ event: 'UserUpdate' }); export default { name: Events.UserUpdate, once: false, async execute(oldUser: User, newUser: User) { + const logFields = { user_id: newUser.id }; + logger.info(logFields, 'event is running'); try { const guilds = await guildService.getGuilds({}); for (let i = 0; i < guilds.length; i++) { @@ -23,8 +27,8 @@ export default { await closeConnection(connection); } } catch (err) { - // TODO: improve error handling - console.log(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 ed373f36..9aa350e5 100644 --- a/src/functions/cronJon.ts +++ b/src/functions/cronJon.ts @@ -5,16 +5,21 @@ import { ChoreographyDict, MBConnection, Status } from '@togethercrew.dev/tc-mes import config from '../config'; import guildExtraction from './guildExtraction'; import { closeConnection } from '../database/connection'; +import parentLogger from '../config/logger'; + +const logger = parentLogger.child({ event: 'CronJob' }); async function createAndStartCronJobSaga(guildId: Snowflake) { - console.log('[createAndStartCronJobSaga]'); - const saga = await MBConnection.models.Saga.create({ - status: Status.NOT_STARTED, - data: { guildId }, - choreography: ChoreographyDict.DISCORD_SCHEDULED_JOB, - }); - console.log('[SAGA] ', saga); - await saga.start(); + try { + const saga = await MBConnection.models.Saga.create({ + status: Status.NOT_STARTED, + data: { guildId }, + choreography: ChoreographyDict.DISCORD_SCHEDULED_JOB, + }); + await saga.start(); + } catch (err) { + logger.error({ guild_Id: guildId, err }, 'Faield to create saga'); + } } /** @@ -22,17 +27,20 @@ async function createAndStartCronJobSaga(guildId: Snowflake) { * @param {Client} client - The discord.js client object used to fetch the guilds. */ export default async function cronJob(client: Client) { - try { - const guilds = await guildService.getGuilds({ isDisconnected: false }); - for (let i = 0; i < guilds.length; i++) { - console.log(`Cron JOB is running for ${guilds[i].guildId}:${guilds[i].name}`); - const connection = databaseService.connectionFactory(guilds[i].guildId, config.mongoose.dbURL); + 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); + try { + logger.info({ guild_id: guilds[i].guildId }, 'is running cronJob for guild'); await guildExtraction(connection, client, guilds[i].guildId); await createAndStartCronJobSaga(guilds[i].guildId); - console.log(`Cron JOB is Done ${guilds[i].guildId}:${guilds[i].name}`); + 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); } - } catch (err) { - console.log('Cron job failed', err); } + logger.info('event is done'); } diff --git a/src/functions/fetchChannels.ts b/src/functions/fetchChannels.ts index e120d7cb..4db591af 100644 --- a/src/functions/fetchChannels.ts +++ b/src/functions/fetchChannels.ts @@ -2,25 +2,9 @@ import { Client, Snowflake, TextChannel, VoiceChannel, CategoryChannel } from 'd import { Connection } from 'mongoose'; import { IChannel } from '@togethercrew.dev/db'; import { channelService, guildService } from '../database/services'; +import parentLogger from '../config/logger'; -/** - * Extracts necessary data from a given channel. - * @param {Array} channelArray - An array of channels from which data is to be extracted. - * @returns {IChannel} - The extracted data in the form of an IChannel object. - */ -function getNeedDataFromChannel(channel: TextChannel | VoiceChannel | CategoryChannel): IChannel { - return { - channelId: channel.id, - name: channel.name, // cast to TextChannel for 'name' - parentId: channel.parentId, - permissionOverwrites: Array.from(channel.permissionOverwrites.cache.values()).map(overwrite => ({ - id: overwrite.id, - type: overwrite.type, - allow: overwrite.allow.bitfield.toString(), - deny: overwrite.deny.bitfield.toString(), - })), - }; -} +const logger = parentLogger.child({ module: 'FetchChannels' }); /** * Iterates over a list of channels and pushes extracted data from each channel to an array. @@ -33,7 +17,7 @@ function pushChannelsToArray( channelArray: Array ): IChannel[] { for (const channel of channelArray) { - arr.push(getNeedDataFromChannel(channel)); + arr.push(channelService.getNeededDateFromChannel(channel)); } return arr; } @@ -45,7 +29,6 @@ function pushChannelsToArray( * @param {Snowflake} guildId - The identifier of the guild to extract text and voice channels from. */ export default async function fetchGuildChannels(connection: Connection, client: Client, guildId: Snowflake) { - console.log(`Fetching text and voice channels for guild: ${guildId}`); try { if (!client.guilds.cache.has(guildId)) { await guildService.updateGuild({ guildId }, { isDisconnected: false }); @@ -58,8 +41,7 @@ export default async function fetchGuildChannels(connection: Connection, client: ) as Array; pushChannelsToArray(channelsToStore, textAndVoiceChannels); await channelService.createChannels(connection, channelsToStore); // assuming a 'channelService' - } catch (err) { - console.error(`Failed to fetch text and voice channels of guild ${guildId}`, err); + } catch (error) { + logger.error({ guildId, error }, 'Failed to fetch channels'); } - console.log(`Completed fetching text and voice channels for guild: ${guildId}`); } diff --git a/src/functions/fetchMembers.ts b/src/functions/fetchMembers.ts index 1a42d0c7..424d9ab2 100644 --- a/src/functions/fetchMembers.ts +++ b/src/functions/fetchMembers.ts @@ -2,26 +2,9 @@ import { GuildMember, Client, Snowflake } from 'discord.js'; import { Connection } from 'mongoose'; import { IGuildMember } from '@togethercrew.dev/db'; import { guildMemberService, guildService } from '../database/services'; +import parentLogger from '../config/logger'; -/** - * Extracts necessary data from a given guild member. - * @param {IGuildMember} guildMember - The guild member object from which data is to be extracted. - * @returns {Promise} - A promise that resolves to an object of type IRawInfo containing the extracted data. - */ -function getNeedDataFromGuildMember(guildMember: GuildMember): IGuildMember { - return { - discordId: guildMember.user.id, - username: guildMember.user.username, - avatar: guildMember.user.avatar, - joinedAt: guildMember.joinedAt, - roles: guildMember.roles.cache.map(role => role.id), - isBot: guildMember.user.bot, - discriminator: guildMember.user.discriminator, - permissions: guildMember.permissions.bitfield.toString(), - nickname: guildMember.nickname, - globalName: guildMember.user.globalName - }; -} +const logger = parentLogger.child({ module: 'FetchMembers' }); /** * Iterates over a list of guild members and pushes extracted data from each guild member to an array. @@ -32,7 +15,7 @@ function getNeedDataFromGuildMember(guildMember: GuildMember): IGuildMember { */ function pushMembersToArray(arr: IGuildMember[], guildMembersArray: GuildMember[]): IGuildMember[] { for (const guildMember of guildMembersArray) { - arr.push(getNeedDataFromGuildMember(guildMember)); + arr.push(guildMemberService.getNeededDateFromGuildMember(guildMember)); } return arr; } @@ -44,7 +27,6 @@ function pushMembersToArray(arr: IGuildMember[], guildMembersArray: GuildMember[ * @param {Snowflake} guildId - The identifier of the guild to extract information from. */ export default async function fetchGuildMembers(connection: Connection, client: Client, guildId: Snowflake) { - console.log(`Fetching members for guild: ${guildId}`); try { if (!client.guilds.cache.has(guildId)) { await guildService.updateGuild({ guildId }, { isDisconnected: false }); @@ -55,8 +37,7 @@ export default async function fetchGuildMembers(connection: Connection, client: const fetchMembers = await guild.members.fetch(); pushMembersToArray(membersToStore, [...fetchMembers.values()]); await guildMemberService.createGuildMembers(connection, membersToStore); - } catch (err) { - console.error(`Failed to fetch members of guild ${guildId}`, err); + } catch (error) { + logger.error({ guildId, error }, 'Failed to fetch guild members'); } - console.log(`Completed fetching members for guild: ${guildId}`); } diff --git a/src/functions/fetchMessages.ts b/src/functions/fetchMessages.ts index 5b31f269..3f903d6e 100644 --- a/src/functions/fetchMessages.ts +++ b/src/functions/fetchMessages.ts @@ -2,7 +2,9 @@ import { Message, TextChannel, Collection, User, Role, ThreadChannel, Snowflake import { IRawInfo } from '@togethercrew.dev/db'; import { rawInfoService } from '../database/services'; import { Connection } from 'mongoose'; +import parentLogger from '../config/logger'; +const logger = parentLogger.child({ module: 'FetchMessages' }); interface threadInfo { threadId: Snowflake; threadName: string; @@ -37,7 +39,7 @@ async function getReactions(message: Message): Promise { return reactionsArr; } catch (err) { - console.log(err); + logger.error({ message, err }, 'Faild to get reactions'); return []; } } @@ -128,7 +130,10 @@ async function fetchMessages( fetchDirection: 'before' | 'after' = 'before' ) { try { - console.log(`fetch msgs is running for ${channel.name}: ${channel.id}`); + logger.info( + { guild_id: connection.name, channel_id: channel.id, fetchDirection }, + 'Fetching channel messages is running' + ); const messagesToStore: IRawInfo[] = []; const options: FetchOptions = { limit: 10 }; if (rawInfo) { @@ -166,10 +171,16 @@ async function fetchMessages( fetchedMessages = await channel.messages.fetch(options); } await rawInfoService.createRawInfos(connection, messagesToStore); - console.log(`fetch msgs is done for ${channel.name}: ${channel.id}`); } catch (err) { - console.log(`Failed to fetchMessages of channle: ${channel.id} `, err); + logger.error( + { guild_id: connection.name, channel_id: channel.id, fetchDirection, err }, + 'Fetching channel messages failed' + ); } + logger.info( + { guild_id: connection.name, channel_id: channel.id, fetchDirection }, + 'Fetching channel messages is done' + ); } /** @@ -179,9 +190,9 @@ async function fetchMessages( * @param {Date} period - A date object specifying the oldest date for the messages to be fetched. * @throws Will throw an error if an issue is encountered during processing. */ -export default async function fetchChannelMessages(connection: Connection, channel: TextChannel, period: Date) { +export default async function handleFetchChannelMessages(connection: Connection, channel: TextChannel, period: Date) { + logger.info({ guild_id: connection.name, channel_id: channel.id }, 'Handle channel messages for channel is running'); try { - console.log(`fetch channel messages is running for ${channel.name}: ${channel.id} : ${channel.type}`); const oldestChannelRawInfo = await rawInfoService.getOldestRawInfo(connection, { channelId: channel?.id, threadId: null, @@ -225,9 +236,8 @@ export default async function fetchChannelMessages(connection: Connection, chann await fetchMessages(connection, thread, undefined, period, 'before'); } } - console.log(`fetch channel messages is done for ${channel.name}: ${channel.id} : ${channel.type}`); - console.log('###################################'); } catch (err) { - console.log(`Failed to fetchChannelMessages of channle: ${channel.id} `, err); + logger.error({ guild_id: connection.name, channel_id: channel.id, err }, 'Handle fetch channel messages failed'); } + logger.info({ guild_id: connection.name, channel_id: channel.id }, 'Handle fetch channel messages is done'); } diff --git a/src/functions/fetchRoles.ts b/src/functions/fetchRoles.ts index d48b0f03..113050fc 100644 --- a/src/functions/fetchRoles.ts +++ b/src/functions/fetchRoles.ts @@ -2,19 +2,9 @@ import { Client, Snowflake, Role } from 'discord.js'; import { Connection } from 'mongoose'; import { IRole } from '@togethercrew.dev/db'; import { roleService, guildService } from '../database/services'; +import parentLogger from '../config/logger'; -/** - * Extracts necessary data from a given role. - * @param {Role} role - The discord.js role object from which data is to be extracted. - * @returns {IRole} - The extracted data in the form of an IRole object. - */ -function getNeedDataFromRole(role: Role): IRole { - return { - roleId: role.id, - name: role.name, - color: role.color, - }; -} +const logger = parentLogger.child({ module: 'FetchRoles' }); /** * Iterates over a list of roles and pushes extracted data from each role to an array. @@ -24,7 +14,7 @@ function getNeedDataFromRole(role: Role): IRole { */ function pushRolesToArray(arr: IRole[], roleArray: Role[]): IRole[] { for (const role of roleArray) { - arr.push(getNeedDataFromRole(role)); + arr.push(roleService.getNeededDateFromRole(role)); } return arr; } @@ -36,7 +26,6 @@ function pushRolesToArray(arr: IRole[], roleArray: Role[]): IRole[] { * @param {Snowflake} guildId - The identifier of the guild to extract roles from. */ export default async function fetchGuildRoles(connection: Connection, client: Client, guildId: Snowflake) { - console.log(`Fetching roles for guild: ${guildId}`); try { if (!client.guilds.cache.has(guildId)) { await guildService.updateGuild({ guildId }, { isDisconnected: false }); @@ -46,8 +35,7 @@ export default async function fetchGuildRoles(connection: Connection, client: Cl const rolesToStore: IRole[] = []; pushRolesToArray(rolesToStore, [...guild.roles.cache.values()]); await roleService.createRoles(connection, rolesToStore); // assuming a 'roleService' - } catch (err) { - console.error(`Failed to fetch roles of guild ${guildId}`, err); + } catch (error) { + logger.error({ guildId, error }, 'Failed to fetch roles'); } - console.log(`Completed fetching roles for guild: ${guildId}`); } diff --git a/src/functions/guildExtraction.ts b/src/functions/guildExtraction.ts index 04962bb9..79eb31ff 100644 --- a/src/functions/guildExtraction.ts +++ b/src/functions/guildExtraction.ts @@ -1,8 +1,10 @@ -import { Client, TextChannel, Snowflake } from 'discord.js'; +import { Client, Snowflake } from 'discord.js'; import { guildService } from '../database/services'; -import fetchChannelMessages from './fetchMessages'; +import handleFetchChannelMessages from './fetchMessages'; import { Connection } from 'mongoose'; +import parentLogger from '../config/logger'; +const logger = parentLogger.child({ module: 'GuildExtraction' }); /** * Extracts information from a given guild. * @param {Connection} connection - Mongoose connection object for the database. @@ -10,10 +12,10 @@ import { Connection } from 'mongoose'; * @param {Snowflake} guildId - The identifier of the guild to extract information from. */ export default async function guildExtraction(connection: Connection, client: Client, guildId: Snowflake) { + logger.info({ guild_id: guildId }, 'Guild extraction for guild is running'); try { - console.log(`********guild Extraction is running for ${guildId}********`); - if (!client.guilds.cache.has(guildId)) { - await guildService.updateGuild({ guildId }, { isDisconnected: false }); + const hasBotAccessToGuild = await guildService.checkBotAccessToGuild(client, guildId); + if (!hasBotAccessToGuild) { return; } const guild = await client.guilds.fetch(guildId); @@ -22,40 +24,15 @@ export default async function guildExtraction(connection: Connection, client: Cl await guildService.updateGuild({ guildId }, { isInProgress: true }); const selectedChannelsIds = guildDoc.selectedChannels.map(selectedChannel => selectedChannel.channelId); for (const channelId of selectedChannelsIds) { - const channel = (await guild.channels.fetch(channelId)) as TextChannel; - if (channel.type !== 0) { - continue; + const channel = await guild.channels.fetch(channelId); + if (channel) { + if (channel.type !== 0) continue; + await handleFetchChannelMessages(connection, channel, guildDoc?.period); } - await fetchChannelMessages(connection, channel, guildDoc?.period); } } - console.log(`********guild Extraction is done for ${guildId}********`); - console.log('###################################'); } catch (err) { - console.log(err); + logger.error({ guild_id: guildId, err }, 'Guild extraction CronJob failed for guild'); } + logger.info({ guild_id: guildId }, 'Guild extraction for guild is done'); } - -// GUILD : 980858613587382322 -// Channel name: Text Channels, ID: 980858613587382323 -// Channel name: Voice Channels, ID: 980858613587382324 -// Channel name: general, ID: 980858613587382325 -// Channel name: General, ID: 980858613587382326 -// Channel name: test, ID: 1029501237554581564 -// Channel name: special-channel-💪, ID: 1045029797346160741 -// Channel name: smalltest, ID: 1045807353729134592 -// Channel name: sss, ID: 1050520586692075533 -// Channel name: 🏁start-here, ID: 1050530657253736578 -// Channel name: ✅introductions, ID: 1050531295765201086 -// Channel name: 📘directory, ID: 1050531395191181352 -// Channel name: support, ID: 1070322209811349554 -// Channel name: new-channel-1, ID: 1105164023009386596 -// Channel name: new-category, ID: 1105752192629088267 -// Channel name: c1, ID: 1105752303820083221 -// Channel name: c2-voice, ID: 1105752336124612719 -// Channel name: c3-private, ID: 1105752380026392576 -// Channel name: tag, ID: 1108405617846128734 -// Channel name: c4, ID: 1109052576978173982 -// Channel name: do-not-spam-here, ID: 1109369850276610048 -// Channel name: do-not-spam, ID: 1109421233436635198 -// Channel name: test1, ID: 1110556724844310568 diff --git a/src/index.ts b/src/index.ts index cecc77d9..66978504 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,9 @@ 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'; + +const logger = parentLogger.child({ module: 'App' }); Sentry.init({ dsn: config.sentry.dsn, @@ -38,22 +41,23 @@ const partial = func(...args, ...rest); const fetchMethod = async (msg: any) => { - console.log(`Starting fetch initial with: ${msg}`); + logger.info({ msg }, 'fetchMethod is running'); if (!msg) return; - const { content } = msg; const saga = await MBConnection.models.Saga.findOne({ sagaId: content.uuid }); + 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); - if (isGuildCreated) { await fetchMembers(connection, client, guildId); + await fetchRoles(connection, client, guildId); + await fetchChannels(connection, client, guildId); } else { await guildExtraction(connection, client, guildId); } await closeConnection(connection); - console.log(`Finished fetch initial data.`); + logger.info({ msg }, 'fetchMethod is done'); }; const notifyUserAboutAnalysisFinish = async ( @@ -99,14 +103,25 @@ async function app() { await loadEvents(client); await client.login(config.discord.botToken); await connectDB(); - // *****************************RABBITMQ - await MBConnection.connect(config.mongoose.dbURL); - await RabbitMQ.connect(config.rabbitMQ.url, RabbitMQQueue.DISCORD_BOT).then(() => { - console.log('Connected to RabbitMQ!'); - }); + try { + await MBConnection.connect(config.mongoose.dbURL); + } catch (error) { + logger.error({ url: config.mongoose.dbURL, error }, 'Failed to connect to MongoDB!'); + } + await RabbitMQ.connect(config.rabbitMQ.url, RabbitMQQueue.DISCORD_BOT) + .then(() => { + logger.info({ url: config.rabbitMQ.url, queue: RabbitMQQueue.DISCORD_BOT }, 'Connected to RabbitMQ!'); + }) + .catch(error => + logger.error( + { url: config.rabbitMQ.url, queue: RabbitMQQueue.DISCORD_BOT, error }, + 'Failed to connect to RabbitMQ!' + ) + ); + RabbitMQ.onEvent(Event.DISCORD_BOT.FETCH, async msg => { - console.log(`Received ${Event.DISCORD_BOT.FETCH} event with msg: ${msg}`); + logger.info({ msg, event: Event.DISCORD_BOT.FETCH }, 'is running'); if (!msg) return; const { content } = msg; @@ -114,11 +129,11 @@ async function app() { const fn = partial(fetchMethod, msg); await saga.next(fn); - console.log(`Finished ${Event.DISCORD_BOT.FETCH} event with msg: ${msg}`); + logger.info({ msg, event: Event.DISCORD_BOT.FETCH }, 'is done'); }); RabbitMQ.onEvent(Event.DISCORD_BOT.SEND_MESSAGE, async msg => { - console.log(`Received ${Event.DISCORD_BOT.SEND_MESSAGE} event with msg: ${msg}`); + logger.info({ msg, event: Event.DISCORD_BOT.SEND_MESSAGE }, 'is running'); if (!msg) return; const { content } = msg; @@ -131,11 +146,11 @@ async function app() { const fn = notifyUserAboutAnalysisFinish.bind({}, discordId, { guildId, message, useFallback }); await saga.next(fn); - console.log(`Finished ${Event.DISCORD_BOT.SEND_MESSAGE} event with msg: ${msg}`); + logger.info({ msg, event: Event.DISCORD_BOT.SEND_MESSAGE }, 'is done'); }); RabbitMQ.onEvent(Event.DISCORD_BOT.FETCH_MEMBERS, async msg => { - console.log(`Received ${Event.DISCORD_BOT.FETCH_MEMBERS} event with msg: ${msg}`); + logger.info({ msg, event: Event.DISCORD_BOT.FETCH_MEMBERS }, 'is running'); if (!msg) return; const { content } = msg; @@ -145,7 +160,7 @@ async function app() { const fn = fetchInitialData.bind({}, guildId); await saga.next(fn); - console.log(`Finished ${Event.DISCORD_BOT.FETCH_MEMBERS} event with msg: ${msg}`); + logger.info({ msg, event: Event.DISCORD_BOT.FETCH_MEMBERS }, 'is done'); }); // *****************************BULLMQ @@ -192,11 +207,11 @@ async function app() { // Listen for completed and failed events to log the job status worker.on('completed', job => { - console.log(`Job ${job?.id} completed successfully.`); + logger.info({ job }, 'Job is done'); }); worker.on('failed', (job, error) => { - console.error(`Job ${job?.id} failed with error:`, error); + logger.error({ job, error }, 'Job failed'); }); } app();