| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 | 
							- import config from "config";
 
- import mongoose from "mongoose";
 
- import bluebird from "bluebird";
 
- import async from "async";
 
- import CoreClass from "../../core";
 
- const REQUIRED_DOCUMENT_VERSIONS = {
 
- 	activity: 2,
 
- 	news: 1,
 
- 	playlist: 3,
 
- 	punishment: 1,
 
- 	queueSong: 1,
 
- 	report: 2,
 
- 	song: 4,
 
- 	station: 5,
 
- 	user: 3
 
- };
 
- const regex = {
 
- 	azAZ09_: /^[A-Za-z0-9_]+$/,
 
- 	az09_: /^[a-z0-9_]+$/,
 
- 	emailSimple: /^[\x00-\x7F]+@[a-z0-9]+\.[a-z0-9]+(\.[a-z0-9]+)?$/,
 
- 	ascii: /^[\x00-\x7F]+$/,
 
- 	custom: regex => new RegExp(`^[${regex}]+$`)
 
- };
 
- const isLength = (string, min, max) => !(typeof string !== "string" || string.length < min || string.length > max);
 
- mongoose.Promise = bluebird;
 
- let DBModule;
 
- class _DBModule extends CoreClass {
 
- 	// eslint-disable-next-line require-jsdoc
 
- 	constructor() {
 
- 		super("db");
 
- 		DBModule = this;
 
- 	}
 
- 	/**
 
- 	 * Initialises the database module
 
- 	 *
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	initialize() {
 
- 		return new Promise((resolve, reject) => {
 
- 			this.schemas = {};
 
- 			this.models = {};
 
- 			const mongoUrl = config.get("mongo").url;
 
- 			mongoose.set("useFindAndModify", false);
 
- 			mongoose
 
- 				.connect(mongoUrl, {
 
- 					useNewUrlParser: true,
 
- 					useUnifiedTopology: true,
 
- 					useCreateIndex: true
 
- 				})
 
- 				.then(async () => {
 
- 					this.schemas = {
 
- 						song: {},
 
- 						queueSong: {},
 
- 						station: {},
 
- 						user: {},
 
- 						activity: {},
 
- 						playlist: {},
 
- 						news: {},
 
- 						report: {},
 
- 						punishment: {}
 
- 					};
 
- 					const importSchema = schemaName =>
 
- 						new Promise(resolve => {
 
- 							import(`./schemas/${schemaName}`).then(schema => {
 
- 								this.schemas[schemaName] = new mongoose.Schema(schema.default);
 
- 								return resolve();
 
- 							});
 
- 						});
 
- 					await importSchema("song");
 
- 					await importSchema("queueSong");
 
- 					await importSchema("station");
 
- 					await importSchema("user");
 
- 					await importSchema("activity");
 
- 					await importSchema("playlist");
 
- 					await importSchema("news");
 
- 					await importSchema("report");
 
- 					await importSchema("punishment");
 
- 					this.models = {
 
- 						song: mongoose.model("song", this.schemas.song),
 
- 						queueSong: mongoose.model("queueSong", this.schemas.queueSong),
 
- 						station: mongoose.model("station", this.schemas.station),
 
- 						user: mongoose.model("user", this.schemas.user),
 
- 						activity: mongoose.model("activity", this.schemas.activity),
 
- 						playlist: mongoose.model("playlist", this.schemas.playlist),
 
- 						news: mongoose.model("news", this.schemas.news),
 
- 						report: mongoose.model("report", this.schemas.report),
 
- 						punishment: mongoose.model("punishment", this.schemas.punishment)
 
- 					};
 
- 					mongoose.connection.on("error", err => {
 
- 						this.log("ERROR", err);
 
- 					});
 
- 					mongoose.connection.on("disconnected", () => {
 
- 						this.log("ERROR", "Disconnected, going to try to reconnect...");
 
- 						this.setStatus("RECONNECTING");
 
- 					});
 
- 					mongoose.connection.on("reconnected", () => {
 
- 						this.log("INFO", "Reconnected.");
 
- 						this.setStatus("READY");
 
- 					});
 
- 					mongoose.connection.on("reconnectFailed", () => {
 
- 						this.log("INFO", "Reconnect failed, stopping reconnecting.");
 
- 						// this.failed = true;
 
- 						// this._lockdown();
 
- 						this.setStatus("FAILED");
 
- 					});
 
- 					// User
 
- 					this.schemas.user
 
- 						.path("username")
 
- 						.validate(
 
- 							username => isLength(username, 2, 32) && regex.custom("a-zA-Z0-9_-").test(username),
 
- 							"Invalid username."
 
- 						);
 
- 					this.schemas.user.path("email.address").validate(email => {
 
- 						if (!isLength(email, 3, 254)) return false;
 
- 						if (email.indexOf("@") !== email.lastIndexOf("@")) return false;
 
- 						return regex.emailSimple.test(email) && regex.ascii.test(email);
 
- 					}, "Invalid email.");
 
- 					// Station
 
- 					this.schemas.station
 
- 						.path("name")
 
- 						.validate(id => isLength(id, 2, 16) && regex.az09_.test(id), "Invalid station name.");
 
- 					this.schemas.station
 
- 						.path("displayName")
 
- 						.validate(
 
- 							displayName => isLength(displayName, 2, 32) && regex.ascii.test(displayName),
 
- 							"Invalid display name."
 
- 						);
 
- 					this.schemas.station.path("description").validate(description => {
 
- 						if (!isLength(description, 2, 200)) return false;
 
- 						const characters = description.split("");
 
- 						return characters.filter(character => character.charCodeAt(0) === 21328).length === 0;
 
- 					}, "Invalid display name.");
 
- 					this.schemas.station.path("owner").validate({
 
- 						validator: owner =>
 
- 							new Promise((resolve, reject) => {
 
- 								this.models.station.countDocuments({ owner }, (err, c) => {
 
- 									if (err) reject(new Error("A mongo error happened."));
 
- 									else if (c >= 3) reject(new Error("User already has 3 stations."));
 
- 									else resolve();
 
- 								});
 
- 							}),
 
- 						message: "User already has 3 stations."
 
- 					});
 
- 					/*
 
- 					this.schemas.station.path('queue').validate((queue, callback) => { //Callback no longer works, see station max count
 
- 						let totalDuration = 0;
 
- 						queue.forEach((song) => {
 
- 							totalDuration += song.duration;
 
- 						});
 
- 						return callback(totalDuration <= 3600 * 3);
 
- 					}, 'The max length of the queue is 3 hours.');
 
- 		
 
- 					this.schemas.station.path('queue').validate((queue, callback) => { //Callback no longer works, see station max count
 
- 						if (queue.length === 0) return callback(true);
 
- 						let totalDuration = 0;
 
- 						const userId = queue[queue.length - 1].requestedBy;
 
- 						queue.forEach((song) => {
 
- 							if (userId === song.requestedBy) {
 
- 								totalDuration += song.duration;
 
- 							}
 
- 						});
 
- 						return callback(totalDuration <= 900);
 
- 					}, 'The max length of songs per user is 15 minutes.');
 
- 		
 
- 					this.schemas.station.path('queue').validate((queue, callback) => { //Callback no longer works, see station max count
 
- 						if (queue.length === 0) return callback(true);
 
- 						let totalSongs = 0;
 
- 						const userId = queue[queue.length - 1].requestedBy;
 
- 						queue.forEach((song) => {
 
- 							if (userId === song.requestedBy) {
 
- 								totalSongs++;
 
- 							}
 
- 						});
 
- 						if (totalSongs <= 2) return callback(true);
 
- 						if (totalSongs > 3) return callback(false);
 
- 						if (queue[queue.length - 2].requestedBy !== userId || queue[queue.length - 3] !== userId) return callback(true);
 
- 						return callback(false);
 
- 					}, 'The max amount of songs per user is 3, and only 2 in a row is allowed.');
 
- 					*/
 
- 					// Song
 
- 					const songTitle = title => isLength(title, 1, 100);
 
- 					this.schemas.song.path("title").validate(songTitle, "Invalid title.");
 
- 					this.schemas.song.path("artists").validate(artists => artists.length <= 10, "Invalid artists.");
 
- 					const songArtists = artists =>
 
- 						artists.filter(artist => isLength(artist, 1, 64) && artist !== "NONE").length ===
 
- 						artists.length;
 
- 					this.schemas.song.path("artists").validate(songArtists, "Invalid artists.");
 
- 					const songGenres = genres => {
 
- 						if (genres.length > 16) return false;
 
- 						return (
 
- 							genres.filter(genre => isLength(genre, 1, 32) && regex.ascii.test(genre)).length ===
 
- 							genres.length
 
- 						);
 
- 					};
 
- 					this.schemas.song.path("genres").validate(songGenres, "Invalid genres.");
 
- 					const songThumbnail = thumbnail => {
 
- 						if (!isLength(thumbnail, 1, 256)) return false;
 
- 						if (config.get("cookie.secure") === true) return thumbnail.startsWith("https://");
 
- 						return thumbnail.startsWith("http://") || thumbnail.startsWith("https://");
 
- 					};
 
- 					this.schemas.song.path("thumbnail").validate(songThumbnail, "Invalid thumbnail.");
 
- 					// Playlist
 
- 					this.schemas.playlist
 
- 						.path("displayName")
 
- 						.validate(
 
- 							displayName => isLength(displayName, 1, 32) && regex.ascii.test(displayName),
 
- 							"Invalid display name."
 
- 						);
 
- 					this.schemas.playlist.path("createdBy").validate(createdBy => {
 
- 						this.models.playlist.countDocuments({ createdBy }, (err, c) => !(err || c >= 10));
 
- 					}, "Max 10 playlists per user.");
 
- 					this.schemas.playlist
 
- 						.path("songs")
 
- 						.validate(songs => songs.length <= 5000, "Max 5000 songs per playlist.");
 
- 					this.schemas.playlist.path("songs").validate(songs => {
 
- 						if (songs.length === 0) return true;
 
- 						return songs[0].duration <= 10800;
 
- 					}, "Max 3 hours per song.");
 
- 					this.schemas.playlist.index({ createdFor: 1, type: 1 }, { unique: true });
 
- 					// Report
 
- 					this.schemas.report
 
- 						.path("description")
 
- 						.validate(
 
- 							description =>
 
- 								!description || (isLength(description, 0, 400) && regex.ascii.test(description)),
 
- 							"Invalid description."
 
- 						);
 
- 					if (config.get("skipDbDocumentsVersionCheck")) resolve();
 
- 					else {
 
- 						this.runJob("CHECK_DOCUMENT_VERSIONS", {}, null, -1)
 
- 							.then(() => {
 
- 								resolve();
 
- 							})
 
- 							.catch(err => {
 
- 								reject(err);
 
- 							});
 
- 					}
 
- 				})
 
- 				.catch(err => {
 
- 					this.log("ERROR", err);
 
- 					reject(err);
 
- 				});
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Checks if all documents have the correct document version
 
- 	 *
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	CHECK_DOCUMENT_VERSIONS() {
 
- 		return new Promise((resolve, reject) => {
 
- 			async.each(
 
- 				Object.keys(REQUIRED_DOCUMENT_VERSIONS),
 
- 				(modelName, next) => {
 
- 					const model = DBModule.models[modelName];
 
- 					const requiredDocumentVersion = REQUIRED_DOCUMENT_VERSIONS[modelName];
 
- 					model.countDocuments({ documentVersion: { $ne: requiredDocumentVersion } }, (err, count) => {
 
- 						if (err) next(err);
 
- 						else if (count > 0)
 
- 							next(
 
- 								`Collection "${modelName}" has ${count} documents with a wrong document version. Run migration.`
 
- 							);
 
- 						else next();
 
- 					});
 
- 				},
 
- 				err => {
 
- 					if (err) reject(new Error(err));
 
- 					else resolve();
 
- 				}
 
- 			);
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Returns a database model
 
- 	 *
 
- 	 * @param {object} payload - object containing the payload
 
- 	 * @param {object} payload.modelName - name of the model to get
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	GET_MODEL(payload) {
 
- 		return new Promise(resolve => {
 
- 			resolve(DBModule.models[payload.modelName]);
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Returns a database schema
 
- 	 *
 
- 	 * @param {object} payload - object containing the payload
 
- 	 * @param {object} payload.schemaName - name of the schema to get
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	GET_SCHEMA(payload) {
 
- 		return new Promise(resolve => {
 
- 			resolve(DBModule.schemas[payload.schemaName]);
 
- 		});
 
- 	}
 
- 	/**
 
- 	 * Checks if a password to be stored in the database has a valid length
 
- 	 *
 
- 	 * @param {object} password - the password itself
 
- 	 * @returns {Promise} - returns promise (reject, resolve)
 
- 	 */
 
- 	passwordValid(password) {
 
- 		return isLength(password, 6, 200);
 
- 	}
 
- }
 
- export default new _DBModule();
 
 
  |