database/db.js

/**
 * @module database
 */

const MongoClient = require('mongodb').MongoClient;
const assert = require('assert');
const dotenv = require('dotenv');
const fs = require('fs');
const Permissions = require('./permissions');
dotenv.config();

let _client;

const readOptions = {
	encoding: "utf8"
};

async function useClient() {
	/**
	 * Returns the MongoDB client as a promise
	 */

	let uri;

	if (process.env.DEVELOPMENT) {
		uri = "mongodb://" + process.env.MONGO_ADDRESS;
	}
	else {
		const passKeyPath = "/run/secrets/mongopass.txt";
		const passKey = fs.readFileSync(passKeyPath, readOptions);
		uri= "mongodb+srv://" + process.env.MONGO_USERNAME + ":" + passKey + "@" + process.env.MONGO_ADDRESS;
	}

	// check if we already have the client and return it if we do.
	if (_client) {
			return _client
	}
	else {
		// otherwise connect to the client and return it
		_client = await MongoClient.connect(uri, {useNewUrlParser: true}).catch(err => {throw(err);});
		return _client
	}
}

/**
 * Defines an interface for database management classes to implement/inherit from.
 *
 * @interface DatabaseInterface
 */
class DatabaseInterface {

	/**
	 * Retrieves the Loom database to work with.
	 * @returns {Object}
	 */
	static async getDatabase() {
		const client = await useClient().catch(error => {console.log(error)});
		const db = client.db("Loom");
		return db;
	}

	/**
	 * Get a collection from the Loom database. Override this method in children classes to retrieve the specific collection(s) they operate on.
	 * @param {string} name - The name of the collection.
	 * @returns {Object}
	 */
	static async getCollection(name) {
		const db =  await this.getDatabase().catch(error => {console.log(error)});
		const collection = db.collection(name.toString());
		return collection;
	}

    /**
	 * Adds a list of permissions to a user, possibly checking the permissions of the requesting user to do so
     * @param {string} id - the id of the object to change permissions for
     * @param {string[]} permission_names - an array of permission names to add to the user
     * @param {string} otherUserId - the id of the user to add the permissions for
     * @param {Object} user - the user requesting the permissions be added (for checking their permissions)
     * @param {bool} checkPermissions - a boolean describing whether the permissions of user allow them to add permissions. Defaults to true.
     * @returns {Promise.<void>}
     */
	static async addPermissions(id, permission_names, otherUserId, user, checkPermissions = true) {
		const object = await this.get(id);

		const permissions = new Permissions(object.permissions);

		if (!checkPermissions || permissions.check('edit', user._id)){
			for (let permission of permission_names) {
				permissions.add(permission, otherUserId);
			}
		}

		// actually update the object
		await this.update(id, {$set: {permissions: permissions.permissions}});
	}

    /**
	 * Removes a list of permissions from a user, possibly checking the permissions of the requesting user to do so
     * @param {string} id - the id of the object to change permissions for
     * @param {string[]} permission_names - an array of permission names to remove from the user
     * @param {string} otherUserId - the id of the user to remove the permissions from
     * @param {Object} user - the user requesting the permissions be removed (for checking their permissions)
     * @param {bool} checkPermissions - a boolean describing whether the permissions of user allow them to remove permissions. Defaults to true.
     * @returns {Promise.<void>}
     */
	static async removePermissions(id, permission_names, otherUserId, user, checkPermissions = true) {
		const object = await this.get(id);

		const permissions = new Permissions(object.permissions);

		if (!checkPermissions || permissions.check('edit', user._id)){
			for (let permission of permission_names) {
				permissions.remove(permission, otherUserId);
			}
		}

		// actually update the object
		await this.update(id, {$set: {permissions: permissions.permissions}});
	}

    /**
	 * Checks if a user has a set of permissions to an object in the database.
     * @param {string} id - the database id of the object to check permissions for
     * @param {string[]} permission_names - An array of permission names to look for
     * @param {Object} user - the user to check permissions for
     * @returns {Promise.<boolean>}
     */
	static async checkPermissions(id, permission_names, user, object=null) {

		if (object === null) {
			object = await this.get(id);
		}

		const permissions = new Permissions(object.permissions);

		for (const permission of permission_names) {

			if (!permissions.check(permission, user._id)) {
				return false
			}
		}
		return true;
	}

    /**
	 * Checks if a user has any permissions to an object in the database.
     * @param {string} id - the id of the object in the database
     * @param {string} user_id - the id of the user to check has permissions
     * @returns {Promise.<boolean>}
     */
	static async hasAnyPermissions(id, user_id) {
		const object = await this.get(id);

		const permissions = new Permissions(object.permissions);

		return permissions.has_permissions(user_id);
	}

	static async owns(id, user) {
		return false;
	}

	static async create(){};

	static async del(id){};

	static async get(id){};

	static async update(id, update){};

	static validateObjectIdHex(id){
		return id.match(/^[0-9a-fA-F]{24}$/);
	}

}

module.exports = {
	useClient: useClient,
	DatabaseInterface: DatabaseInterface
};