129 lines
4.7 KiB
Python
129 lines
4.7 KiB
Python
"""Репозиторий шаблонов сообщений (Блок 4.1: редактирование в админке).
|
||
|
||
Каждое изменение текста пишет запись в ``message_template_history`` — это аудит
|
||
и материал для отката. Бот подключится к этим данным в Блоке 4.5.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
from common.db.pool import get_pool
|
||
|
||
|
||
async def load_texts() -> dict[str, str]:
|
||
"""Актуальные тексты включённых шаблонов (для кеша бота в ``common.texts.store``)."""
|
||
async with get_pool().connection() as conn:
|
||
cur = await conn.execute("SELECT key, text FROM bot.message_templates WHERE enabled ORDER BY key")
|
||
rows = await cur.fetchall()
|
||
return {row["key"]: row["text"] for row in rows}
|
||
|
||
|
||
async def list_templates() -> list[dict[str, Any]]:
|
||
"""Вернуть список шаблонов без текста (для таблицы в админке)."""
|
||
async with get_pool().connection() as conn:
|
||
cur = await conn.execute(
|
||
"""
|
||
SELECT key, description, enabled, updated_at, updated_by,
|
||
length(text) AS text_length
|
||
FROM bot.message_templates
|
||
ORDER BY key
|
||
"""
|
||
)
|
||
return await cur.fetchall()
|
||
|
||
|
||
async def get_template(key: str) -> dict[str, Any] | None:
|
||
"""Вернуть шаблон целиком (с текстом) или ``None``."""
|
||
async with get_pool().connection() as conn:
|
||
cur = await conn.execute(
|
||
"""
|
||
SELECT key, text, description, enabled, updated_at, updated_by
|
||
FROM bot.message_templates
|
||
WHERE key = %s
|
||
""",
|
||
(key,),
|
||
)
|
||
return await cur.fetchone()
|
||
|
||
|
||
async def create_template(
|
||
key: str,
|
||
text: str,
|
||
description: str,
|
||
*,
|
||
enabled: bool = True,
|
||
editor_id: int | None = None,
|
||
) -> bool:
|
||
"""Создать новый шаблон. False — если ключ уже занят."""
|
||
async with get_pool().connection() as conn:
|
||
cur = await conn.execute("SELECT 1 FROM bot.message_templates WHERE key = %s", (key,))
|
||
if await cur.fetchone() is not None:
|
||
return False
|
||
|
||
await conn.execute(
|
||
"""
|
||
INSERT INTO bot.message_templates (key, text, description, enabled, updated_by)
|
||
VALUES (%s, %s, %s, %s, %s)
|
||
""",
|
||
(key, text, description, enabled, editor_id),
|
||
)
|
||
await conn.execute(
|
||
"""
|
||
INSERT INTO bot.message_template_history (key, old_text, new_text, editor_id)
|
||
VALUES (%s, NULL, %s, %s)
|
||
""",
|
||
(key, text, editor_id),
|
||
)
|
||
return True
|
||
|
||
|
||
async def update_template(key: str, text: str, *, editor_id: int | None = None) -> dict[str, Any] | None:
|
||
"""Обновить текст шаблона и записать историю.
|
||
|
||
Возвращает ``None``, если шаблона нет. Старое значение читается под
|
||
``FOR UPDATE``, поэтому параллельные правки не потеряют историю.
|
||
"""
|
||
async with get_pool().connection() as conn:
|
||
cur = await conn.execute("SELECT text FROM bot.message_templates WHERE key = %s FOR UPDATE", (key,))
|
||
row = await cur.fetchone()
|
||
if row is None:
|
||
return None
|
||
|
||
old_text = row["text"]
|
||
if old_text == text:
|
||
return {"key": key, "old_text": old_text, "text": text, "changed": False}
|
||
|
||
await conn.execute(
|
||
"""
|
||
UPDATE bot.message_templates
|
||
SET text = %s, updated_at = now(), updated_by = %s
|
||
WHERE key = %s
|
||
""",
|
||
(text, editor_id, key),
|
||
)
|
||
await conn.execute(
|
||
"""
|
||
INSERT INTO bot.message_template_history (key, old_text, new_text, editor_id)
|
||
VALUES (%s, %s, %s, %s)
|
||
""",
|
||
(key, old_text, text, editor_id),
|
||
)
|
||
return {"key": key, "old_text": old_text, "text": text, "changed": True}
|
||
|
||
|
||
async def template_history(key: str, *, limit: int = 10) -> list[dict[str, Any]]:
|
||
"""Последние изменения шаблона (для блока истории в редакторе)."""
|
||
async with get_pool().connection() as conn:
|
||
cur = await conn.execute(
|
||
"""
|
||
SELECT id, old_text, new_text, editor_id, changed_at
|
||
FROM bot.message_template_history
|
||
WHERE key = %s
|
||
ORDER BY changed_at DESC
|
||
LIMIT %s
|
||
""",
|
||
(key, limit),
|
||
)
|
||
return await cur.fetchall()
|