Files
pronature-platform/backend/app/Db.js
T

272 lines
11 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', 'scenarios', 'games']){
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[]} Array of records, списък от записи
*/
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);
}
/**
* Gets last asset Id from database, намира последния пореден идентификатор на обект в базата от данни
* @returns {Number} Last Asset Id, последен (най-голям) идентификатор
*/
async getLastId(collection){
let ag = await this.aggregate(collection, [
{
$group:{
_id: null,
max: {
$max: "$id",
},
},
},
]);
return ag.max || 0;
}
async stop(){
await db.close();
}
}
export { Db };