import { ObjectId, MongoClient } from 'mongodb'; let db; let dbo; /** * Manages database operations */ class Db { name = 'db'; init(app){ } async start(app){ db = await MongoClient.connect(app.config.db.url, {maxPoolSize: 256}); try { dbo = db.db(app.config.db.name); this.instance = dbo; for (let c of ['users', 'user_sessions', 'history', 'log', 'assets']){ try { await dbo.createCollection(c); }catch(err){} } }finally{ } } /** * Inserts a record in a db collection * @param {string} collection The name of the collection * @param {Object} value The object to insert * @returns {ObjectId} Inserted Id */ async create(collection, value){ try { delete value._id; return await dbo.collection(collection).insertOne(value); }finally{ } } /** * Loads a record from db collection * @param {string} collection The name of the collection * @param {Object} key Record identifier * @param {Object} projection What data to take from the object * @returns {Object} A record */ async get(collection, key, projection){ try { let res = await dbo.collection(collection).findOne(key, projection ? {projection} : undefined) return res; }finally{ } } /** * Performs a database query * @param {string} collection Collection name * @param {Object} query A mongo db query * @returns {Object[]} Array of records */ async list(collection, query){ try { let cursor = dbo.collection(collection).find(query.query, query.project ? {projection: query.project} : undefined); query.sort && cursor.sort(query.sort); query.skip && cursor.skip(query.skip); query.limit && cursor.limit(query.limit); let count = await dbo.collection(collection).countDocuments(query.query); let result = await cursor.toArray(); return {data: result, count:count}; }finally{ } } async aggregate(collection, specs){ try { let cursor = dbo.collection(collection); let aggCursor = cursor.aggregate(specs); let result = await aggCursor.toArray(); return result.length == 1 ? result[0] : result; }finally{ } } async distinct(collection, key, query){ try { return await dbo.collection(collection).distinct(key, query); }finally{ } } async update(collection, key, value){ let r; try { delete value._id; r = await dbo.collection(collection).replaceOne(key, value, {upsert:true}); }finally{ return r; } } async updateSet(collection, key, value){ let r; try { r = await dbo.collection(collection).updateMany(key, value); }finally{ return r; } } async remove(collection, key){ try { await dbo.collection(collection).deleteMany(key); }finally{ } } convertToObjectId(object, key, recursive, result){ if (object && object[key]){ if (Array.isArray(object[key])){ object[key].forEach((v, i, a)=>{ a[i] = this.ObjectId(v); result && result.push(a[i]); }) }else{ let oid = this.ObjectId(object[key]) object[key] = oid; result && result.push(oid); } } if (recursive){ for (var k in object){ if (typeof(object[k]) == 'object'){ this.convertToObjectId(object[k], key, recursive, result); } } } } sanitizeQuery(q){ if (!q) return; Object.getOwnPropertyNames(q).forEach(n=>{ if (n.startsWith('$')){ //sanitize $ console.warn('Deleting suspicious query key', n) delete q[n]; } if (typeof(q[n]) == 'object'){ this.sanitizeQuery(q[n]); } //prepare for DB if (n.startsWith('*')){ let n1 = '$' + n.slice(1); q[n1] = q[n]; delete q[n]; n = n1; } if (['$where', '$group', '$merge', '$lookup', '$accumulator', '$function'].includes(n)){ delete q[n]; console.warn('Deleting suspicious query key', n); } }) if (q.$oid){ q.$oid.forEach(o=>{ q[o] = this.ObjectId(q[o]); }) delete q.$oid; } return q; } sanitizeProjection(p){ if (!p) return { _id: 1 }; let props = Object.getOwnPropertyNames(p); props.forEach(n=>{ if (typeof p[n] == 'object'){ console.warn('Deleting suspicious projection key, object', n) delete p[n] }else if (!n.match(/^[0-9a-zA-Z\._\$\#\-\:]*$/)){ console.warn('Deleting suspicious projection key', n) delete p[n] }else if (typeof p[n] !== 'number' && !p[n].match(/^[0-9a-zA-Z\._\$\#\-]*$/)){ console.warn('Deleting suspicious projection value', n, p[n]) delete p[n] } }) props = Object.getOwnPropertyNames(p); if (props.length == 0){ return { _id: 1 }; } } checkLimit(q){ if (!q) return; if (!q.limit) q.limit = 100; if (q.limit == 'all') delete q.limit; } ObjectId(id){ return new ObjectId(id); } } export { Db };