268 lines
11 KiB
JavaScript
268 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']){
|
|
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;
|
|
}
|
|
}
|
|
|
|
export { Db }; |