You've already forked OpenIntegrations
mirror of
https://github.com/Bayselonarrend/OpenIntegrations.git
synced 2025-08-10 22:41:43 +02:00
TCP
This commit is contained in:
@@ -1,3 +0,0 @@
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
linker = "zig cc"
|
||||
|
2551
src/addins/mongo/Cargo.lock
generated
2551
src/addins/mongo/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,568 +0,0 @@
|
||||
use mongodb::{options::{ClientOptions, FindOptions}, bson::{doc, Document}};
|
||||
use mongodb::sync::{Client, Collection, Database};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
pub struct MongoClient {
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl MongoClient {
|
||||
|
||||
// СЛУЖЕБНЫЕ МЕТОДЫ ----------------------------------------------------------------------------
|
||||
|
||||
// Конструктор для создания клиента
|
||||
pub fn new(uri: &str) -> MongoClient {
|
||||
match ClientOptions::parse(uri) {
|
||||
Ok(client_options) => match Client::with_options(client_options) {
|
||||
Ok(client) => MongoClient { client },
|
||||
Err(_) => MongoClient { client: Client::with_options(ClientOptions::default()).unwrap() },
|
||||
},
|
||||
Err(_) => MongoClient { client: Client::with_options(ClientOptions::default()).unwrap() },
|
||||
}
|
||||
}
|
||||
|
||||
// Вспомогательная функция для формирования JSON ответа
|
||||
fn make_response(ok: bool, data: &str) -> String {
|
||||
// Попробуем распарсить строку как JSON
|
||||
let parsed_data: Result<Value, _> = serde_json::from_str(data);
|
||||
|
||||
// Если удаётся распарсить, вставляем как объект, иначе — как строку
|
||||
let response = match parsed_data {
|
||||
Ok(json_data) => json!({
|
||||
"ok": ok,
|
||||
"data": json_data
|
||||
}),
|
||||
Err(_) => json!({
|
||||
"ok": ok,
|
||||
"data": data
|
||||
}),
|
||||
};
|
||||
|
||||
response.to_string()
|
||||
}
|
||||
|
||||
// ОСНОВНЫЕ МЕТОДЫ -----------------------------------------------------------------------------
|
||||
|
||||
// РАБОТА С БАЗАМИ ДАННЫХ ----------------------------------------------------------------------
|
||||
|
||||
// Получение списка баз
|
||||
pub fn list_databases(&self) -> String {
|
||||
match self.client.list_database_names(None, None) {
|
||||
Ok(databases) =>
|
||||
Self::make_response(
|
||||
true,
|
||||
&format!("{:?}", databases)),
|
||||
|
||||
Err(err) =>
|
||||
Self::make_response(
|
||||
false,
|
||||
&format!("Failed to list databases: {}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
// Получение статистики базы данных
|
||||
pub fn database_stats(&self, db_name: &str) -> String {
|
||||
let db = self.client.database(db_name);
|
||||
match db.run_command(doc! { "dbStats": 1 }, None) {
|
||||
Ok(stats) =>
|
||||
Self::make_response(
|
||||
true,
|
||||
&format!("{:?}", stats)),
|
||||
|
||||
Err(err) =>
|
||||
Self::make_response(
|
||||
false,
|
||||
&format!("Failed to get database stats for '{}': {}", db_name, err)),
|
||||
}
|
||||
}
|
||||
|
||||
// Удаление базы
|
||||
pub fn drop_database(&self, db_name: &str) -> String {
|
||||
match self.client.database(db_name).drop(None) {
|
||||
Ok(_) =>
|
||||
Self::make_response(
|
||||
true,
|
||||
&format!("Database '{}' dropped successfully", db_name)),
|
||||
Err(err) =>
|
||||
Self::make_response(
|
||||
false,
|
||||
&format!("Failed to drop database '{}': {}", db_name, err)),
|
||||
}
|
||||
}
|
||||
|
||||
// Проверка существования базы
|
||||
pub fn database_exists(&self, db_name: &str) -> bool {
|
||||
// Получаем список всех баз данных
|
||||
let db_list = match self.client.list_databases(None, None) {
|
||||
Ok(databases) => databases,
|
||||
Err(_) => return false, // Если ошибка при получении списка баз, возвращаем false
|
||||
};
|
||||
|
||||
// Проверяем, есть ли указанная база данных в списке
|
||||
db_list.iter().any(|db| db.name == db_name)
|
||||
}
|
||||
|
||||
// РАБОТА С КОЛЛЕКЦИЯМИ ------------------------------------------------------------------------
|
||||
|
||||
// Получение списка коллекций
|
||||
pub fn list_collections(&self, db_name: &str) -> String {
|
||||
let db: Database = self.client.database(db_name);
|
||||
|
||||
match db.list_collection_names(None) {
|
||||
Ok(collection_names) => {
|
||||
// Преобразуем список коллекций в JSON
|
||||
let collections_json = serde_json::to_string(&collection_names).unwrap();
|
||||
Self::make_response(true, &collections_json)
|
||||
}
|
||||
Err(err) => Self::make_response(false, &format!("Failed to list collections: {}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
// Создание коллекции в базе данных
|
||||
pub fn create_collection(&self, db_name: &str, collection_name: &str) -> String {
|
||||
let db = self.client.database(db_name);
|
||||
match db.create_collection(collection_name, None) {
|
||||
Ok(_) => Self::make_response(
|
||||
true,
|
||||
&format!("Collection '{}' created successfully in database '{}'", collection_name, db_name)),
|
||||
|
||||
Err(err) =>
|
||||
Self::make_response(
|
||||
false,
|
||||
&format!("Failed to create collection '{}': {}", collection_name, err)),
|
||||
}
|
||||
}
|
||||
|
||||
// Удаление коллекции из базы данных
|
||||
pub fn drop_collection(&self, db_name: &str, collection_name: &str) -> String {
|
||||
let collection = self.client.database(db_name).collection::<Document>(collection_name);
|
||||
match collection.drop(None) {
|
||||
Ok(_) =>
|
||||
Self::make_response(
|
||||
true,
|
||||
&format!("Collection '{}' dropped successfully from database '{}'", collection_name, db_name)),
|
||||
|
||||
Err(err) =>
|
||||
Self::make_response(
|
||||
false,
|
||||
&format!("Failed to drop collection '{}': {}", collection_name, err)),
|
||||
}
|
||||
}
|
||||
|
||||
// Проверка, существует ли коллекция
|
||||
pub fn collection_exists(&self, db_name: &str, collection_name: &str) -> String {
|
||||
match self.client.database(db_name).list_collection_names(None) {
|
||||
Ok(collections) => {
|
||||
if collections.contains(&collection_name.to_string()) {
|
||||
Self::make_response(
|
||||
true,
|
||||
&format!("Collection '{}' exists in database '{}'", collection_name, db_name))
|
||||
} else {
|
||||
Self::make_response(
|
||||
false,
|
||||
&format!("Collection '{}' does not exist in database '{}'", collection_name, db_name))
|
||||
}
|
||||
}
|
||||
Err(err) =>
|
||||
Self::make_response(
|
||||
false,
|
||||
&format!("Failed to list collections: {}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
// РАБОТА С ДОКУМЕНТАМИ ------------------------------------------------------------------------
|
||||
|
||||
// ДОБАВЛЕНИЕ ДОКУМЕНТОВ -----------------------------------------------------------------------
|
||||
|
||||
// Метод для вставки одного документа
|
||||
pub fn insert_data(&self, db_name: &str, collection_name: &str, data: &str) -> String {
|
||||
let collection: Collection<Document> = self.client.database(db_name).collection(collection_name);
|
||||
|
||||
let json_value: Value = match serde_json::from_str(data) {
|
||||
Ok(value) => value,
|
||||
Err(err) =>
|
||||
return Self::make_response(
|
||||
false,
|
||||
&format!("Failed to parse JSON: {}", err)),
|
||||
};
|
||||
|
||||
let bson_document = match bson::to_bson(&json_value) {
|
||||
Ok(bson) => bson,
|
||||
Err(err) =>
|
||||
return Self::make_response(
|
||||
false,
|
||||
&format!("Failed to convert to BSON: {}", err)),
|
||||
};
|
||||
|
||||
match collection.insert_one(bson_document.as_document().unwrap(), None) {
|
||||
Ok(_) =>
|
||||
Self::make_response(
|
||||
true,
|
||||
"Insert successful"),
|
||||
Err(err) =>
|
||||
Self::make_response(
|
||||
false,
|
||||
&format!("Insert failed: {}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
// Метод для вставки нескольких документов
|
||||
pub fn insert_many(&self, db_name: &str, collection_name: &str, data: &str) -> String {
|
||||
let collection: Collection<Document> = self.client.database(db_name).collection(collection_name);
|
||||
|
||||
let json_values: Vec<Value> = match serde_json::from_str(data) {
|
||||
Ok(values) => values,
|
||||
Err(err) =>
|
||||
return Self::make_response(
|
||||
false,
|
||||
&format!("Failed to parse JSON array: {}", err)),
|
||||
};
|
||||
|
||||
let bson_documents: Vec<Document> = match json_values
|
||||
.into_iter()
|
||||
.map(|value| bson::to_bson(&value))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
{
|
||||
Ok(bson_array) => bson_array
|
||||
.into_iter()
|
||||
.filter_map(|bson| bson.as_document().cloned())
|
||||
.collect(),
|
||||
Err(err) =>
|
||||
return Self::make_response(
|
||||
false,
|
||||
&format!("Failed to convert JSON to BSON: {}", err)),
|
||||
};
|
||||
|
||||
match collection.insert_many(bson_documents, None) {
|
||||
Ok(result) =>
|
||||
Self::make_response(
|
||||
true,
|
||||
&format!("Insert successful: {} documents inserted", result.inserted_ids.len())),
|
||||
Err(err) =>
|
||||
Self::make_response(
|
||||
false,
|
||||
&format!("Insert failed: {}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
// ОБНОВЛЕНИЕ ДОКУМЕНТОВ -----------------------------------------------------------------------
|
||||
|
||||
// Обновление одного документа в коллекции
|
||||
pub fn update_data(&self, db_name: &str, collection_name: &str, query: &str, update: &str) -> String {
|
||||
let collection: Collection<Document> = self.client.database(db_name).collection(collection_name);
|
||||
|
||||
// Парсим JSON в структуру Value
|
||||
let bson_query: Value = match serde_json::from_str(query) {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Self::make_response(false, &format!("Failed to parse query JSON: {}", err)),
|
||||
};
|
||||
|
||||
let bson_update: Value = match serde_json::from_str(update) {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Self::make_response(false, &format!("Failed to parse update JSON: {}", err)),
|
||||
};
|
||||
|
||||
// Преобразуем в BSON документы
|
||||
let bson_query_doc = bson::to_document(&bson_query).unwrap();
|
||||
let bson_update_doc = bson::to_document(&bson_update).unwrap();
|
||||
|
||||
// Преобразуем в UpdateModifications
|
||||
let update_modifications = doc! {
|
||||
"$set": bson_update_doc
|
||||
};
|
||||
|
||||
// Обновляем один документ
|
||||
match collection.update_one(bson_query_doc, update_modifications, None) {
|
||||
Ok(_) => Self::make_response(true, "Update successful"),
|
||||
Err(err) => Self::make_response(false, &format!("Update failed: {}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
// Обновление нескольких документов
|
||||
pub fn update_many(&self, db_name: &str, collection_name: &str, query: &str, update: &str) -> String {
|
||||
let collection: Collection<Document> = self.client.database(db_name).collection(collection_name);
|
||||
|
||||
// Парсим JSON в структуру Value
|
||||
let bson_query: Value = match serde_json::from_str(query) {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Self::make_response(false, &format!("Failed to parse query JSON: {}", err)),
|
||||
};
|
||||
|
||||
let bson_update: Value = match serde_json::from_str(update) {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Self::make_response(false, &format!("Failed to parse update JSON: {}", err)),
|
||||
};
|
||||
|
||||
// Преобразуем в BSON документы
|
||||
let bson_query_doc = bson::to_document(&bson_query).unwrap();
|
||||
let bson_update_doc = bson::to_document(&bson_update).unwrap();
|
||||
|
||||
// Преобразуем в UpdateModifications
|
||||
let update_modifications = doc! {"$set": bson_update_doc};
|
||||
|
||||
// Обновляем все документы, которые соответствуют запросу
|
||||
match collection.update_many(bson_query_doc, update_modifications, None) {
|
||||
Ok(result) => Self::make_response(
|
||||
true,
|
||||
&format!("Update successful: {} documents updated", result.modified_count)
|
||||
),
|
||||
Err(err) => Self::make_response(false, &format!("Update failed: {}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// УДАЛЕНИЕ ДОКУМЕНТОВ -------------------------------------------------------------------------
|
||||
|
||||
// Удаление одного документа
|
||||
pub fn delete_data(&self, db_name: &str, collection_name: &str, query: &str) -> String {
|
||||
let collection: Collection<Document> = self.client.database(db_name).collection(collection_name);
|
||||
|
||||
// Преобразуем строку JSON в объект
|
||||
let query_value: Value = match serde_json::from_str(query) {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Self::make_response(false, &format!("Failed to parse query JSON: {}", err)),
|
||||
};
|
||||
|
||||
// Преобразуем объект JSON в BSON
|
||||
let bson_query = match bson::to_bson(&query_value) {
|
||||
Ok(bson) => bson,
|
||||
Err(err) => return Self::make_response(false, &format!("Failed to convert query to BSON: {}", err)),
|
||||
};
|
||||
|
||||
// Проверяем, что BSON является документом
|
||||
match bson_query.as_document() {
|
||||
Some(bson_query_doc) => {
|
||||
// Удаляем один документ
|
||||
match collection.delete_one(bson_query_doc.clone(), None) {
|
||||
Ok(result) => {
|
||||
if result.deleted_count > 0 {
|
||||
Self::make_response(true, "Delete successful")
|
||||
} else {
|
||||
Self::make_response(false, "No documents matched the query")
|
||||
}
|
||||
}
|
||||
Err(err) => Self::make_response(false, &format!("Delete failed: {}", err)),
|
||||
}
|
||||
}
|
||||
None => Self::make_response(false, "Query is not a valid BSON document"),
|
||||
}
|
||||
}
|
||||
|
||||
// Удаление нескольких документов
|
||||
pub fn delete_many(&self, db_name: &str, collection_name: &str, query: &str) -> String {
|
||||
let collection: Collection<Document> = self.client.database(db_name).collection(collection_name);
|
||||
|
||||
// Преобразуем строку JSON в объект
|
||||
let query_value: Value = match serde_json::from_str(query) {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Self::make_response(false, &format!("Failed to parse query JSON: {}", err)),
|
||||
};
|
||||
|
||||
// Преобразуем объект JSON в BSON
|
||||
let bson_query = match bson::to_bson(&query_value) {
|
||||
Ok(bson) => bson,
|
||||
Err(err) => return Self::make_response(false, &format!("Failed to convert query to BSON: {}", err)),
|
||||
};
|
||||
|
||||
// Проверяем, что BSON является документом
|
||||
match bson_query.as_document() {
|
||||
Some(bson_query_doc) => {
|
||||
// Удаляем несколько документов
|
||||
match collection.delete_many(bson_query_doc.clone(), None) {
|
||||
Ok(result) => Self::make_response(
|
||||
true,
|
||||
&format!("Delete successful: {} documents deleted", result.deleted_count),
|
||||
),
|
||||
Err(err) => Self::make_response(false, &format!("Delete failed: {}", err)),
|
||||
}
|
||||
}
|
||||
None => Self::make_response(false, "Query is not a valid BSON document"),
|
||||
}
|
||||
}
|
||||
|
||||
// ПОИСК ДОКУМЕНТОВ ----------------------------------------------------------------------------
|
||||
|
||||
// Поиск одного документа
|
||||
pub fn find_data(&self, db_name: &str, collection_name: &str, query: &str) -> String {
|
||||
|
||||
let collection: Collection<Document> = self.client.database(db_name).collection(collection_name);
|
||||
let filter = doc! { "name": query };
|
||||
|
||||
match collection.find_one(filter, None) {
|
||||
Ok(Some(doc)) => Self::make_response(true, &format!("{:?}", doc)),
|
||||
Ok(None) => Self::make_response(false, "Document not found"),
|
||||
Err(err) => Self::make_response(false, &format!("Find failed: {}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
// Поиск нескольких документов с пагинацией
|
||||
pub fn find_many(&self, db_name: &str, collection_name: &str, query: &str, page: i32, page_size: i32) -> String {
|
||||
|
||||
let collection: Collection<Document> = self.client.database(db_name).collection(collection_name);
|
||||
|
||||
// Парсим JSON в структуру Value
|
||||
let bson_query: Value = match serde_json::from_str(query) {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Self::make_response(false, &format!("Failed to parse query JSON: {}", err)),
|
||||
};
|
||||
|
||||
// Преобразуем JSON в BSON документ
|
||||
let bson_query_doc = match bson::to_document(&bson_query) {
|
||||
Ok(doc) => doc,
|
||||
Err(err) => return Self::make_response(false, &format!("Failed to convert query to BSON: {}", err)),
|
||||
};
|
||||
|
||||
// Создаем параметры запроса с пагинацией
|
||||
let skip = page * page_size;
|
||||
let find_options = FindOptions::builder()
|
||||
.skip(skip as u64) // Пропустить количество документов
|
||||
.limit(page_size as i64) // Ограничить количество документов
|
||||
.build();
|
||||
|
||||
// Выполняем поиск
|
||||
let cursor = match collection.find(bson_query_doc, find_options) {
|
||||
Ok(cursor) => cursor,
|
||||
Err(err) => return Self::make_response(false, &format!("Find failed: {}", err)),
|
||||
};
|
||||
|
||||
// Собираем все документы из курсора в вектор
|
||||
let mut results = Vec::new();
|
||||
for result in cursor {
|
||||
match result {
|
||||
Ok(doc) => results.push(doc),
|
||||
Err(err) => return Self::make_response(false, &format!("Error iterating documents: {}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
// Преобразуем документы в JSON строку
|
||||
let json_results = match serde_json::to_string(&results) {
|
||||
Ok(json) => json,
|
||||
Err(err) => return Self::make_response(false, &format!("Failed to convert documents to JSON: {}", err)),
|
||||
};
|
||||
|
||||
Self::make_response(true, &json_results)
|
||||
}
|
||||
|
||||
// Поиск всех документов с пагинацией
|
||||
pub fn find_all(&self, db_name: &str, collection_name: &str, query: &str, page: i32, page_size: i32) -> String {
|
||||
|
||||
let collection: Collection<Document> = self.client.database(db_name).collection(collection_name);
|
||||
|
||||
// Парсим JSON в структуру Value
|
||||
let bson_query: Value = match serde_json::from_str(query) {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Self::make_response(false, &format!("Failed to parse query JSON: {}", err)),
|
||||
};
|
||||
|
||||
// Преобразуем JSON в BSON документ
|
||||
let bson_query_doc = match bson::to_document(&bson_query) {
|
||||
Ok(doc) => doc,
|
||||
Err(err) => return Self::make_response(false, &format!("Failed to convert query to BSON: {}", err)),
|
||||
};
|
||||
|
||||
// Создаем параметры запроса с пагинацией
|
||||
let skip = page * page_size;
|
||||
let find_options = FindOptions::builder()
|
||||
.skip(skip as u64) // Пропустить количество документов
|
||||
.limit(page_size as i64) // Ограничить количество документов
|
||||
.build();
|
||||
|
||||
// Выполняем поиск
|
||||
let cursor = match collection.find(bson_query_doc, find_options) {
|
||||
Ok(cursor) => cursor,
|
||||
Err(err) => return Self::make_response(false, &format!("Find failed: {}", err)),
|
||||
};
|
||||
|
||||
// Собираем все документы из курсора в вектор
|
||||
let mut results = Vec::new();
|
||||
for result in cursor {
|
||||
match result {
|
||||
Ok(doc) => results.push(doc),
|
||||
Err(err) => return Self::make_response(false, &format!("Error iterating documents: {}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
// Преобразуем документы в JSON строку
|
||||
let json_results = match serde_json::to_string(&results) {
|
||||
Ok(json) => json,
|
||||
Err(err) => return Self::make_response(false, &format!("Failed to convert documents to JSON: {}", err)),
|
||||
};
|
||||
|
||||
Self::make_response(true, &json_results)
|
||||
}
|
||||
|
||||
// Проверка существования документа
|
||||
pub fn document_exists(&self, db_name: &str, collection_name: &str, query: &str) -> String {
|
||||
let collection: Collection<Document> = self.client.database(db_name).collection(collection_name);
|
||||
let filter = doc! { "name": query };
|
||||
|
||||
match collection.find_one(filter, None) {
|
||||
Ok(Some(_)) => Self::make_response(true, "true"),
|
||||
Ok(None) => Self::make_response(false, "false"),
|
||||
Err(err) => Self::make_response(false, &format!("Check failed: {}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
// Количество документов в коллекции
|
||||
pub fn count_documents(&self, db_name: &str, collection_name: &str) -> String {
|
||||
let collection: Collection<Document> = self.client.database(db_name).collection(collection_name);
|
||||
|
||||
match collection.count_documents(None, None) {
|
||||
Ok(count) => Self::make_response(true, &count.to_string()),
|
||||
Err(err) => Self::make_response(false, &format!("Count failed: {}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
// ДРУГОЕ --------------------------------------------------------------------------------------
|
||||
|
||||
// Произвольный запрос
|
||||
pub fn run_custom_query(&self, db_name: &str, query: &str) -> String {
|
||||
let db = self.client.database(db_name);
|
||||
|
||||
// Парсим строку запроса в BSON
|
||||
let bson_query: Value = match serde_json::from_str(query) {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Self::make_response(false, &format!("Failed to parse query JSON: {}", err)),
|
||||
};
|
||||
|
||||
let bson_query_doc = match bson::to_document(&bson_query) {
|
||||
Ok(doc) => doc,
|
||||
Err(err) => return Self::make_response(false, &format!("Failed to convert query to BSON: {}", err)),
|
||||
};
|
||||
|
||||
// Выполнение произвольного запроса
|
||||
match db.run_command(bson_query_doc, None) {
|
||||
Ok(result) => Self::make_response(true, &format!("{:?}", result)),
|
||||
Err(err) => Self::make_response(false, &format!("Query execution failed: {}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
// Агрегация
|
||||
pub fn aggregate(&self, db_name: &str, collection_name: &str, pipeline: &str) -> String {
|
||||
let collection: Collection<Document> = self.client.database(db_name).collection(collection_name);
|
||||
|
||||
// Парсим строку JSON в массив стадий агрегации
|
||||
let bson_pipeline: Vec<Document> = match serde_json::from_str(pipeline) {
|
||||
Ok(value) => value,
|
||||
Err(err) => return Self::make_response(false, &format!("Failed to parse pipeline JSON: {}", err)),
|
||||
};
|
||||
|
||||
// Выполняем агрегацию
|
||||
let cursor = match collection.aggregate(bson_pipeline, None) {
|
||||
Ok(cursor) => cursor,
|
||||
Err(err) => return Self::make_response(false, &format!("Aggregation failed: {}", err)),
|
||||
};
|
||||
|
||||
// Собираем результат в вектор
|
||||
let mut result: Vec<Value> = Vec::new();
|
||||
for document in cursor {
|
||||
match document {
|
||||
Ok(doc) => result.push(serde_json::to_value(doc).unwrap()),
|
||||
Err(err) => return Self::make_response(false, &format!("Failed to read document: {}", err)),
|
||||
}
|
||||
}
|
||||
|
||||
// Возвращаем результат как строку
|
||||
Self::make_response(true, &serde_json::to_string(&result).unwrap())
|
||||
}
|
||||
}
|
@@ -1,226 +0,0 @@
|
||||
mod methods;
|
||||
|
||||
use addin1c::{name, Variant};
|
||||
use crate::core::getset;
|
||||
|
||||
// МЕТОДЫ КОМПОНЕНТЫ -------------------------------------------------------------------------------
|
||||
|
||||
// Синонимы
|
||||
pub const METHODS: &[&[u16]] = &[
|
||||
name!("ListDatabases"), // 0
|
||||
name!("DatabaseStats"), // 1
|
||||
name!("DropDatabase"), // 2
|
||||
name!("DatabaseExists"), // 3
|
||||
name!("ListCollections"), // 4
|
||||
name!("CreateCollection"), // 5
|
||||
name!("DropCollection"), // 6
|
||||
name!("CollectionExists"), // 7
|
||||
name!("InsertData"), // 8
|
||||
name!("InsertMany"), // 9
|
||||
name!("UpdateData"), // 10
|
||||
name!("UpdateMany"), // 11
|
||||
name!("DeleteData"), // 12
|
||||
name!("DeleteMany"), // 13
|
||||
name!("FindData"), // 14
|
||||
name!("FindMany"), // 15
|
||||
name!("FindAll"), // 16
|
||||
name!("DocumentExists"), // 17
|
||||
name!("CountDocuments"), // 18
|
||||
name!("RunCustomQuery"), // 19
|
||||
name!("Aggregate"), // 20
|
||||
];
|
||||
|
||||
// Число параметров функций компоненты
|
||||
pub fn get_params_amount(num: usize) -> usize {
|
||||
match num {
|
||||
0 => 0, // list_databases (нет параметров)
|
||||
1 => 1, // database_stats
|
||||
2 => 1, // drop_database
|
||||
3 => 1, // database_exists
|
||||
4 => 1, // list_collections
|
||||
5 => 2, // create_collection
|
||||
6 => 2, // drop_collection
|
||||
7 => 2, // collection_exists
|
||||
8 => 3, // insert_data
|
||||
9 => 3, // insert_many
|
||||
10 => 4, // update_data
|
||||
11 => 4, // update_many
|
||||
12 => 3, // delete_data
|
||||
13 => 3, // delete_many
|
||||
14 => 3, // find_data
|
||||
15 => 5, // find_many
|
||||
16 => 5, // find_all
|
||||
17 => 3, // document_exists
|
||||
18 => 2, // count_documents
|
||||
19 => 2, // run_custom_query
|
||||
20 => 3, // aggregate
|
||||
_ => 0, // по умолчанию 0
|
||||
}
|
||||
}
|
||||
|
||||
// Соответствие функций Rust функциям компоненты
|
||||
// Вызовы должны быть обернуты в Box::new
|
||||
pub fn cal_func(obj: &AddIn, num: usize, params: &mut [Variant]) -> Box<dyn crate::core::getset::ValueType> {
|
||||
|
||||
let address = &obj.address;
|
||||
let client = methods::MongoClient::new(address);
|
||||
|
||||
match num {
|
||||
0 => { // list_databases
|
||||
Box::new(client.list_databases())
|
||||
},
|
||||
1 => { // database_stats
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
Box::new(client.database_stats(&db_name))
|
||||
},
|
||||
2 => { // drop_database
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
Box::new(client.drop_database(&db_name))
|
||||
},
|
||||
3 => { // database_exists
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
Box::new(client.database_exists(&db_name))
|
||||
},
|
||||
4 => { // list_collections
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
Box::new(client.list_collections(&db_name))
|
||||
},
|
||||
5 => { // create_collection
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
let collection_name = params[1].get_string().unwrap_or(String::new());
|
||||
Box::new(client.create_collection(&db_name, &collection_name))
|
||||
},
|
||||
6 => { // drop_collection
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
let collection_name = params[1].get_string().unwrap_or(String::new());
|
||||
Box::new(client.drop_collection(&db_name, &collection_name))
|
||||
},
|
||||
7 => { // collection_exists
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
let collection_name = params[1].get_string().unwrap_or(String::new());
|
||||
Box::new(client.collection_exists(&db_name, &collection_name))
|
||||
},
|
||||
8 => { // insert_data
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
let collection_name = params[1].get_string().unwrap_or(String::new());
|
||||
let data = params[2].get_string().unwrap_or(String::new());
|
||||
Box::new(client.insert_data(&db_name, &collection_name, &data))
|
||||
},
|
||||
9 => { // insert_many
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
let collection_name = params[1].get_string().unwrap_or(String::new());
|
||||
let data = params[2].get_string().unwrap_or(String::new());
|
||||
Box::new(client.insert_many(&db_name, &collection_name, &data))
|
||||
},
|
||||
10 => { // update_data
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
let collection_name = params[1].get_string().unwrap_or(String::new());
|
||||
let query = params[2].get_string().unwrap_or(String::new());
|
||||
let update = params[3].get_string().unwrap_or(String::new());
|
||||
Box::new(client.update_data(&db_name, &collection_name, &query, &update))
|
||||
},
|
||||
11 => { // update_many
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
let collection_name = params[1].get_string().unwrap_or(String::new());
|
||||
let query = params[2].get_string().unwrap_or(String::new());
|
||||
let update = params[3].get_string().unwrap_or(String::new());
|
||||
Box::new(client.update_many(&db_name, &collection_name, &query, &update))
|
||||
},
|
||||
12 => { // delete_data
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
let collection_name = params[1].get_string().unwrap_or(String::new());
|
||||
let query = params[2].get_string().unwrap_or(String::new());
|
||||
Box::new(client.delete_data(&db_name, &collection_name, &query))
|
||||
},
|
||||
13 => { // delete_many
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
let collection_name = params[1].get_string().unwrap_or(String::new());
|
||||
let query = params[2].get_string().unwrap_or(String::new());
|
||||
Box::new(client.delete_many(&db_name, &collection_name, &query))
|
||||
},
|
||||
14 => { // find_data
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
let collection_name = params[1].get_string().unwrap_or(String::new());
|
||||
let query = params[2].get_string().unwrap_or(String::new());
|
||||
Box::new(client.find_data(&db_name, &collection_name, &query))
|
||||
},
|
||||
15 => { // find_many
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
let collection_name = params[1].get_string().unwrap_or(String::new());
|
||||
let query = params[2].get_string().unwrap_or(String::new());
|
||||
let page = params[3].get_i32().unwrap_or(0);
|
||||
let page_size = params[4].get_i32().unwrap_or(0) as i32;
|
||||
Box::new(client.find_many(&db_name, &collection_name, &query, page, page_size))
|
||||
},
|
||||
16 => { // find_all
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
let collection_name = params[1].get_string().unwrap_or(String::new());
|
||||
let query = params[2].get_string().unwrap_or(String::new());
|
||||
let page = params[3].get_i32().unwrap_or(0);
|
||||
let page_size = params[4].get_i32().unwrap_or(0);
|
||||
Box::new(client.find_all(&db_name, &collection_name, &query, page, page_size))
|
||||
},
|
||||
17 => { // document_exists
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
let collection_name = params[1].get_string().unwrap_or(String::new());
|
||||
let query = params[2].get_string().unwrap_or(String::new());
|
||||
Box::new(client.document_exists(&db_name, &collection_name, &query))
|
||||
},
|
||||
18 => { // count_documents
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
let collection_name = params[1].get_string().unwrap_or(String::new());
|
||||
Box::new(client.count_documents(&db_name, &collection_name))
|
||||
},
|
||||
19 => { // run_custom_query
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
let query = params[1].get_string().unwrap_or(String::new());
|
||||
Box::new(client.run_custom_query(&db_name, &query))
|
||||
},
|
||||
20 => { // aggregate
|
||||
let db_name = params[0].get_string().unwrap_or(String::new());
|
||||
let collection_name = params[1].get_string().unwrap_or(String::new());
|
||||
let pipeline = params[2].get_string().unwrap_or(String::new());
|
||||
Box::new(client.aggregate(&db_name, &collection_name, &pipeline))
|
||||
},
|
||||
_ => {
|
||||
Box::new(false)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
// ПОЛЯ КОМПОНЕНТЫ ---------------------------------------------------------------------------------
|
||||
|
||||
// Синонимы
|
||||
pub const PROPS: &[&[u16]] = &[
|
||||
name!("ConnectionString")
|
||||
];
|
||||
|
||||
// Имена и типы
|
||||
pub struct AddIn {
|
||||
address: String
|
||||
}
|
||||
|
||||
// Конструктор
|
||||
impl AddIn {
|
||||
|
||||
// Значения по умолчанию
|
||||
pub fn new() -> AddIn {
|
||||
AddIn {
|
||||
address: String::from("")
|
||||
}
|
||||
}
|
||||
|
||||
// Сюда просто нужно еще раз добавить имена полей
|
||||
pub fn get_field_ptr(&self, index: usize) -> *const dyn getset::ValueType {
|
||||
match index {
|
||||
0 => &self.address as &dyn getset::ValueType as *const _,
|
||||
_ => panic!("Index out of bounds"),
|
||||
}
|
||||
}
|
||||
pub fn get_field_ptr_mut(&mut self, index: usize) -> *mut dyn getset::ValueType { self.get_field_ptr(index) as *mut _ }
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
370
src/addins/tcp/Cargo.lock
generated
Normal file
370
src/addins/tcp/Cargo.lock
generated
Normal file
@@ -0,0 +1,370 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addin1c"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef34e8b7ff4c43e87491a4cc30a4779a9f67c50db43378a36362c7a56246e05b"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
"utf16_lit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.168"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opi_tcp"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"addin1c",
|
||||
"native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||
|
||||
[[package]]
|
||||
name = "utf16_lit"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14706d2a800ee8ff38c1d3edb873cd616971ea59eb7c0d046bb44ef59b06a1ae"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
@@ -1,10 +1,8 @@
|
||||
[package]
|
||||
name = "opi_mongodb"
|
||||
name = "opi_tcp"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
@@ -13,17 +11,7 @@ lto = true # Enable Link Time Optimization
|
||||
codegen-units = 1 # Reduce number of codegen units to increase optimizations.
|
||||
panic = "abort" # Abort on panic
|
||||
strip = true # Automatically strip symbols from the binary.
|
||||
opt-level = "z"
|
||||
|
||||
[dependencies]
|
||||
addin1c = "0.5.0"
|
||||
bson = "2.0"
|
||||
serde_json = "1.0.133"
|
||||
|
||||
|
||||
|
||||
[dependencies.mongodb]
|
||||
version = "=2.8.2"
|
||||
default-features = false
|
||||
features = ["sync"]
|
||||
|
||||
native-tls = "0.2.12"
|
64
src/addins/tcp/src/component/methods.rs
Normal file
64
src/addins/tcp/src/component/methods.rs
Normal file
@@ -0,0 +1,64 @@
|
||||
use std::io::{Read, Write};
|
||||
use std::net::TcpStream;
|
||||
use native_tls::TlsStream;
|
||||
use std::time::Duration;
|
||||
|
||||
pub enum Connection {
|
||||
Tcp(TcpStream),
|
||||
Tls(TlsStream<TcpStream>),
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
pub fn write(&mut self, data: &[u8]) -> std::io::Result<usize> {
|
||||
match self {
|
||||
Connection::Tcp(stream) => stream.write(data),
|
||||
Connection::Tls(stream) => stream.write(data),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(&mut self, buffer: &mut [u8]) -> std::io::Result<usize> {
|
||||
match self {
|
||||
Connection::Tcp(stream) => stream.read(buffer),
|
||||
Connection::Tls(stream) => stream.read(buffer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Отправляет данные
|
||||
pub fn send(connection: &mut Connection, data: Vec<u8>) -> bool {
|
||||
match connection.write(&data) {
|
||||
Ok(_) => true,
|
||||
Err(_) => false, // Ошибка при отправке данных
|
||||
}
|
||||
}
|
||||
|
||||
/// Считывает данные
|
||||
pub fn receive(connection: &mut Connection, buffer_size: i32, timeout_ms: i32) -> Vec<u8> {
|
||||
let mut result = Vec::new();
|
||||
let mut buffer = vec![0u8; buffer_size as usize];
|
||||
|
||||
match connection {
|
||||
Connection::Tcp(stream) => {
|
||||
stream.set_read_timeout(Some(Duration::from_millis(timeout_ms as u64))).ok();
|
||||
}
|
||||
Connection::Tls(stream) => {
|
||||
stream.get_ref().set_read_timeout(Some(Duration::from_millis(timeout_ms as u64))).ok();
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
match connection.read(&mut buffer) {
|
||||
Ok(0) => break, // Конец данных (EOF)
|
||||
Ok(size) => result.extend_from_slice(&buffer[..size]),
|
||||
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => break, // Нет данных
|
||||
Err(e) => break, // Любая другая ошибка
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Закрывает соединение
|
||||
pub fn disconnect(add_in: &mut crate::component::AddIn) {
|
||||
add_in.connection = None;
|
||||
}
|
132
src/addins/tcp/src/component/mod.rs
Normal file
132
src/addins/tcp/src/component/mod.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
mod methods;
|
||||
|
||||
use addin1c::{name, Variant};
|
||||
use crate::core::getset;
|
||||
use methods::Connection;
|
||||
use std::net::TcpStream;
|
||||
use native_tls::{TlsConnector};
|
||||
use crate::component::methods::disconnect;
|
||||
// МЕТОДЫ КОМПОНЕНТЫ -------------------------------------------------------------------------------
|
||||
|
||||
// Синонимы
|
||||
pub const METHODS: &[&[u16]] = &[
|
||||
name!("Connect"), // 0
|
||||
name!("Disconnect"), // 1
|
||||
name!("Read"), // 2
|
||||
name!("Write"), // 3
|
||||
];
|
||||
|
||||
// Число параметров функций компоненты
|
||||
pub fn get_params_amount(num: usize) -> usize {
|
||||
match num {
|
||||
0 => 0,
|
||||
1 => 0,
|
||||
2 => 2,
|
||||
3 => 1,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Соответствие функций Rust функциям компоненты
|
||||
// Вызовы должны быть обернуты в Box::new
|
||||
pub fn cal_func(obj: &mut AddIn, num: usize, params: &mut [Variant]) -> Box<dyn getset::ValueType> {
|
||||
match num {
|
||||
0 => Box::new(obj.connect()),
|
||||
1 => {
|
||||
disconnect(obj);
|
||||
Box::new(true) // Возвращаем true для обозначения успешного выполнения
|
||||
},
|
||||
2 => {
|
||||
let size = params[0].get_i32().unwrap_or(0);
|
||||
let timeout = params[1].get_i32().unwrap_or(0);
|
||||
|
||||
if let Some(ref mut connection) = obj.connection {
|
||||
Box::new(methods::receive(connection, size, timeout))
|
||||
} else {
|
||||
Box::new(Vec::<u8>::new()) // Если соединения нет, возвращаем пустой массив
|
||||
}
|
||||
},
|
||||
3 => {
|
||||
let empty_array: [u8; 0] = [];
|
||||
let data = params[0].get_blob().unwrap_or(&empty_array);
|
||||
if let Some(ref mut connection) = obj.connection {
|
||||
Box::new(methods::send(connection, data.to_vec()))
|
||||
} else {
|
||||
Box::new(false) // Если соединения нет, возвращаем false
|
||||
}
|
||||
},
|
||||
_ => Box::new(false), // Неверный номер команды
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------------------
|
||||
|
||||
// ПОЛЯ КОМПОНЕНТЫ ---------------------------------------------------------------------------------
|
||||
|
||||
// Синонимы
|
||||
pub const PROPS: &[&[u16]] = &[
|
||||
name!("Address"),
|
||||
name!("SSL")
|
||||
];
|
||||
|
||||
|
||||
pub struct AddIn {
|
||||
pub address: String,
|
||||
pub use_ssl: bool,
|
||||
connection: Option<Connection>,
|
||||
}
|
||||
|
||||
impl AddIn {
|
||||
/// Создает новый объект
|
||||
pub fn new() -> Self {
|
||||
AddIn {
|
||||
address: String::new(),
|
||||
use_ssl: false,
|
||||
connection: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Подключается к серверу
|
||||
pub fn connect(&mut self) -> bool {
|
||||
if self.address.is_empty() {
|
||||
return false; // Ошибка: пустой адрес
|
||||
}
|
||||
|
||||
if self.use_ssl {
|
||||
let connector = match TlsConnector::new() {
|
||||
Ok(conn) => conn,
|
||||
Err(_) => return false, // Ошибка при создании TLS-коннектора
|
||||
};
|
||||
let stream = match TcpStream::connect(&self.address) {
|
||||
Ok(s) => s,
|
||||
Err(_) => return false, // Ошибка при подключении
|
||||
};
|
||||
match connector.connect(&self.address, stream) {
|
||||
Ok(tls_stream) => {
|
||||
self.connection = Some(Connection::Tls(tls_stream));
|
||||
true
|
||||
}
|
||||
Err(_) => false, // Ошибка при установлении TLS
|
||||
}
|
||||
} else {
|
||||
match TcpStream::connect(&self.address) {
|
||||
Ok(tcp_stream) => {
|
||||
self.connection = Some(Connection::Tcp(tcp_stream));
|
||||
true
|
||||
}
|
||||
Err(_) => false, // Ошибка при подключении
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_field_ptr(&self, index: usize) -> *const dyn getset::ValueType {
|
||||
match index {
|
||||
0 => &self.address as &dyn getset::ValueType as *const _,
|
||||
1 => &self.use_ssl as &dyn getset::ValueType as *const _,
|
||||
_ => panic!("Index out of bounds"),
|
||||
}
|
||||
}
|
||||
pub fn get_field_ptr_mut(&mut self, index: usize) -> *mut dyn getset::ValueType { self.get_field_ptr(index) as *mut _ }
|
||||
}
|
||||
// -------------------------------------------------------------------------------------------------
|
@@ -31,7 +31,7 @@ pub unsafe extern "C" fn DestroyObject(component: *mut *mut c_void) -> c_long {
|
||||
#[no_mangle]
|
||||
pub extern "C" fn GetClassNames() -> *const u16 {
|
||||
// small strings for performance
|
||||
name!("Main").as_ptr()
|
||||
name!("Client").as_ptr()
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
@@ -31,7 +31,7 @@ pub unsafe extern "C" fn DestroyObject(component: *mut *mut c_void) -> c_long {
|
||||
#[no_mangle]
|
||||
pub extern "C" fn GetClassNames() -> *const u16 {
|
||||
// small strings for performance
|
||||
name!("Client").as_ptr()
|
||||
name!("Main").as_ptr()
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
|
@@ -47,7 +47,7 @@
|
||||
#Область УправлениеБазамиДанных
|
||||
|
||||
// Получить список баз
|
||||
// Получает список баз
|
||||
// Получает список баз данных
|
||||
//
|
||||
// Примечание:
|
||||
// Метод в документации MongoDB: [listDatabases](@mongodb.com/docs/manual/reference/command/listDatabases/)
|
||||
@@ -68,6 +68,110 @@
|
||||
|
||||
КонецФункции
|
||||
|
||||
// Получить статистику базы
|
||||
// Получает информацию о базе данных
|
||||
//
|
||||
// Примечание:
|
||||
// Метод в документации MongoDB: [dbStats](@mongodb.com/docs/manual/reference/command/dbStats/)
|
||||
//
|
||||
// Параметры:
|
||||
// СтрокаПодключения - Строка - Строка подключения к серверу MongoDB - connect
|
||||
// БазаДанных - Строка - Имя базы данных - db
|
||||
//
|
||||
// Возвращаемое значение:
|
||||
// Структура Из КлючИЗначение - сериализованный JSON ответа от сервера MongoDB
|
||||
Функция ПолучитьСтатистикуБазы(Знач СтрокаПодключения, Знач БазаДанных) Экспорт
|
||||
|
||||
OPI_ПреобразованиеТипов.ПолучитьСтроку(БазаДанных);
|
||||
|
||||
Клиент = КлиентMongoDB(СтрокаПодключения);
|
||||
|
||||
Ответ = Клиент.DatabaseStats(БазаДанных);
|
||||
Ответ = OPI_Инструменты.JsonВСтруктуру(Ответ, Ложь);
|
||||
|
||||
Возврат Ответ;
|
||||
|
||||
КонецФункции
|
||||
|
||||
// База данных существует
|
||||
// Проверяет существование базы данных по имени
|
||||
//
|
||||
// Параметры:
|
||||
// СтрокаПодключения - Строка - Строка подключения к серверу MongoDB - connect
|
||||
// БазаДанных - Строка - Имя базы данных - db
|
||||
//
|
||||
// Возвращаемое значение:
|
||||
// Структура Из КлючИЗначение - сериализованный JSON ответа от сервера MongoDB
|
||||
Функция БазаДанныхСуществует(Знач СтрокаПодключения, Знач БазаДанных) Экспорт
|
||||
|
||||
OPI_ПреобразованиеТипов.ПолучитьСтроку(БазаДанных);
|
||||
|
||||
Клиент = КлиентMongoDB(СтрокаПодключения);
|
||||
|
||||
Ответ = Клиент.DatabaseExists(БазаДанных);
|
||||
Ответ = OPI_Инструменты.JsonВСтруктуру(Ответ, Ложь);
|
||||
|
||||
Возврат Ответ;
|
||||
|
||||
КонецФункции
|
||||
|
||||
// Удалить базу
|
||||
// Удаляет базу данных
|
||||
//
|
||||
// Примечание:
|
||||
// Метод в документации MongoDB: [db.dropDatabase](@mongodb.com/docs/manual/reference/method/db.dropDatabase/)
|
||||
//
|
||||
// Параметры:
|
||||
// СтрокаПодключения - Строка - Строка подключения к серверу MongoDB - connect
|
||||
// БазаДанных - Строка - Имя базы данных - db
|
||||
//
|
||||
// Возвращаемое значение:
|
||||
// Структура Из КлючИЗначение - сериализованный JSON ответа от сервера MongoDB
|
||||
Функция УдалитьБазу(Знач СтрокаПодключения, Знач БазаДанных) Экспорт
|
||||
|
||||
OPI_ПреобразованиеТипов.ПолучитьСтроку(БазаДанных);
|
||||
|
||||
Клиент = КлиентMongoDB(СтрокаПодключения);
|
||||
|
||||
Ответ = Клиент.DropDatabase(БазаДанных);
|
||||
Ответ = OPI_Инструменты.JsonВСтруктуру(Ответ, Ложь);
|
||||
|
||||
Возврат Ответ;
|
||||
|
||||
КонецФункции
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область УправлениеКоллекциями
|
||||
|
||||
// Создать коллекцию
|
||||
// Создает пустую коллекцию в выбранной базе
|
||||
//
|
||||
// Примечание:
|
||||
// Если база с именем, указанным в параметре БазаДанных, отсутствует, то она будет создана
|
||||
// Метод в документации MongoDB: [db.createCollection](@mongodb.com/docs/manual/reference/method/db.createCollection/)
|
||||
//
|
||||
// Параметры:
|
||||
// СтрокаПодключения - Строка - Строка подключения к серверу MongoDB - connect
|
||||
// БазаДанных - Строка - Имя базы данных - db
|
||||
// Наименование - Строка - Имя коллекции - cl
|
||||
//
|
||||
// Возвращаемое значение:
|
||||
// Структура Из КлючИЗначение - сериализованный JSON ответа от сервера MongoDB
|
||||
Функция СоздатьКоллекцию(Знач СтрокаПодключения, Знач БазаДанных, Знач Наименование) Экспорт
|
||||
|
||||
OPI_ПреобразованиеТипов.ПолучитьСтроку(БазаДанных);
|
||||
OPI_ПреобразованиеТипов.ПолучитьСтроку(Наименование);
|
||||
|
||||
Клиент = КлиентMongoDB(СтрокаПодключения);
|
||||
|
||||
Ответ = Клиент.CreateCollection(БазаДанных, Наименование);
|
||||
Ответ = OPI_Инструменты.JsonВСтруктуру(Ответ, Ложь);
|
||||
|
||||
Возврат Ответ;
|
||||
|
||||
КонецФункции
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#КонецОбласти
|
||||
@@ -78,7 +182,8 @@
|
||||
|
||||
OPI_ПреобразованиеТипов.ПолучитьСтроку(СтрокаПодключения);
|
||||
|
||||
Клиент = OPI_Инструменты.ПолучитьКомпоненту("MongoDB");
|
||||
Клиент = OPI_Инструменты.ПолучитьКомпоненту("MongoDB");
|
||||
|
||||
Клиент.ConnectionString = СтрокаПодключения;
|
||||
|
||||
Возврат Клиент;
|
||||
|
@@ -262,6 +262,7 @@
|
||||
НовыйТест(ТаблицаТестов, "AWS_РаботаСБакетами" , "Работа с бакетами" , S3_);
|
||||
НовыйТест(ТаблицаТестов, "AWS_РаботаСОбъектами" , "Работа с объектами" , S3_);
|
||||
НовыйТест(ТаблицаТестов, "Mongo_УправлениеБазамиДанных" , "Управление базами данных" , Монго);
|
||||
НовыйТест(ТаблицаТестов, "Mongo_УправлениеКоллекциями" , "Управление коллекциями" , Монго);
|
||||
|
||||
Возврат ТаблицаТестов;
|
||||
|
||||
|
@@ -2178,9 +2178,27 @@
|
||||
|
||||
ПараметрыТеста = Новый Структура;
|
||||
OPI_ПолучениеДанныхТестов.ПараметрВКоллекцию("MDB_CString", ПараметрыТеста);
|
||||
|
||||
СтрокаПодключения = ПараметрыТеста["MDB_CString"];
|
||||
БазаДанных = "testbase";
|
||||
Наименование = "testcollection";
|
||||
|
||||
MongoDB_ПолучитьСписокБаз(ПараметрыТеста);
|
||||
OPI_MongoDB.СоздатьКоллекцию(СтрокаПодключения, БазаДанных, Наименование);
|
||||
|
||||
MongoDB_ПолучитьСписокБаз(ПараметрыТеста);
|
||||
MongoDB_ПолучитьСтатистикуБазы(ПараметрыТеста);
|
||||
MongoDB_БазаДанныхСуществует(ПараметрыТеста);
|
||||
MongoDB_УдалитьБазу(ПараметрыТеста);
|
||||
|
||||
КонецПроцедуры
|
||||
|
||||
Процедура Mongo_УправлениеКоллекциями() Экспорт
|
||||
|
||||
ПараметрыТеста = Новый Структура;
|
||||
OPI_ПолучениеДанныхТестов.ПараметрВКоллекцию("MDB_CString", ПараметрыТеста);
|
||||
|
||||
MongoDB_СоздатьКоллекцию(ПараметрыТеста);
|
||||
|
||||
КонецПроцедуры
|
||||
|
||||
#КонецОбласти
|
||||
@@ -15677,6 +15695,59 @@
|
||||
|
||||
КонецПроцедуры
|
||||
|
||||
Процедура MongoDB_ПолучитьСтатистикуБазы(ПараметрыФункции)
|
||||
|
||||
СтрокаПодключения = ПараметрыФункции["MDB_CString"];
|
||||
БазаДанных = "testbase";
|
||||
|
||||
Результат = OPI_MongoDB.ПолучитьСтатистикуБазы(СтрокаПодключения, БазаДанных);
|
||||
|
||||
// END
|
||||
|
||||
OPI_ПолучениеДанныхТестов.ЗаписатьЛог(Результат, "ПолучитьСтатистикуБазы", "MongoDB");
|
||||
|
||||
КонецПроцедуры
|
||||
|
||||
Процедура MongoDB_БазаДанныхСуществует(ПараметрыФункции)
|
||||
|
||||
СтрокаПодключения = ПараметрыФункции["MDB_CString"];
|
||||
БазаДанных = "testbase";
|
||||
|
||||
Результат = OPI_MongoDB.БазаДанныхСуществует(СтрокаПодключения, БазаДанных);
|
||||
|
||||
// END
|
||||
|
||||
OPI_ПолучениеДанныхТестов.ЗаписатьЛог(Результат, "БазаДанныхСуществует", "MongoDB");
|
||||
|
||||
КонецПроцедуры
|
||||
|
||||
Процедура MongoDB_УдалитьБазу(ПараметрыФункции)
|
||||
|
||||
СтрокаПодключения = ПараметрыФункции["MDB_CString"];
|
||||
БазаДанных = "testbase";
|
||||
|
||||
Результат = OPI_MongoDB.УдалитьБазу(СтрокаПодключения, БазаДанных);
|
||||
|
||||
// END
|
||||
|
||||
OPI_ПолучениеДанныхТестов.ЗаписатьЛог(Результат, "УдалитьБазу", "MongoDB");
|
||||
|
||||
КонецПроцедуры
|
||||
|
||||
Процедура MongoDB_СоздатьКоллекцию(ПараметрыФункции)
|
||||
|
||||
СтрокаПодключения = ПараметрыФункции["MDB_CString"];
|
||||
БазаДанных = "testbase";
|
||||
Наименование = "testcollection";
|
||||
|
||||
Результат = OPI_MongoDB.СоздатьКоллекцию(СтрокаПодключения, БазаДанных, Наименование);
|
||||
|
||||
// END
|
||||
|
||||
OPI_ПолучениеДанныхТестов.ЗаписатьЛог(Результат, "СоздатьКоллекцию", "MongoDB");
|
||||
|
||||
КонецПроцедуры
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#КонецОбласти
|
||||
|
Reference in New Issue
Block a user