import async from "async"; import mongoose from "mongoose"; import CoreClass from "../core"; let PunishmentsModule; let CacheModule; let DBModule; let UtilsModule; let WSModule; class _PunishmentsModule extends CoreClass { // eslint-disable-next-line require-jsdoc constructor() { super("punishments"); PunishmentsModule = this; } /** * Initialises the punishments module * * @returns {Promise} - returns promise (reject, resolve) */ async initialize() { this.setStage(1); CacheModule = this.moduleManager.modules.cache; DBModule = this.moduleManager.modules.db; UtilsModule = this.moduleManager.modules.utils; WSModule = this.moduleManager.modules.ws; this.punishmentModel = this.PunishmentModel = await DBModule.runJob("GET_MODEL", { modelName: "punishment" }); this.punishmentSchemaCache = await CacheModule.runJob("GET_SCHEMA", { schemaName: "punishment" }); return new Promise((resolve, reject) => { async.waterfall( [ next => { this.setStage(2); CacheModule.runJob("HGETALL", { table: "punishments" }) .then(punishments => { next(null, punishments); }) .catch(next); }, (punishments, next) => { this.setStage(3); if (!punishments) return next(); const punishmentIds = Object.keys(punishments); return async.each( punishmentIds, (punishmentId, cb) => { PunishmentsModule.punishmentModel.findOne({ _id: punishmentId }, (err, punishment) => { if (err) next(err); else if (!punishment) CacheModule.runJob("HDEL", { table: "punishments", key: punishmentId }) .then(() => cb()) .catch(next); else cb(); }); }, next ); }, next => { this.setStage(4); PunishmentsModule.punishmentModel.find({}, next); }, (punishments, next) => { this.setStage(5); async.each( punishments, (punishment, next) => { if (punishment.active === false || punishment.expiresAt < Date.now()) return next(); return CacheModule.runJob("HSET", { table: "punishments", key: punishment._id, value: PunishmentsModule.punishmentSchemaCache(punishment, punishment._id) }) .then(() => next()) .catch(next); }, next ); } ], async err => { if (err) { const formattedErr = await UtilsModule.runJob("GET_ERROR", { error: err }); reject(new Error(formattedErr)); } else resolve(); } ); }); } /** * Gets all punishments in the cache that are active, and removes those that have expired * * @returns {Promise} - returns promise (reject, resolve) */ GET_PUNISHMENTS() { return new Promise((resolve, reject) => { const punishmentsToRemove = []; async.waterfall( [ next => { CacheModule.runJob("HGETALL", { table: "punishments" }, this) .then(punishmentsObj => next(null, punishmentsObj)) .catch(next); }, (punishmentsObj, next) => { const punishments = Object.keys(punishmentsObj).map(punishmentKey => { const punishment = punishmentsObj[punishmentKey]; punishment.punishmentId = punishmentKey; return punishment; }); const filteredPunishments = punishments.filter(punishment => { if (punishment.expiresAt < Date.now()) punishmentsToRemove.push(punishment); return punishment.expiresAt > Date.now(); }); next(null, filteredPunishments); }, (punishments, next) => { async.each( punishmentsToRemove, (punishment, next2) => { CacheModule.runJob( "HDEL", { table: "punishments", key: punishment.punishmentId }, this ).finally(() => { WSModule.runJob( "EMIT_TO_ROOMS", { rooms: [`admin.punishments`, `view-punishment.${punishment.punishmentId}`], args: [ "event:admin.punishment.updated", { data: { punishment: { ...punishment, status: "Inactive" } } } ] }, this ).finally(() => next2()); }); }, () => { next(null, punishments); } ); } ], (err, punishments) => { if (err && err !== true) return reject(new Error(err)); return resolve(punishments); } ); }); } /** * Gets a punishment by id * * @param {object} payload - object containing the payload * @param {string} payload.id - the id of the punishment we are trying to get * @returns {Promise} - returns promise (reject, resolve) */ GET_PUNISHMENT(payload) { return new Promise((resolve, reject) => { async.waterfall( [ next => { if (!mongoose.Types.ObjectId.isValid(payload.id)) return next("Id is not a valid ObjectId."); return CacheModule.runJob( "HGET", { table: "punishments", key: payload.id }, this ) .then(punishment => next(null, punishment)) .catch(next); }, (punishment, next) => { if (punishment) return next(true, punishment); return PunishmentsModule.punishmentModel.findOne({ _id: payload.id }, next); }, (punishment, next) => { if (punishment) { CacheModule.runJob( "HSET", { table: "punishments", key: payload.id, value: punishment }, this ) .then(punishment => { next(null, punishment); }) .catch(next); } else next("Punishment not found."); } ], (err, punishment) => { if (err && err !== true) return reject(new Error(err)); return resolve(punishment); } ); }); } /** * Gets all punishments from a userId * * @param {object} payload - object containing the payload * @param {string} payload.userId - the userId of the punishment(s) we are trying to get * @returns {Promise} - returns promise (reject, resolve) */ GET_PUNISHMENTS_FROM_USER_ID(payload) { return new Promise((resolve, reject) => { async.waterfall( [ next => { PunishmentsModule.runJob("GET_PUNISHMENTS", {}, this) .then(punishments => { next(null, punishments); }) .catch(next); }, (punishments, next) => { const filteredPunishments = punishments.filter( punishment => punishment.type === "banUserId" && punishment.value === payload.userId ); next(null, filteredPunishments); } ], (err, punishments) => { if (err && err !== true) return reject(new Error(err)); return resolve(punishments); } ); }); } /** * Adds a new punishment to the database * * @param {object} payload - object containing the payload * @param {string} payload.reason - the reason for the punishment e.g. spam * @param {string} payload.type - the type of punishment (enum: ["banUserId", "banUserIp"]) * @param {string} payload.value - the user id/ip address for the ban (depends on punishment type) * @param {Date} payload.expiresAt - the date at which the punishment expires at * @param {string} payload.punishedBy - the userId of the who initiated the punishment * @returns {Promise} - returns promise (reject, resolve) */ ADD_PUNISHMENT(payload) { return new Promise((resolve, reject) => { async.waterfall( [ next => { const punishment = new PunishmentsModule.PunishmentModel({ type: payload.type, value: payload.value, reason: payload.reason, active: true, expiresAt: payload.expiresAt, punishedAt: Date.now(), punishedBy: payload.punishedBy }); punishment.save((err, punishment) => { if (err) return next(err); return next(null, punishment); }); }, (punishment, next) => { CacheModule.runJob( "HSET", { table: "punishments", key: punishment._id, value: PunishmentsModule.punishmentSchemaCache(punishment, punishment._id) }, this ) .then(() => next(null, punishment)) .catch(next); } ], (err, punishment) => { if (err) return reject(new Error(err)); return resolve(punishment); } ); }); } /** * Deactivates a punishment * * @param {object} payload - object containing the payload * @param {string} payload.punishmentId - the MongoDB id of the punishment * @returns {Promise} - returns promise (reject, resolve) */ DEACTIVATE_PUNISHMENT(payload) { return new Promise((resolve, reject) => { async.waterfall( [ next => { PunishmentsModule.punishmentModel.findOne({ _id: payload.punishmentId }, next); }, (punishment, next) => { if (!punishment) next("Punishment does not exist."); else PunishmentsModule.punishmentModel.updateOne( { _id: payload.punishmentId }, { $set: { active: false } }, next ); }, (res, next) => { CacheModule.runJob( "HDEL", { table: "punishments", key: payload.punishmentId }, this ) .then(() => next()) .catch(next); }, next => { PunishmentsModule.punishmentModel.findOne({ _id: payload.punishmentId }, next); } ], (err, punishment) => { if (err) return reject(new Error(err)); return resolve(punishment); } ); }); } } export default new _PunishmentsModule();