Files
pyproject2/CLAUDE.md
2025-12-25 15:25:46 +03:00

459 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
```markdown
# Telegram Bot "Что почитать" - Техническое задание
## Описание проекта
Telegram-бот для просмотра книг с описаниями, обложками и рейтингами. Бот является интеграцией к существующему веб-сервису на Django с общей PostgreSQL базой данных.
## Технологический стек
- **Python 3.10+**
- **aiogram 3.x** - асинхронный фреймворк для Telegram Bot API [web:1][web:3]
- **asyncpg** или **psycopg2** - для работы с PostgreSQL [web:6]
- **SQLAlchemy 2.0** (асинхронный режим) - ORM для работы с БД
- **python-dotenv** - для хранения конфигурации
## Структура проекта
```
telegram_bot/
├── .env \# Переменные окружения
├── .env.example \# Пример конфигурации
├── requirements.txt \# Зависимости
├── main.py \# Точка входа
├── config.py \# Конфигурация
├── database/
│ ├── __init__.py
│ ├── connection.py \# Подключение к БД
│ └── models.py \# SQLAlchemy модели
├── handlers/
│ ├── __init__.py
│ ├── auth.py \# Авторизация/регистрация
│ ├── books.py \# Просмотр книг
│ └── favorites.py \# Избранное
├── keyboards/
│ ├── __init__.py
│ └── inline.py \# Inline-клавиатуры[^1][^2]
├── middlewares/
│ ├── __init__.py
│ └── auth.py \# Проверка авторизации
└── utils/
├── __init__.py
└── states.py \# FSM состояния
```
## Схема базы данных (уже существует)
### Таблица: users (из Django)
- `id` - PRIMARY KEY
- `username` - уникальное имя пользователя
- `email` - уникальный email (используется для входа)
- `password` - хешированный пароль
- `telegram_id` - BigInteger, уникальный, nullable
- `first_name`, `last_name` - имя и фамилия
- остальные поля Django User
### Таблица: books_book
- `id` - PRIMARY KEY
- `title` - название книги (max 500)
- `description` - описание (TEXT)
- `cover_url` - ссылка на обложку (URL)
- `average_rating` - средний рейтинг (Decimal 0.00-5.00)
- `rating_count` - количество оценок
- `language` - язык книги
- `created_at`, `updated_at` - временные метки
### Таблица: books_author
- `id` - PRIMARY KEY
- `name` - имя автора
### Таблица: books_genre
- `id` - PRIMARY KEY
- `name` - название жанра
### Таблица: books_book_authors (ManyToMany)
- `book_id` - FK на books_book
- `author_id` - FK на books_author
### Таблица: books_book_genres (ManyToMany)
- `book_id` - FK на books_book
- `genre_id` - FK на books_genre
### Таблица: books_favorite
- `id` - PRIMARY KEY
- `user_id` - FK на users
- `book_id` - FK на books_book
- `created_at` - дата добавления
- UNIQUE(user_id, book_id)
## Функциональные требования
### 1. Авторизация и регистрация
**Команда:** `/start`
**Логика:**
- Проверить, есть ли у пользователя `telegram_id` в таблице `users`
- Если да → авторизован, показать главное меню
- Если нет → показать кнопки "Войти" и "Зарегистрироваться"
**Регистрация:**
1. Кнопка "Зарегистрироваться" → запросить email
2. Проверить уникальность email в БД
3. Запросить username (проверить уникальность)
4. Запросить пароль (минимум 8 символов)
5. Создать запись в таблице `users` с `telegram_id = message.from_user.id`
6. Хешировать пароль через `bcrypt` или `django.contrib.auth.hashers`
**Вход:**
1. Кнопка "Войти" → запросить email
2. Запросить пароль
3. Проверить credentials в БД
4. Если успешно → обновить `telegram_id` для этого пользователя
5. Показать главное меню
### 2. Главное меню (после авторизации)
Inline-клавиатура [web:7]:
- 📚 Все книги
- ⭐ Избранное
- 🔍 Поиск по жанру
- 👤 Мой профиль
### 3. Просмотр всех книг
**Запрос к БД:**
```
SELECT
b.id, b.title, b.description, b.cover_url,
b.average_rating, b.rating_count,
STRING_AGG(DISTINCT a.name, ', ') as authors,
STRING_AGG(DISTINCT g.name, ', ') as genres
FROM books_book b
LEFT JOIN books_book_authors ba ON b.id = ba.book_id
LEFT JOIN books_author a ON ba.author_id = a.id
LEFT JOIN books_book_genres bg ON b.id = bg.book_id
LEFT JOIN books_genre g ON bg.genre_id = g.id
WHERE b.deleted_at IS NULL
GROUP BY b.id
ORDER BY b.created_at DESC
LIMIT 10 OFFSET ?
```
**Формат вывода:**
```
📖 [Название книги]
✍️ Авторы: [список через запятую]
🏷 Жанры: [список через запятую]
⭐ Рейтинг: X.XX (Y оценок)
[Описание книги, максимум 200 символов]
[Картинка по cover_url]
Кнопки: [❤️ В избранное] [➡️ След. страница]
```
**Пагинация:** по 10 книг на страницу, кнопки "⬅️ Назад" / "➡️ Вперед"
### 4. Избранное
**Запрос к БД:**
```
SELECT
b.id, b.title, b.cover_url, b.average_rating,
STRING_AGG(DISTINCT a.name, ', ') as authors
FROM books_favorite f
JOIN books_book b ON f.book_id = b.id
LEFT JOIN books_book_authors ba ON b.id = ba.book_id
LEFT JOIN books_author a ON ba.author_id = a.id
WHERE f.user_id = ? AND b.deleted_at IS NULL
GROUP BY b.id, f.created_at
ORDER BY f.created_at DESC
```
**Действия:**
- Показать список избранных книг (формат как в п.3)
- Кнопка "❌ Удалить из избранного" → DELETE FROM books_favorite
### 5. Добавление в избранное
**Callback:** `add_fav:{book_id}`
**Запрос:**
```
INSERT INTO books_favorite (user_id, book_id, created_at)
VALUES (?, ?, NOW())
ON CONFLICT (user_id, book_id) DO NOTHING
```
**Ответ:** "✅ Книга добавлена в избранное!"
### 6. Поиск по жанру
1. Получить список жанров: `SELECT id, name FROM books_genre ORDER BY name`
2. Показать inline-клавиатуру с жанрами (по 2 в строке) [web:7]
3. После выбора жанра → фильтровать книги:
```
SELECT DISTINCT b.* FROM books_book b
JOIN books_book_genres bg ON b.id = bg.book_id
WHERE bg.genre_id = ? AND b.deleted_at IS NULL
```
## Технические требования
### 1. Подключение к базе данных
**Файл:** `database/connection.py`
```
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
from config import DATABASE_URL
engine = create_async_engine(DATABASE_URL, echo=False)
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
async def get_session():
async with async_session() as session:
yield session
```
**Конфигурация .env:**
```
BOT_TOKEN=your_bot_token_here
DATABASE_URL=postgresql+asyncpg://user:password@host:port/dbname
```
### 2. Middleware для проверки авторизации [web:3]
**Файл:** `middlewares/auth.py`
```
from aiogram import BaseMiddleware
from aiogram.types import Message, CallbackQuery
class AuthMiddleware(BaseMiddleware):
async def __call__(self, handler, event, data):
\# Пропускать команду /start
if isinstance(event, Message) and event.text == '/start':
return await handler(event, data)
# Проверить telegram_id в БД
user = await get_user_by_telegram_id(event.from_user.id)
if not user:
await event.answer("⛔ Сначала авторизуйтесь через /start")
return
data['user'] = user
return await handler(event, data)
```
### 3. FSM для авторизации
**Файл:** `utils/states.py`
```
from aiogram.fsm.state import State, StatesGroup
class RegistrationStates(StatesGroup):
waiting_email = State()
waiting_username = State()
waiting_password = State()
class LoginStates(StatesGroup):
waiting_email = State()
waiting_password = State()
```
### 4. Inline-клавиатуры [web:7][web:10]
**Файл:** `keyboards/inline.py`
```
from aiogram.utils.keyboard import InlineKeyboardBuilder
from aiogram.types import InlineKeyboardButton
def get_main_menu():
builder = InlineKeyboardBuilder()
builder.button(text="📚 Все книги", callback_data="books_all")
builder.button(text="⭐ Избранное", callback_data="favorites")
builder.button(text="🔍 Поиск по жанру", callback_data="search_genre")
builder.button(text="👤 Мой профиль", callback_data="profile")
builder.adjust(2, 2) \# 2 кнопки в ряду
return builder.as_markup()
```
### 5. Обработка картинок
Использовать `bot.send_photo()` с параметром `photo=cover_url` (если URL публичный).
Если картинка не загружается → показать текстовую заглушку: "🖼 [Обложка недоступна]"
## Требования к коду
1. **Асинхронность:** все функции БД и хендлеры должны быть `async/await` [web:3]
2. **Обработка ошибок:** try/except для всех запросов к БД
3. **Логирование:** использовать `logging` для отладки
4. **Типизация:** использовать type hints везде
5. **Документация:** docstrings для всех функций
6. **Безопасность:**
- Хешировать пароли через `bcrypt`
- Не логировать пароли
- SQL-инъекции предотвращаются через SQLAlchemy
7. **Простота:** избегать избыточных абстракций, код должен быть понятным
## Примеры запросов SQLAlchemy
**Получить пользователя:**
```
from sqlalchemy import select
from database.models import User
async def get_user_by_telegram_id(telegram_id: int):
async with async_session() as session:
result = await session.execute(
select(User).where(User.telegram_id == telegram_id)
)
return result.scalar_one_or_none()
```
**Получить книги с авторами (raw SQL через SQLAlchemy):**
```
from sqlalchemy import text
async def get_books_with_details(offset: int = 0, limit: int = 10):
async with async_session() as session:
query = text("""
SELECT b.id, b.title, b.description, b.cover_url,
b.average_rating, b.rating_count,
STRING_AGG(DISTINCT a.name, ', ') as authors,
STRING_AGG(DISTINCT g.name, ', ') as genres
FROM books_book b
LEFT JOIN books_book_authors ba ON b.id = ba.book_id
LEFT JOIN books_author a ON ba.author_id = a.id
LEFT JOIN books_book_genres bg ON b.id = bg.book_id
LEFT JOIN books_genre g ON bg.genre_id = g.id
WHERE b.deleted_at IS NULL
GROUP BY b.id
ORDER BY b.created_at DESC
LIMIT :limit OFFSET :offset
""")
result = await session.execute(query, {"limit": limit, "offset": offset})
return result.mappings().all()
```
## Зависимости (requirements.txt)
```
aiogram>=3.4.0
asyncpg>=0.29.0
SQLAlchemy>=2.0.0
python-dotenv>=1.0.0
bcrypt>=4.1.0
```
## Дополнительные указания
1. **Начните с файла `main.py`** - инициализация бота, диспетчера, подключение роутеров [web:8]
2. **Используйте роутеры aiogram** для группировки хендлеров по функциональности [web:2]
3. **Тестируйте каждую функцию по отдельности** перед интеграцией
4. **Используйте callback_data формата:** `action:param` (например, `add_fav:123`)
5. **Обрабатывайте длинные сообщения:** если описание книги > 1024 символов, обрезать с "..."
6. **Escape специальных символов** в Markdown, если используете `parse_mode='Markdown'`
## Важно
- НЕ модифицировать существующую Django БД
- НЕ создавать новые таблицы
- Использовать только SELECT, INSERT, UPDATE, DELETE для существующих таблиц
- Следовать naming conventions Django (таблицы `app_model`, например `books_book`)
- При обновлении `telegram_id` проверять уникальность (один Telegram аккаунт = один пользователь)
## Пример main.py
```
import asyncio
import logging
from aiogram import Bot, Dispatcher
from aiogram.fsm.storage.memory import MemoryStorage
from config import BOT_TOKEN
from handlers import auth, books, favorites
from middlewares.auth import AuthMiddleware
logging.basicConfig(level=logging.INFO)
async def main():
bot = Bot(token=BOT_TOKEN)
dp = Dispatcher(storage=MemoryStorage())
# Регистрация middleware
dp.message.middleware(AuthMiddleware())
dp.callback_query.middleware(AuthMiddleware())
# Регистрация роутеров
dp.include_router(auth.router)
dp.include_router(books.router)
dp.include_router(favorites.router)
await bot.delete_webhook(drop_pending_updates=True)
await dp.start_polling(bot)
if __name__ == '__main__':
asyncio.run(main())
```
Начни реализацию с базовой структуры проекта и авторизации!
```
Этот MD-файл содержит полную спецификацию для Cursor. Он включает:[^3][^4][^5]
- Четкую структуру проекта с организацией файлов[^6]
- Схему существующей Django БД (на основе ваших моделей)
- Детальные функциональные требования для каждой фичи
- Примеры SQL-запросов и SQLAlchemy кода[^7]
- Инструкции по работе с inline-клавиатурами aiogram[^2][^1]
- Требования к асинхронности и middleware[^5]
- Готовый пример точки входа приложения
Просто скопируйте этот контент в `.cursorrules` или отдельный MD-файл в корне проекта, и Cursor сможет генерировать код согласно спецификации!
<span style="display:none">[^10][^8][^9]</span>
<div align="center">⁂</div>
[^1]: https://docs.aiogram.dev/en/latest/utils/keyboard.html
[^2]: https://stackoverflow.com/questions/67652875/how-to-make-an-inline-keyboard-in-aiogram
[^3]: https://habr.com/ru/articles/953902/
[^4]: https://github.com/MasterGroosha/aiogram-3-guide
[^5]: https://aiogram.dev
[^6]: https://www.youtube.com/watch?v=wRatZLMa4oE
[^7]: https://stackoverflow.com/questions/75900203/how-do-i-connect-my-telegram-bot-telebot-to-postgresql-url
[^8]: https://www.youtube.com/watch?v=LufjNEv0OuQ
[^9]: https://www.youtube.com/watch?v=mNrqcTl13Wg
[^10]: https://www.youtube.com/watch?v=z5JT-bPdusY
## Дополнительные указания
- Не пиши тесты
- Не пиши объясняющие файлы любого рода(.txt,.md и тд.)