initial commit

This commit is contained in:
2025-12-25 15:25:46 +03:00
commit 58827ac124
20 changed files with 1854 additions and 0 deletions

459
CLAUDE.md Normal file
View File

@@ -0,0 +1,459 @@
```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 и тд.)