database-proxy
Version:
Through a set of access control rules configuration database access to realize the client directly access the database via HTTP.
425 lines (424 loc) • 13.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MongoAccessor = void 0;
const types_1 = require("../types");
const mongodb_1 = require("mongodb");
const logger_1 = require("../logger");
const events_1 = require("events");
const bson_1 = require("bson");
/**
* Mongodb Accessor is responsible for performing MongoDB data operations.
*/
class MongoAccessor {
get db() {
return this.client.db();
}
get logger() {
if (!this._logger) {
this._logger = new logger_1.DefaultLogger();
}
return this._logger;
}
setLogger(logger) {
this._logger = logger;
}
constructor(client) {
this.type = 'mongo';
this._event = new events_1.EventEmitter();
this.client = client;
}
emit(event, ...args) {
return this._event.emit(event, ...args);
}
once(event, listener) {
this.once(event, listener);
}
removeAllListeners(event) {
this._event.removeAllListeners(event);
}
on(event, listener) {
this._event.on(event, listener);
}
off(event, listener) {
this._event.off(event, listener);
}
async close() {
await this.client.close();
this.logger.info('mongo connection closed');
}
async execute(params) {
const { collection, action } = params;
this.logger.info(`mongo start executing {${collection}}: ` + JSON.stringify(params));
switch (action) {
case types_1.ActionType.READ:
return await this.read(collection, params);
case types_1.ActionType.UPDATE:
return await this.update(collection, params);
case types_1.ActionType.AGGREGATE:
return await this.aggregate(collection, params);
case types_1.ActionType.REMOVE:
return await this.remove(collection, params);
case types_1.ActionType.ADD:
return await this.add(collection, params);
case types_1.ActionType.COUNT:
return await this.count(collection, params);
case types_1.ActionType.CREATE_INDEX:
return await this.createIndex(collection, params);
case types_1.ActionType.CREATE_INDEX:
return await this.createIndex(collection, params);
case types_1.ActionType.DROP_INDEX:
return await this.dropIndex(collection, params);
case types_1.ActionType.LIST_INDEXES:
return await this.listIndexes(collection, params);
}
const error = new Error(`invalid 'action': ${action}`);
this.logger.error(`mongo end of executing occurred error: `, error);
throw error;
}
/**
* Query a single document, mainly used for data queries in `access rules`
*/
async get(collection, query) {
const coll = this.db.collection(collection);
return await coll.findOne(query);
}
/**
* Emit result event
* @param params
* @param data
*/
emitResult(params, result) {
this.emit('result', { params, result });
}
/**
* Execute read query
* @param collection collection name
* @param params query params
* @returns
*/
async read(collection, params) {
const coll = this.db.collection(collection);
const { order, offset, limit, projection, count } = params;
const query = this.deserializedEjson(params.query || {});
const options = {
limit: 100,
skip: 0,
};
if (order)
options.sort = this.processOrder(order);
if (offset)
options.skip = offset;
if (projection)
options.projection = projection;
if (limit) {
options.limit = limit;
}
this.logger.debug(`mongo before read {${collection}}: `, { query, options });
const data = await coll.find(query, options).toArray();
this.logger.debug(`mongo end of read {${collection}}: `, {
query,
options,
dataLength: data.length,
});
let total;
if (count) {
total = await coll.countDocuments(query);
}
this.emitResult(params, { data });
const serialized = data.map((doc) => this.serializeBson(doc));
return {
list: serialized,
limit: options.limit,
offset: options.skip,
total,
};
}
/**
* Execute aggregate query
* @param collection
* @param params
* @returns
*/
async aggregate(collection, params) {
const coll = this.db.collection(collection);
const stages = this.processAggregateStages(params.stages);
this.logger.debug(`mongo before aggregate {${collection}}: `, stages);
const data = await coll.aggregate(stages).toArray();
this.logger.debug(`mongo after aggregate {${collection}}: `, stages, {
dataLength: data.length,
});
this.emitResult(params, { data });
const serialized = data.map((doc) => this.serializeBson(doc));
return { list: serialized };
}
/**
* Execute update query
* @param collection Collection name
* @param params
* @returns
*/
async update(collection, params) {
const coll = this.db.collection(collection);
let { query, data, multi, upsert, merge } = params;
query = this.deserializedEjson(query || {});
data = this.deserializedEjson(data || {});
const options = {};
if (upsert)
options.upsert = upsert;
// merge 不为 true 代表替换操作,暂只允许单条替换
if (!merge) {
this.logger.debug(`mongo before update (replaceOne) {${collection}}: `, {
query,
data,
options,
merge,
multi,
});
const result = await coll.replaceOne(query, data, options);
const _data = {
upsert_id: result.upsertedId,
updated: result.modifiedCount,
matched: result.matchedCount,
};
this.emitResult(params, _data);
return _data;
}
let result;
// multi 表示更新一条或多条
if (!multi) {
this.logger.debug(`mongo before update (updateOne) {${collection}}: `, {
query,
data,
options,
merge,
multi,
});
result = await coll.updateOne(query, data, options);
}
else {
options.upsert = false;
this.logger.debug(`mongo before update (updateMany) {${collection}}: `, {
query,
data,
options,
merge,
multi,
});
result = (await coll.updateMany(query, data, options));
}
const ret = {
upsert_id: this.serializeBson(result.upsertedId),
updated: result.modifiedCount,
matched: result.matchedCount,
};
this.emitResult(params, ret);
this.logger.debug(`mongo end of update {${collection}}: `, {
query,
data,
options,
merge,
multi,
result: ret,
});
return ret;
}
/**
* Execute insert query
* @param collection Collection name
* @param params
* @returns
*/
async add(collection, params) {
const coll = this.db.collection(collection);
let { data, multi } = params;
data = this.deserializedEjson(data || {});
let result;
this.logger.debug(`mongo before add {${collection}}: `, { data, multi });
// multi 表示单条或多条添加
if (!multi) {
data._id = this.generateDocId();
result = await coll.insertOne(data);
}
else {
data = data instanceof Array ? data : [data];
data.forEach((ele) => (ele._id = this.generateDocId()));
result = await coll.insertMany(data);
}
const ids = result.insertedIds ||
result.insertedId;
const ret = {
_id: this.serializeBson(ids),
insertedCount: result.insertedCount,
};
this.emitResult(params, ret);
this.logger.debug(`mongo end of add {${collection}}: `, {
data,
multi,
result: ret,
});
return ret;
}
/**
* Execute remove query
* @param collection 集合名
* @param params 请求参数
* @returns 执行结果
*/
async remove(collection, params) {
const coll = this.db.collection(collection);
let { query, multi } = params;
query = this.deserializedEjson(query || {});
let result;
this.logger.debug(`mongo before remove {${collection}}: `, { query, multi });
// multi means delete one or more
if (!multi) {
result = await coll.deleteOne(query);
}
else {
result = await coll.deleteMany(query);
}
const ret = {
deleted: result.deletedCount,
};
this.emitResult(params, ret);
this.logger.debug(`mongo end of remove {${collection}}: `, ret);
return ret;
}
/**
* Execute count query
* @param collection collection name
* @param params query params
* @returns
*/
async count(collection, params) {
const coll = this.db.collection(collection);
const query = this.deserializedEjson(params.query || {});
const options = {};
this.logger.debug(`mongo before count {${collection}}: `, { query });
const result = await coll.countDocuments(query, options);
this.logger.debug(`mongo end of count {${collection}}: `, { query, result });
this.emitResult(params, result);
return {
total: result,
};
}
/**
* Convert order params to Mongodb's order format
* @param order
* @returns
*/
processOrder(order) {
if (!(order instanceof Array))
return undefined;
return order.map((o) => {
const dir = o.direction === types_1.Direction.DESC ? -1 : 1;
return [o.field, dir];
});
}
/**
* Generate a hex string document id
* @returns
*/
generateDocId() {
const id = new mongodb_1.ObjectId();
return id.toHexString();
}
/**
* Serialize Bson to Extended JSON
* @see https://docs.mongodb.com/manual/reference/mongodb-extended-json/
* @param bsonDoc
* @returns
*/
serializeBson(bsonDoc) {
return bson_1.EJSON.serialize(bsonDoc, { relaxed: true });
}
/**
* Deserialize Extended JSOn to Bson
* @see https://docs.mongodb.com/manual/reference/mongodb-extended-json/
* @param ejsonDoc
* @returns
*/
deserializedEjson(ejsonDoc) {
return bson_1.EJSON.deserialize(ejsonDoc, { relaxed: true });
}
/**
* Convert aggregate stages params to Mongodb aggregate pipelines
* @param stages
* @returns
*/
processAggregateStages(stages) {
const _stages = stages.map((item) => {
const key = item.stageKey;
const value = bson_1.EJSON.parse(item.stageValue, { relaxed: true });
return { [key]: value };
});
return _stages;
}
/**
* Execute create index query
* @param collection Collection name
* @param params
* @returns
*/
async createIndex(collection, params) {
const coll = this.db.collection(collection);
let { data } = params;
data = this.deserializedEjson(data || {});
const { keys, options } = data;
this.logger.debug(`mongo before creating index {${collection}}: `, { data });
const result = await coll.createIndex(keys, options);
const ret = {
indexName: result,
};
this.emitResult(params, ret);
this.logger.debug(`mongo end of creating index {${collection}}: `, {
data,
result: ret,
});
return ret;
}
/**
* Execute drop index query
* @param collection Collection name
* @param params
* @returns
*/
async dropIndex(collection, params) {
const coll = this.db.collection(collection);
let { data } = params;
data = this.deserializedEjson(data || {});
this.logger.debug(`mongo before drop index {${collection}}: `, { data });
const result = await coll.dropIndex(data);
const ret = {
result,
};
this.emitResult(params, ret);
this.logger.debug(`mongo end of drop index {${collection}}: `, {
data,
result: ret,
});
return ret;
}
/**
* Execute list indexes query
* @param collection Collection name
* @param params
* @returns
*/
async listIndexes(collection, params) {
const coll = this.db.collection(collection);
let { data } = params;
data = this.deserializedEjson(data || {});
this.logger.debug(`mongo before listing indexes {${collection}}: `, {
data,
});
const result = await coll.listIndexes(data).toArray();
this.logger.debug(`mongo end of listing indexes {${collection}}: `, {
data,
});
this.emitResult(params, { result });
const serialized = result.map((doc) => this.serializeBson(doc));
return { list: serialized };
}
}
exports.MongoAccessor = MongoAccessor;