Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge Development into the Main #128

Merged
merged 14 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# env vars
src/config/*.env
*.env

# compiled output
/lib
/dist
/node_modules

#migration
migrate.json
.migrate
# Logs
logs
*.log
Expand Down
2,940 changes: 1,762 additions & 1,178 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
"test:ci": "jest --ci --detectOpenHandles",
"lint": "eslint **/*.ts",
"lint-fix": "eslint --fix **/*.ts",
"format": "prettier --write src/**/*.ts"
"format": "prettier --write src/**/*.ts",
"migrate:create": "migrate create --template-file ./src/migrations/utils/template.ts --migrations-dir=\"./src/migrations/db\"",
"migrate:up": "migrate --migrations-dir=\"./lib/migrations/db\" up",
"migrate:down": "migrate --migrations-dir=\"./lib/migrations/db\" down"
},
"repository": {
"type": "git",
Expand All @@ -26,16 +29,18 @@
"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.5.1",
"@togethercrew.dev/tc-messagebroker": "^0.0.40",
"babel-jest": "^29.5.0",
"bullmq": "^3.14.0",
"discord.js": "^14.12.1",
"joi": "^17.9.2",
"migrate": "^2.0.0",
"moment": "^2.29.4",
"mongodb": "^5.4.0",
"mongoose": "^6.11.1",
"node-fetch": "^2.6.7",
"pino": "^8.15.0",
"redis": "^4.6.6"
},
"devDependencies": {
Expand All @@ -62,4 +67,4 @@
"ts-node": "^10.9.1",
"typescript": "^5.0.4"
}
}
}
4 changes: 4 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -51,4 +52,7 @@ export default {
dsn: envVars.SENTRY_DSN,
env: envVars.SENTRY_ENV,
},
logger: {
level: envVars.LOG_LEVEL,
},
};
18 changes: 18 additions & 0 deletions src/config/logger.ts
Original file line number Diff line number Diff line change
@@ -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,
};
},
});
10 changes: 7 additions & 3 deletions src/database/connection.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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.fatal({ database: connection.name, error }, 'Failed to close the connection to the database');
}
}
11 changes: 7 additions & 4 deletions src/database/index.ts
Original file line number Diff line number Diff line change
@@ -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!'));
}
95 changes: 71 additions & 24 deletions src/database/services/channel.service.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -10,8 +14,13 @@ import { IChannel, IChannelMethods, IChannelUpdateBody } from '@togethercrew.dev
async function createChannel(connection: Connection, channel: IChannel): Promise<IChannel | null> {
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;
}
}
Expand All @@ -25,8 +34,13 @@ async function createChannel(connection: Connection, channel: IChannel): Promise
async function createChannels(connection: Connection, channels: IChannel[]): Promise<IChannel[] | []> {
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 [];
}
}
Expand All @@ -38,12 +52,7 @@ async function createChannels(connection: Connection, channels: IChannel[]): Pro
* @returns {Promise<IChannel | null>} - 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);
}

/**
Expand All @@ -53,35 +62,30 @@ async function getChannel(connection: Connection, filter: object): Promise<(ICha
* @returns {Promise<IChannel[] | []>} - A promise that resolves to an array of the matching channel objects.
*/
async function getChannels(connection: Connection, filter: object): Promise<IChannel[] | []> {
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<IChannel | null>} - 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<IChannel | null> {
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;
}
}
Expand All @@ -90,24 +94,67 @@ 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<number>} - A promise that resolves to the number of updated channel entries.
*/
async function updateChannels(connection: Connection, filter: object, UpdateBody: IChannelUpdateBody): Promise<number> {
async function updateChannels(connection: Connection, filter: object, updateBody: IChannelUpdateBody): Promise<number> {
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<void>} - A promise that resolves when the create or update operation is complete.
*
*/
async function handelChannelChanges(
connection: Connection,
channel: TextChannel | VoiceChannel | CategoryChannel
): Promise<void> {
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,
updateChannel,
getChannel,
getChannels,
updateChannels,
handelChannelChanges,
getNeededDateFromChannel,
};
36 changes: 22 additions & 14 deletions src/database/services/guild.service.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -15,54 +19,58 @@ async function getGuild(filter: object): Promise<IGuild | null> {
* @returns {Promise<object[]>} - A promise that resolves to an array of matching guild entries.
*/
async function getGuilds(filter: object): Promise<IGuild[]> {
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<object|null>} - A promise that resolves to the updated guild entry, or null if not found.
*/
async function updateGuild(filter: object, UpdateBody: IGuildUpdateBody): Promise<IGuild | null> {
async function updateGuild(filter: object, updateBody: IGuildUpdateBody): Promise<IGuild | null> {
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;
}
}

/**
* 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<number>} - A promise that resolves to the number of guild entries updated.
*/
async function updateManyGuilds(filter: object, UpdateBody: IGuildUpdateBody): Promise<number> {
async function updateManyGuilds(filter: object, updateBody: IGuildUpdateBody): Promise<number> {
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,
};
Loading
Loading