Files
pronature-platform/backend/app/Db.js
T
2024-11-27 12:46:03 +02:00

250 lines
7.8 KiB
JavaScript

import { ObjectId, MongoClient } from 'mongodb';
let db;
let dbo;
/**
* Manages database operations
*/
class Db {
name = 'db';
/**
* Initializes the database plugin
* @param {App} app The application instance
*/
init(app){
}
/**
* Starts the database plugin
* @param {App} app The application instance
*/
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{
}
}
/**
* Performs a database aggregation according to a given pipeline
* @param {string} collection Database collection name
* @param {Object} specs aggregation definition (the pipeline)
* @returns {Object[]} Array of records
*/
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{
}
}
/**
* Finds the distinct values for a specified field across a single collection
* @param {string} collection Database collection name
* @param {Object} key The target field for the distinction
* @param {Object} query filter to be applied
* @returns {Object[]}
*/
async distinct(collection, key, query){
try {
return await dbo.collection(collection).distinct(key, query);
}finally{
}
}
/**
* Updates a record in database by given key and value
* @param {Object} collection DB collection
* @param {Object} key The key/query which identifies the record to be updated
* @param {Object} value The new value for the record
* @returns {Object} The result from the update operation
*/
async update(collection, key, value){
let r;
try {
delete value._id;
r = await dbo.collection(collection).replaceOne(key, value, {upsert:true});
}finally{
return r;
}
}
/**
* Performs partial update on a record by given key and partial value
* @param {Object} collection Database collection
* @param {Object} key The key/query which identifies the record to be updated
* @param {Object} value The partial value to be updated
* @returns {Object} The result from the update operation
*/
async updateSet(collection, key, value){
let r;
try {
r = await dbo.collection(collection).updateMany(key, value);
}finally{
return r;
}
}
/**
* Removes a record from the database by given key
* @param {Object} collection Database collection
* @param {Object} key The key/query which identifies the record to be updated
*/
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 };