UNPKG

umbot

Version:

Мультиплатформенный фреймворк для создания голосовых навыков и чат-ботов с единой бизнес-логикой. Встроенная поддержка ВКонтакте, Telegram, Viber, MAX, Яндекс Алисы, Маруси и Сбера SmartApp. Архитектура на адаптерах позволяет подключать любые другие платф

378 lines (377 loc) 15 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MongoAdapter = void 0; const Base_1 = require("../Base/Base"); const index_1 = require("../../../index"); const mongodb_1 = require("mongodb"); /** * Адаптер для работы с базой данных mongodb */ class MongoAdapter extends Base_1.Base { /** * Формат базы данных */ dbFormat = 'mongodb'; constructor(options) { super(options); } /** * Метод инициализации плагина. * Вызывается один раз при подключении через `bot.use()`. * @param appContext Контекст приложения */ init(appContext) { if (this._dbOptions) { appContext.appConfig.db ??= { host: '', user: '', pass: '', database: '' }; appContext.appConfig.db.options = this._dbOptions.options || appContext.appConfig.db?.options; appContext.appConfig.db.host = this._dbOptions.host || appContext.appConfig.db?.host; appContext.appConfig.db.user = this._dbOptions.user || appContext.appConfig.db?.user; appContext.appConfig.db.pass = this._dbOptions.pass || appContext.appConfig.db?.pass; appContext.appConfig.db.database = this._dbOptions.database || appContext.appConfig.db?.database; } super.init(appContext); } async connect() { const errors = []; let mongoClient = null; let mongoConnect = null; if (this._appContext.appConfig.db) { try { const options = { timeoutMS: 3000, serverSelectionTimeoutMS: 2000, // Тайм-аут на выбор сервера connectTimeoutMS: 2000, socketTimeoutMS: 2000, maxPoolSize: 50, ...this._appContext.appConfig.db.options, serverApi: { version: mongodb_1.ServerApiVersion.v1, strict: true, deprecationErrors: true, ...this._appContext.appConfig.db.options?.serverApi, }, }; if (this._appContext.appConfig.db.user) { options.auth = { username: this._appContext.appConfig.db.user, password: this._appContext.appConfig.db.pass, }; } mongoClient = new mongodb_1.MongoClient(this._appContext.appConfig.db.host, options); const connect = async () => { if (!mongoClient) { return false; } // Проверяем, есть ли активное соединение перед закрытием if (this._appContext.database.databaseInfo?.mongoClient) { await mongoClient.close(true); } mongoConnect = await mongoClient.connect(); // Проверяем подключение сразу после установки return await this.isConnected(); }; if (!(await connect())) { await new Promise((resolve) => { setTimeout(resolve, 2000).unref(); }); if (!(await connect())) { throw new Error('Failed to verify database connection'); } } return true; } catch (err) { errors.push(err.message); // mongoConnect = null; mongoClient = null; this._saveLog('При подключении в базе данных произошла ошибка:', err); return false; } } else { errors.push('Отсутствуют данные для подключения!'); } if (this._appContext.database.databaseInfo) { this._appContext.database.databaseInfo.mongoClient = mongoClient; this._appContext.database.databaseInfo.mongoConnect = mongoConnect; } else { this._appContext.database.databaseInfo = { mongoClient, mongoConnect, }; } if (errors.length > 0) { this._saveLog(`При подключении в базе данных произошли следующие ошибки: ${JSON.stringify(errors)}`); return false; } return true; } /** * Выполняет UPDATE-запрос. * @param updateData Дополнительная информация для запроса. Содержит сам запроса, а также название таблицы и прочие данные. */ async _update(updateData) { let update = updateData.data; let select = updateData.query; if (this._appContext.database.databaseInfo) { update = this.validate(updateData, update); select = this.validate(updateData, select); if (updateData.primaryKeyName) { return !!(await this.query(async (_client, db) => { try { const collection = db.collection(updateData.tableName); const result = await collection.updateOne(select, { $set: update, }); return { status: true, data: { modifiedCount: result.modifiedCount }, }; } catch (err) { return { status: false, error: err, }; } })); } } return false; } /** * Выполняет INSERT-запрос. * @param insertData Дополнительная информация для запроса. Содержит сам запроса, а также название таблицы и прочие данные. */ async _insert(insertData) { let insert = insertData.data; if (this._appContext.database.databaseInfo) { insert = this.validate(insertData, insert); if (insertData.primaryKeyName) { return !!(await this.query(async (_client, db) => { try { const collection = db.collection(insertData.tableName); const result = await collection.insertOne(insert); return { status: true, data: { insertedId: result.insertedId }, }; } catch (err) { return { status: false, error: err instanceof Error ? err : 'Unknown error', }; } })); } } return false; } /** * Выполняет DELETE-запрос. * @param removeData Дополнительная информация для запроса. Содержит сам запроса, а также название таблицы и прочие данные. */ async _remove(removeData) { let remove = removeData.query; if (this._appContext.database.databaseInfo) { remove = this.validate(removeData, remove); return !!(await this.query(async (_client, db) => { try { const collection = db.collection(removeData.tableName); const result = await collection.deleteOne(remove); return { status: true, data: { deletedCount: result.deletedCount }, }; } catch (err) { return { status: false, error: err instanceof Error ? err : 'Unknown error', }; } })); } return false; } /** * Выполняет произвольный запрос через callback. * * @param callback функция обработчик */ async _query(callback) { const vDB = this._appContext.database.databaseInfo; try { if (vDB?.mongoConnect) { const client = vDB.mongoConnect; const db = client.db(this._appContext.appConfig.db?.database); const data = await callback(client, db); if (data.status) { return data.data; } this._saveLog(data.error + ''); return null; } else { this._saveLog('Не удалось выполнить запрос.'); return null; } } catch (err) { this._saveLog(err, err); return null; } } /** * Выполняет произвольный запрос через callback. * * Внутри себя вызывает this._query, основное отличие в том, что в данном методе пишутся метрики. * @param callback функция обработчик */ async query(callback) { return this._query(callback); } /** * Валидация запросов. * Валидирует запрос, приводя его к корректному виду * @param query * @param element */ validate(query, element) { if (!element) { return {}; } const rules = query.rules; if (rules) { rules.forEach((rule) => { let type = 'number'; switch (rule.type) { case 'string': case 'text': type = 'string'; break; case 'int': case 'integer': case 'bool': type = 'number'; break; } rule.name.forEach((data) => { if (type === 'string') { if (rule.max !== undefined) { element[data] = index_1.Text.resize(element[data], rule.max); } element[data] = this.escapeString(element[data]); } else { element[data] = +element[data]; } }); }); } return element; } /** * Выполняет SELECT-запрос. * @param selectData Дополнительная информация для запроса. Содержит информацию о таблице и структуре. * @param where Сам запрос * @param isOne Определяет нужно ли вернуть только 1 найденную запись, либо отдать все доступные данные. */ async _select(selectData, where, isOne = false) { if (this._appContext.database.databaseInfo) { return (await this.query(async (_client, db) => { try { const collection = db.collection(selectData.tableName); let results; if (isOne) { results = await collection.findOne(where || {}); } else { results = await collection .find(where || {}) .toArray(); } return { status: !!results, data: results, }; } catch (err) { return { status: false, error: err, }; } })); } return { status: false, error: 'Не удалось получить данные', }; } /** * Проверяет, установлено ли соединение с БД. */ async isConnected() { if (!this._appContext.database.databaseInfo) { return false; } try { if (!this._appContext.database.databaseInfo.mongoClient) { return false; } // Пингуем базу данных для проверки подключения await this._appContext.database.databaseInfo.mongoClient.db().command({ ping: 1 }); return true; } catch (err) { this._appContext.logError(err.message, { error: err, }); return false; } } /** * Сохранение логов * @param errorMsg * @param error * @protected */ _saveLog(errorMsg, error) { this._appContext?.logError(`MongoDB: ${errorMsg}`, { error, }); } /** * Закрывает все подключения к БД. * Все процессы завершаются, и происходит сохранение данных. */ async destroy() { return this.close(''); } /** * Закрывает подключение к определенной таблице. * В MongoDB нет концепции "закрытия таблицы". Метод close() закрывает всё соединение с базой. * @param tableName */ async close(tableName) { await super.close(tableName); if (this._appContext.database.databaseInfo) { try { await this._appContext.database.databaseInfo.mongoClient.close(); this._appContext.database.databaseInfo.mongoConnect = null; this._appContext.database.databaseInfo.mongoClient = null; } catch (err) { this._appContext.logError(err.message, { error: err, }); } } } } exports.MongoAdapter = MongoAdapter;