import { AssFile, AssUser, MongoDBConfiguration, NID, UploadToken } from 'ass'; import { UserConfig } from '../UserConfig'; import { Database, DatabaseTable, DatabaseValue } from './database'; import mongoose, { Model, Mongoose, Schema } from 'mongoose'; import { log } from '../log'; interface TableVersion { name: string; version: number; } const VERSIONS_SCHEMA = new Schema({ name: String, version: Number }); interface MongoSchema { id: NID, data: T } const FILE_SCHEMA = new Schema>({ id: String, data: { fakeid: String, fileKey: String, filename: String, mimetype: String, save: { local: String, s3: Boolean // this will break if it gets the url object // but im so fucking tired of this, were just // going to keep it like this until it becomes // a problem }, sha256: String, size: Number, timestamp: String, uploader: String } }); const TOKEN_SCHEMA = new Schema>({ id: String, data: { id: String, token: String, hint: String } }); const USER_SCHEMA = new Schema>({ id: String, data: { id: String, username: String, password: String, admin: Boolean, tokens: [ String ], files: [ String ], meta: { type: String, get: (v: string) => JSON.parse(v), set: (v: string) => JSON.stringify(v) } } }); /** * database adapter for mongodb */ export class MongoDBDatabase implements Database { private _client: Mongoose; // mongoose models private _versionModel: Model; private _fileModel: Model>; private _tokenModel: Model>; private _userModel: Model>; private _validateConfig(): string | undefined { // make sure the configuration exists if (!UserConfig.ready) return 'User configuration not ready'; if (typeof UserConfig.config.database != 'object') return 'MongoDB configuration missing'; if (UserConfig.config.database.kind != 'mongodb') return 'Database not set to MongoDB, but MongoDB is in use, something has gone terribly wrong'; if (typeof UserConfig.config.database.options != 'object') return 'MongoDB configuration missing'; let config = UserConfig.config.database.options; // check the postgres config const checker = (val: string) => val != null && val !== ''; const issue = !checker(config.host) ? 'Missing MongoDB Host' : !checker(config.user) ? 'Missing MongoDB User' : !checker(config.password) ? 'Missing MongoDB Password' : !checker(config.database) ? 'Missing MongoDB Database' // ! Blame VS Code for this weird indentation : undefined; return issue; } open(): Promise { return new Promise(async (resolve, reject) => { try { // validate config let configError = this._validateConfig(); if (configError != null) throw new Error(configError); let options = UserConfig.config.database!.options! as MongoDBConfiguration; // connect log.info('MongoDB', `connecting to ${options.host}:${options.port}`); this._client = await mongoose.connect(`mongodb://${options.user}:${options.password}@${options.host}:${options.port}/${options.database}`); log.success('MongoDB', 'ok'); resolve(); } catch (err) { log.error('MongoDB', 'failed to connect'); console.error(err); reject(err); } }); } close(): Promise { return new Promise(async (resolve, reject) => { try { // gracefully disconnect await this._client.disconnect(); resolve(); } catch (err) { log.error('MongoDB', 'failed to disconnect'); console.error(err); reject(err); } }); } configure(): Promise { return new Promise(async (resolve, reject) => { try { this._versionModel = this._client.model('assversions', VERSIONS_SCHEMA); this._fileModel = this._client.model('assfiles', FILE_SCHEMA); this._tokenModel = this._client.model('asstokens', TOKEN_SCHEMA); this._userModel = this._client.model('assusers', USER_SCHEMA); // theres only one version right now so we dont need to worry about anything, just adding the version thingies if they arent there let versions = await this._versionModel.find().exec() .then(res => res.reduce((obj, doc) => obj.set(doc.name, doc.version), new Map())); for (let [table, version] of [['assfiles', 1], ['asstokens', 1], ['assusers', 1]] as [string, number][]) { if (!versions.has(table)) { // set the version new this._versionModel({ name: table, version: version }).save(); versions.set(table, version); } } resolve(); } catch (err) { log.error('MongoDB', 'failed to configure'); console.error(err); reject(err); } }); } put(table: DatabaseTable, key: string, data: DatabaseValue): Promise { return new Promise(async (resolve, reject) => { try { const models = { assfiles: this._fileModel, assusers: this._userModel, asstokens: this._tokenModel }; await new models[table]({ id: key, data: data }).save(); resolve(); } catch (err) { reject(err); } }); } get(table: DatabaseTable, key: string): Promise { return new Promise(async (resolve, reject) => { try { const models = { assfiles: this._fileModel, assusers: this._userModel, asstokens: this._tokenModel }; // @ts-ignore // typescript cant infer this but it is 100% correct // no need to worry :> let result = await models[table].find({ id: key }).exec(); resolve(result.length ? result[0].data : void 0); } catch (err) { reject(err); } }); } getAll(table: DatabaseTable): Promise<{ [index: string]: DatabaseValue; }> { return new Promise(async (resolve, reject) => { try { const models = { assfiles: this._fileModel, assusers: this._userModel, asstokens: this._tokenModel }; // more ts-ignore! // @ts-ignore let result = await models[table].find({}).exec() // @ts-ignore .then(res => res.reduce((obj, doc) => (obj[doc.id] = doc.data, obj), {})); resolve(result); } catch (err) { reject(err); } }); } };