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

16 KiB
Raw Permalink Blame History

# 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 и тд.)