const CoreClass = require("../core.js"); const async = require("async"); const mongoose = require("mongoose"); class SongsModule extends CoreClass { constructor() { super("songs"); } initialize() { return new Promise(async (resolve, reject) => { this.setStage(1); this.cache = this.moduleManager.modules["cache"]; this.db = this.moduleManager.modules["db"]; this.io = this.moduleManager.modules["io"]; this.utils = this.moduleManager.modules["utils"]; const songModel = await this.db.runJob("GET_MODEL", { modelName: "song", }); const songSchema = await this.cache.runJob("GET_SCHEMA", { schemaName: "song", }); async.waterfall( [ (next) => { this.setStage(2); this.cache .runJob("HGETALL", { table: "songs" }) .then((songs) => next(null, songs)) .catch(next); }, (songs, next) => { this.setStage(3); if (!songs) return next(); let songIds = Object.keys(songs); async.each( songIds, (songId, next) => { songModel.findOne({ songId }, (err, song) => { if (err) next(err); else if (!song) this.cache .runJob("HDEL", { table: "songs", key: songId, }) .then(() => next()) .catch(next); else next(); }); }, next ); }, (next) => { this.setStage(4); songModel.find({}, next); }, (songs, next) => { this.setStage(5); async.each( songs, (song, next) => { this.cache .runJob("HSET", { table: "songs", key: song.songId, value: songSchema(song), }) .then(() => next()) .catch(next); }, next ); }, ], async (err) => { if (err) { err = await this.utils.runJob("GET_ERROR", { error: err, }); reject(new Error(err)); } else { resolve(); } } ); }); } /** * Gets a song by id from the cache or Mongo, and if it isn't in the cache yet, adds it the cache * * @param {String} id - the id of the song we are trying to get * @param {Function} cb - gets called once we're done initializing */ GET_SONG(payload) { //id, cb return new Promise(async (resolve, reject) => { const songModel = await this.db.runJob("GET_MODEL", { modelName: "song", }); async.waterfall( [ (next) => { if (!mongoose.Types.ObjectId.isValid(payload.id)) return next("Id is not a valid ObjectId."); this.cache .runJob("HGET", { table: "songs", key: payload.id }) .then((song) => next(null, song)) .catch(next); }, (song, next) => { if (song) return next(true, song); songModel.findOne({ _id: payload.id }, next); }, (song, next) => { if (song) { this.cache .runJob("HSET", { table: "songs", key: payload.id, value: song, }) .then((song) => next(null, song)); } else next("Song not found."); }, ], (err, song) => { if (err && err !== true) return reject(new Error(err)); resolve({ song }); } ); }); } /** * Gets a song by song id from the cache or Mongo, and if it isn't in the cache yet, adds it the cache * * @param {String} songId - the mongo id of the song we are trying to get * @param {Function} cb - gets called once we're done initializing */ GET_SONG_FROM_ID(payload) { //songId, cb return new Promise(async (resolve, reject) => { const songModel = await this.db.runJob("GET_MODEL", { modelName: "song", }); async.waterfall( [ (next) => { songModel.findOne({ songId: payload.songId }, next); }, ], (err, song) => { if (err && err !== true) return reject(new Error(err)); resolve({ song }); } ); }); } /** * Gets a song from id from Mongo and updates the cache with it * * @param {String} songId - the id of the song we are trying to update * @param {Function} cb - gets called when an error occurred or when the operation was successful */ UPDATE_SONG(payload) { //songId, cb return new Promise(async (resolve, reject) => { const songModel = await this.db.runJob("GET_MODEL", { modelName: "song", }); async.waterfall( [ (next) => { songModel.findOne({ _id: payload.songId }, next); }, (song, next) => { if (!song) { this.cache.runJob("HDEL", { table: "songs", key: payload.songId, }); return next("Song not found."); } this.cache .runJob("HSET", { table: "songs", key: payload.songId, value: song, }) .then((song) => next(null, song)) .catch(next); }, ], (err, song) => { if (err && err !== true) return reject(new Error(err)); resolve(song); } ); }); } /** * Deletes song from id from Mongo and cache * * @param {String} songId - the id of the song we are trying to delete * @param {Function} cb - gets called when an error occurred or when the operation was successful */ DELETE_SONG(payload) { //songId, cb return new Promise(async (resolve, reject) => { const songModel = await this.db.runJob("GET_MODEL", { modelName: "song", }); async.waterfall( [ (next) => { songModel.deleteOne({ songId: payload.songId }, next); }, (next) => { this.cache .runJob("HDEL", { table: "songs", key: payload.songId, }) .then(() => next()) .catch(next); }, ], (err) => { if (err && err !== true) return reject(new Error(err)); resolve(); } ); }); } } module.exports = new SongsModule();