UNPKG

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
"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;