From 0b9ef44469fb9bb8bd4cb1d76c3db5bd3c9a05ac Mon Sep 17 00:00:00 2001 From: Markov Andrey Date: Tue, 30 Jun 2026 09:04:48 +0000 Subject: [PATCH] Add new file --- core/commands/global_remove.py | 107 +++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 core/commands/global_remove.py diff --git a/core/commands/global_remove.py b/core/commands/global_remove.py new file mode 100644 index 0000000..cb7214c --- /dev/null +++ b/core/commands/global_remove.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +""" +Команда !global_remove – удаление документа из глобальной базы знаний по его названию. +Только для администраторов, работает только в личном чате. + +Изменения для PostgreSQL (asyncpg): +- Замена курсоров на прямые запросы conn.fetch / conn.execute. +- Использование параметризованных запросов с $1, $2 и ANY($1) для массовых операций. +- Удаление синтаксиса, специфичного для MySQL/MariaDB. +""" + +import logging +from core.commands.base import Command + +logger = logging.getLogger(__name__) + + +class GlobalRemoveCommand(Command): + """Удаляет глобальный документ по точному совпадению source_name.""" + + name = "!global_remove" + aliases = ["/global_remove"] + permission = "admin" + + async def execute(self, msg, args: str, user_jid: str, room_jid: str = None): + """ + Алгоритм: + 1. Проверка прав администратора. + 2. Проверка, что команда вызвана в личном чате (room_jid is None). + 3. Разбор аргументов, извлечение названия документа (в кавычках или без). + 4. Поиск всех глобальных документов с таким source_name. + 5. Удаление найденных документов из Qdrant (по doc_id). + 6. Удаление записей из таблиц document_access и documents в PostgreSQL. + """ + # 1. Проверка прав администратора + if not self.check_permission(user_jid): + reply = msg.reply("⛔ У вас нет прав на удаление глобальных документов.") + if reply: + reply.send() # исправлено + else: + logger.error("Не удалось отправить сообщение о недостатке прав для global_remove") + return + + # 2. Команда работает только в личном чате + if room_jid: + reply = msg.reply("⚠️ Команда !global_remove работает только в личном чате.") + if reply: + reply.send() # исправлено + else: + logger.error("Не удалось отправить сообщение об ограничении !global_remove") + return + + # 3. Разбор аргументов команды + _, flags, pos = self.parse_args(args) + if not pos: + reply = msg.reply("❌ Укажите название документа для удаления. Пример: `!global_remove \"ГОСТ Р 12345\"`") + if reply: + reply.send() # исправлено + else: + logger.error("Не удалось отправить сообщение о необходимости указать название документа") + return + + source_name = " ".join(pos).strip() + # Удаляем возможные кавычки, если пользователь заключил название в них + if source_name.startswith('"') and source_name.endswith('"'): + source_name = source_name[1:-1] + + # 4. Поиск глобальных документов с указанным названием + async with self.bot.db.pool.acquire() as conn: + rows = await conn.fetch( + "SELECT id FROM documents WHERE is_global = TRUE AND source_name = $1", + source_name + ) + doc_ids = [row['id'] for row in rows] + + if not doc_ids: + reply = msg.reply(f"📭 Глобальный документ с названием '{source_name}' не найден.") + if reply: + reply.send() # исправлено + else: + logger.error("Не удалось отправить сообщение о ненайденном глобальном документе") + return + + # 5. Удаление из Qdrant (по каждому doc_id) + for doc_id in doc_ids: + self.bot.qdrant.delete_by_doc_id(doc_id) + logger.debug(f"Удалён из Qdrant doc_id={doc_id}") + + # 6. Удаление из PostgreSQL (сначала связанные права доступа, затем сам документ) + async with self.bot.db.pool.acquire() as conn: + # Удаляем записи о доступе (если есть) + await conn.execute("DELETE FROM document_access WHERE doc_id = ANY($1)", doc_ids) + # Удаляем сами документы + await conn.execute("DELETE FROM documents WHERE id = ANY($1)", doc_ids) + + logger.info(f"Администратор {user_jid} удалил глобальный документ '{source_name}' (doc_ids: {doc_ids})") + reply = msg.reply(f"✅ Глобальный документ '{source_name}' удалён из базы знаний.") + if reply: + reply.send() # исправлено + else: + logger.error("Не удалось отправить подтверждение удаления глобального документа") + + def get_help(self) -> str: + return ( + "Удаляет глобальный документ по названию (админ, только в личном чате). " + "Использование: `!global_remove \"Название документа\"`" + ) \ No newline at end of file