# -*- 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 \"Название документа\"`" )