Современное приложение с Telegram ботом и веб-интерфейсом для просмотра и взаимодействия с фотографиями котов через The Cat API.
- 🚀 Особенности
- 💻 Технологический стек
- 🔧 Установка
- ⚙️ Конфигурация
- 🗄️ База данных
- 📊 API Документация
- 🤖 Telegram Bot
- 🚀 Развертывание
- 🎲 Случайные коты с детальной информацией о породах
- ❤️ Система лайков с персонализацией для пользователей
- 🏆 Лидерборд самых популярных пород
- 🔍 Умный поиск по характеристикам породы
- 📱 Dual Interface: Telegram бот + веб-интерфейс
- 🔄 Real-time обновления через WebSocket
- 👤 Telegram авторизация в веб-интерфейсе
Backend:
- Bun v1.0+ - JavaScript runtime с встроенным WebSocket
- Hono v4 - лёгкий веб-фреймворк
- Telegraf.js v4.11.2 - Telegram Bot Framework
- Prisma ORM v6.16.2 - типобезопасная работа с БД
- Awilix v12.0.0 - Dependency Injection
База данных:
- PostgreSQL (продакшен) / SQLite (dev) через Prisma
- Redis v5.8.2 - сессии и rate limiting (опционально)
Безопасность:
- Web Crypto API - криптография (HMAC, nonce)
- CSRF защита с timing-safe сравнением
- Rate limiting с Redis/in-memory fallback
- CSP, CORS, Security Headers
Frontend:
-
Tailwind CSS (local build) - стилизация (ранее использовался CDN, теперь компилируется локально). Для Tailwind v4 рекомендуется установить плагин Vite:
bun install -d tailwindcss @tailwindcss/vite
-
Vanilla JavaScript - интерактивность
-
WebSocket - real-time обновления
- Bun v1.0.0 или выше
-
Клонируйте репозиторий:
git clone https://github.com/kotru21/cat-api-telegraf.git cd cat-api-telegraf -
Установите зависимости:
bun install
-
Настройте переменные окружения:
Скопируйте
.env.exampleв.envи заполните:# Обязательные CATAPI_KEY=your-cat-api-key-from-thecatapi.com SESSION_SECRET=your-strong-random-secret-min-10-chars # Опциональные (значения по умолчанию) PORT=5200 WEBSITE_URL=http://localhost WEB_ENABLED=true BOT_ENABLED=false BOT_TOKEN=your-telegram-bot-token NODE_ENV=development DATABASE_URL=file:./prisma/main.db # Redis (опционально) REDIS_ENABLED=false # REDIS_URL=redis://localhost:6379
-
Настройте базу данных:
bun run prisma:generate bun run prisma:migrate:dev
-
Запустите приложение:
bun start
Дополнительно: локально можно запускать только сбор CSS (при разработке):
bun run css:watch # слежение и сбор tailwind.css
bun run css:build # один раз собрать minified tailwind.css-
The Cat API ключ:
- Зарегистрируйтесь на thecatapi.com
- Получите бесплатный API ключ
- Добавьте в
.envкакCATAPI_KEY
-
Telegram Bot Token:
- Напишите @BotFather в Telegram
- Создайте нового бота командой
/newbot - Получите токен и добавьте в
.envкакBOT_TOKEN
| Переменная | Тип | По умолчанию | Описание |
|---|---|---|---|
CATAPI_KEY |
string | обязательно | API ключ The Cat API |
PORT |
number | 5200 |
Порт HTTP/WebSocket сервера |
WEBSITE_URL |
string | http://localhost |
Базовый URL приложения |
WEB_ENABLED |
boolean | true |
Включить веб-сервер |
BOT_ENABLED |
boolean | true |
Включить Telegram бота |
BOT_TOKEN |
string | опционально* | Токен Telegram бота |
SESSION_SECRET |
string | your-secret-key-here |
Секрет для сессий |
NODE_ENV |
enum | development |
Окружение |
DATABASE_URL |
string | file:./prisma/main.db |
Строка подключения к БД |
REDIS_URL |
string | опционально** | URL Redis для сессий |
* Обязательно, если BOT_ENABLED=true
** Обязательно в продакшене
Приложение использует Prisma ORM с PostgreSQL в продакшене и SQLite для разработки.
При запуске npm start или bun start приложение автоматически:
Development (SQLite):
- Копирует
schema.sqlite.prisma→schema.prisma - Генерирует Prisma Client
- Создаёт таблицы напрямую через SQL (если их нет)
Production (PostgreSQL):
- Копирует
schema.postgres.prisma→schema.prisma - Генерирует Prisma Client
- Применяет миграции из
prisma/migrations/черезprisma migrate deploy
⚠️ Важно для Production: Перед деплоем убедитесь, что миграции для PostgreSQL созданы и закоммичены в репозиторий.
# Генерация клиента после изменения схемы
npm run prisma:generate
# Создание и применение миграций
npm run prisma:migrate:dev
# Запуск Prisma Studio (GUI для БД)
npm run prisma:studio
Если вы запускаете локально в режиме `development` с SQLite (значение `DATABASE_URL=file:./prisma/main.db` в `.env`), можно использовать удобные dev-скрипты:
```bash
# Генерация клиента для sqlite (использует prisma/schema.sqlite.prisma)
npm run prisma:generate:dev
# Создание / применение миграций локально в sqlite (внутри prisma/main.db)
npm run prisma:migrate:dev:sqlite
## 📊 API Документация
### Основные endpoints
```http
GET /api/cat/:id # Получить кота по ID
GET /api/leaderboard # Лидерборд популярных пород
GET /api/similar # Поиск по характеристикам
POST /api/like # Поставить лайк (требует авторизации)
DELETE /api/like # Убрать лайк (требует авторизации)
GET /api/profile # Профиль пользователя (требует авторизации)
POST /api/auth/telegram # Telegram авторизация
GET /healthz # Проверка состояния
GET /readyz # Проверка готовности| Команда | Описание |
|---|---|
/start |
Приветствие и главное меню |
/fact |
Случайный кот с информацией |
/mylikes |
Список лайкнутых котов |
/top |
Лидерборд популярных пород |
- ❤️ Лайк - поставить/убрать лайк коту
- 🔍 Похожие - найти котов с похожими характеристиками
- 📊 Лидерборд - посмотреть популярные породы
- 🎲 Еще кот - получить еще одного случайного кота
# Обязательные для продакшена
NODE_ENV=production
CATAPI_KEY=your-cat-api-key
SESSION_SECRET=complex-random-string-min-32-chars
DATABASE_URL=postgresql://user:pass@host:5432/db
# Redis (рекомендуется в production)
REDIS_ENABLED=true
REDIS_URL=redis://localhost:6379
# Telegram (если нужен бот)
BOT_ENABLED=true
BOT_TOKEN=your-production-bot-token
# Безопасность
WEBSITE_URL=https://yourdomain.com
## 🧩 Frontend Architecture (2025 Refactor)
Фронтенд разделён на прозрачные слои (Clean-ish layering):
```
src/web/public/js/
api.js // HTTP helper + caching
utils.js // Утилиты и константы (PLACEHOLDER, sanitize, preloadImages)
core/
state/
store.js // Pub/sub store + event bus
lifecycle.js // registerCleanup / runCleanups
services/ // ONLY: fetch -> normalize -> update store (+ TTL кэш)
LeaderboardService.js
LikesService.js
ProfileService.js
CatDetailsService.js
ui/ // Чистый DOM (render / create... без fetch)
leaderboard.js
likes.js
catDetails.js
skeleton.js
errors/
errorMapper.js
notify.js // notifyError / notifySuccess (toast + dedupe)
components/ // Отдельные переиспользуемые "виджеты"
searchAndSort.js
heroAvatars.js
pages/ // Оркестрация: подписка + вызов сервисов + lifecycle
indexPage.js
profilePage.js
catDetailsPage.js
```
Guidelines / Правила слоя:
1. pages → orchestration only (никакой логики трансформации данных, минимум DOM).
2. services → обращение к `api.js`, нормализация формата, TTL-кэш (Map / timestamps), обновление store.
3. ui → функции create*/render* без сетевых запросов; получают уже нормализованные данные.
4. errors → централизованный mapping + уведомления; исключает дублирование try/catch.
5. a11y → `aria-busy` на контейнерах, `role=list/listitem`, скрытие skeleton через `aria-hidden` (частично внедрено).
6. Никаких inline event handlers в HTML (CSP friendly) — см. раздел Security/CSP.
### Data Model (кратко)
Полное описание: `docs/data-model.md` (создано для синхронизации фронт/бэк). Ниже краткая выжимка:
| Entity | Поля (нормализованные) |
|-------------------|----------------------------------------------------------------------------------------------------------|
| LeaderboardRow | `position`, `catId`, `breedName`, `likes`, `change` (резерв), `imageUrl` |
| Like | `catId`, `breedName`, `imageUrl`, `likes` |
| CatDetails | `id`, `breedName`, `description`, `likes`, `wikipediaUrl`, `origin`, `temperament`, `lifeSpan`, `weightMetric`, `weightImperial`, `imageUrl` |
| Profile | Телеграм данные пользователя (`first_name`, `last_name`, `username`, `photo_url`) |
`catId` — единый публичный идентификатор. В переходный период сервис `normalizeRow` имеет fallback цепочку `id || breed_id || cat_id` и выводит предупреждение в dev, если идентификатора нет. План: удалить fallback после выравнивания схемы БД.
### Кэширование (TTL)
- Leaderboard: 15s
- Likes: 10s + отдельный fetch count
- Profile: 30s (см. ProfileService — если будет добавлен TTL)
- CatDetails: 30s (Map cache)
### Тестирование
`jest` + `jsdom` для юнитов (store, нормализация, UI компонентов). Запуск:
```
npm test
```
Покрытие можно расширить тестами на optimistic update (удаление лайка) и поведение skeleton.
### Security / CSP
- Убраны inline обработчики (`onerror`, `onclick`) — заменены на JS привязку.
- Используется Helmet + строгий `script-src-attr 'none'`.
- Fallback изображений через `data-fallback` + JS `error` listener.
### Roadmap / Next
- [ ] Удалить fallback цепочку `breed_id` / `cat_id` → оставить только `id`.
- [ ] Дополнить a11y: скрывать skeleton через `aria-hidden="true"`, live region для обновления лайков.
- [ ] Интегрировать реальный POST лайка на странице деталей (сейчас кнопка локально инкрементирует число).
- [ ] Расширить тесты (optimistic rollback, WebSocket stats stub).
- [ ] Вынести image preloading в общий модуль с абстракцией cancellation.
# Подготовка
heroku create your-app-name
# Использовать MongoDB Atlas (или другой MongoDB add-on) и Heroku Redis
heroku addons:create mongodbatlas # или другой MongoDB add-on (mongolab/mongoDB Atlas)
heroku addons:create heroku-redis:mini
# Переменные окружения
heroku config:set NODE_ENV=production
heroku config:set CATAPI_KEY=your-key
heroku config:set SESSION_SECRET=your-secret
heroku config:set BOT_TOKEN=your-bot-token
heroku config:set REDIS_ENABLED=true
# Если Mongo add-on устанавливает MONGODB_URI, можно скопировать его в DATABASE_URL:
heroku config:set DATABASE_URL=$(heroku config:get MONGODB_URI -a your-app-name)
# Деплой
git push heroku main- GitHub Repository: kotru21/cat-api-telegraf
- The Cat API: thecatapi.com
- Telegram Bot API: core.telegram.org/bots
⭐ Если проект был полезен, поставьте звездочку на GitHub! ⭐
2025-12: Bun Migration - Миграция с Node.js/Express на Bun/Hono. Redis теперь опционален (REDIS_ENABLED), Web Crypto API вместо Node.js crypto, SESSION_SECRET обязателен.
2025-09: Полный рефактор фронтенда (слои services/state/ui, удалены legacy компоненты, введены тесты, устранены inline handlers, консолидация идентификатора catId).