import async from "async"; import { isAdminRequired, isLoginRequired } from "./hooks"; import moduleManager from "../../index"; const DBModule = moduleManager.modules.db; const UtilsModule = moduleManager.modules.utils; const WSModule = moduleManager.modules.ws; const SongsModule = moduleManager.modules.songs; const CacheModule = moduleManager.modules.cache; const ActivitiesModule = moduleManager.modules.activities; const reportableIssues = [ { name: "Video", reasons: ["Doesn't exist", "It's private", "It's not available in my country"] }, { name: "Title", reasons: ["Incorrect", "Inappropriate"] }, { name: "Duration", reasons: ["Skips too soon", "Skips too late", "Starts too soon", "Skips too late"] }, { name: "Artists", reasons: ["Incorrect", "Inappropriate"] }, { name: "Thumbnail", reasons: ["Incorrect", "Inappropriate", "Doesn't exist"] } ]; CacheModule.runJob("SUB", { channel: "report.resolve", cb: reportId => { WSModule.runJob("EMIT_TO_ROOM", { room: "admin.reports", args: ["event:admin.report.resolved", { data: { reportId } }] }); } }); CacheModule.runJob("SUB", { channel: "report.create", cb: report => { WSModule.runJob("EMIT_TO_ROOM", { room: "admin.reports", args: ["event:admin.report.created", { data: { report } }] }); } }); export default { /** * Gets all reports * * @param {object} session - the session object automatically added by the websocket * @param {Function} cb - gets called with the result */ index: isAdminRequired(async function index(session, cb) { const reportModel = await DBModule.runJob( "GET_MODEL", { modelName: "report" }, this ); async.waterfall( [ next => { reportModel.find({ resolved: false }).sort({ released: "desc" }).exec(next); } ], async (err, reports) => { if (err) { err = await UtilsModule.runJob("GET_ERROR", { error: err }, this); this.log("ERROR", "REPORTS_INDEX", `Indexing reports failed. "${err}"`); return cb({ status: "error", message: err }); } this.log("SUCCESS", "REPORTS_INDEX", "Indexing reports successful."); return cb({ status: "success", data: { reports } }); } ); }), /** * Gets a specific report * * @param {object} session - the session object automatically added by the websocket * @param {string} reportId - the id of the report to return * @param {Function} cb - gets called with the result */ findOne: isAdminRequired(async function findOne(session, reportId, cb) { const reportModel = await DBModule.runJob( "GET_MODEL", { modelName: "report" }, this ); async.waterfall( [ next => { reportModel.findOne({ _id: reportId }).exec(next); } ], async (err, report) => { if (err) { err = await UtilsModule.runJob("GET_ERROR", { error: err }, this); this.log("ERROR", "REPORTS_FIND_ONE", `Finding report "${reportId}" failed. "${err}"`); return cb({ status: "error", message: err }); } this.log("SUCCESS", "REPORTS_FIND_ONE", `Finding report "${reportId}" successful.`); return cb({ status: "success", data: { report } }); } ); }), /** * Gets all reports for a songId * * @param {object} session - the session object automatically added by the websocket * @param {string} songId - the id of the song to index reports for * @param {Function} cb - gets called with the result */ getReportsForSong: isAdminRequired(async function getReportsForSong(session, songId, cb) { const reportModel = await DBModule.runJob( "GET_MODEL", { modelName: "report" }, this ); async.waterfall( [ next => { reportModel .find({ song: { _id: songId }, resolved: false }) .sort({ released: "desc" }) .exec(next); }, (_reports, next) => { const reports = []; for (let i = 0; i < _reports.length; i += 1) { data.push(_reports[i]._id); } next(null, reports); } ], async (err, reports) => { if (err) { err = await UtilsModule.runJob("GET_ERROR", { error: err }, this); this.log("ERROR", "GET_REPORTS_FOR_SONG", `Indexing reports for song "${songId}" failed. "${err}"`); return cb({ status: "error", message: err }); } this.log("SUCCESS", "GET_REPORTS_FOR_SONG", `Indexing reports for song "${songId}" successful.`); return cb({ status: "success", data: { reports } }); } ); }), /** * Resolves a report * * @param {object} session - the session object automatically added by the websocket * @param {string} reportId - the id of the report that is getting resolved * @param {Function} cb - gets called with the result */ resolve: isAdminRequired(async function resolve(session, reportId, cb) { const reportModel = await DBModule.runJob( "GET_MODEL", { modelName: "report" }, this ); async.waterfall( [ next => { reportModel.findOne({ _id: reportId }).exec(next); }, (report, next) => { if (!report) return next("Report not found."); report.resolved = true; return report.save(err => { if (err) return next(err.message); return next(); }); } ], async err => { if (err) { err = await UtilsModule.runJob("GET_ERROR", { error: err }, this); this.log( "ERROR", "REPORTS_RESOLVE", `Resolving report "${reportId}" failed by user "${session.userId}". "${err}"` ); return cb({ status: "error", message: err }); } CacheModule.runJob("PUB", { channel: "report.resolve", value: reportId }); this.log("SUCCESS", "REPORTS_RESOLVE", `User "${session.userId}" resolved report "${reportId}".`); return cb({ status: "success", message: "Successfully resolved Report" }); } ); }), /** * Creates a new report * * @param {object} session - the session object automatically added by the websocket * @param {object} data - the object of the report data * @param {Function} cb - gets called with the result */ create: isLoginRequired(async function create(session, data, cb) { const reportModel = await DBModule.runJob("GET_MODEL", { modelName: "report" }, this); const songModel = await DBModule.runJob("GET_MODEL", { modelName: "song" }, this); async.waterfall( [ next => { songModel.findOne({ youtubeId: data.youtubeId }).exec(next); }, (song, next) => { if (!song) return next("Song not found."); return SongsModule.runJob("GET_SONG", { songId: song._id }, this) .then(res => next(null, res.song)) .catch(next); }, (song, next) => { if (!song) return next("Song not found."); delete data.youtubeId; data.song = { _id: song._id, youtubeId: song.youtubeId }; for (let z = 0; z < data.issues.length; z += 1) { if (reportableIssues.filter(issue => issue.name === data.issues[z].name).length > 0) { for (let r = 0; r < reportableIssues.length; r += 1) { if ( reportableIssues[r].reasons.every( reason => data.issues[z].reasons.indexOf(reason) < -1 ) ) { return cb({ status: "error", message: "Invalid data" }); } } } else return cb({ status: "error", message: "Invalid data" }); } return next(null, { title: song.title, artists: song.artists, thumbnail: song.thumbnail }); }, (song, next) => { const issues = []; for (let r = 0; r < data.issues.length; r += 1) { if (!data.issues[r].reasons.length <= 0) issues.push(data.issues[r]); } data.issues = issues; next(null, song); }, (song, next) => { data.createdBy = session.userId; data.createdAt = Date.now(); reportModel.create(data, (err, report) => next(err, report, song)); } ], async (err, report, song) => { if (err) { err = await UtilsModule.runJob("GET_ERROR", { error: err }, this); this.log( "ERROR", "REPORTS_CREATE", `Creating report for "${data.song._id}" failed by user "${session.userId}". "${err}"` ); return cb({ status: "error", message: err }); } CacheModule.runJob("PUB", { channel: "report.create", value: report }); ActivitiesModule.runJob("ADD_ACTIVITY", { userId: report.createdBy, type: "song__report", payload: { message: `Reported song ${song.title} by ${song.artists.join(", ")}`, youtubeId: data.song.youtubeId, thumbnail: song.thumbnail } }); this.log( "SUCCESS", "REPORTS_CREATE", `User "${session.userId}" created report for "${data.youtubeId}".` ); return cb({ status: "success", message: "Successfully created report" }); } ); }) };