Veg Recipes

Многоплатформенная платформа вегетарианских рецептов — полноценный production-ready продукт с AI-ассистентом, геймификацией, PRO-подпиской и Telegram-ботом.
Python 88,8%
TypeScript 7,8%
HTML 1,8%
CSS 1,3%
Shell 0,3%

О проекте

VegRecipes — это экосистема для тех, кто готовит без мяса. Проект охватывает все точки взаимодействия пользователя: веб, мобильное приложение и Telegram-бот.

Ключевые возможности:
  • 🔍 Полнотекстовый поиск с русской морфологией (Elasticsearch)
  • 🤖 AI-ассистент на базе Claude API — адаптирует рецепты под ваши предпочтения и отвечает на вопросы
  • 📊 Визуализация нутриентов и витаминов (donut-чарты, radar-чарты)
  • 🗓 PRO: генерация недельного меню с учётом КБЖУ
  • 🏆 Геймификация: достижения, дуэли рецептов, дневник здоровья
  • 🌍 Интерактивная карта кухонь мира
  • 🎰 Рулетка рецептов с фильтрами
  • 📸 Сканер холодильника через камеру
  • 💳 PRO-подписка через Stripe
  • 🌐 Dynamic OG-теги для share-превью
  • 🔐 Email-верификация и сброс пароля

Архитектура

VegRecipes Ecosystem
Web (React) │ Mobile (Expo) │ Telegram Bot │ Backend (FastAPI) │ 15 страниц │ 12 экранов │ 16 команд │ 52 endpoint

REST API
FastAPI Backend │ PostgreSQL │ Redis │ Elasticsearch │ MinIO │ Celery Beat │ Stripe │ Claude API │ Sentry

Стек технологий

vegrecipes/
├── backend/ # FastAPI приложение
│ ├── main.py # App, CORS, Sentry, rate limiter
│ ├── config.py # Pydantic settings
│ ├── database.py # SQLAlchemy async engine
│ ├── models/
│ │ ├── recipe.py # Recipe, Category, Ingredient, Nutrition, Steps
│ │ ├── user.py # User, RefreshToken (+ email verify / password reset)
│ │ ├── spice.py # Spice, SpiceNutrition, SpiceCombo
│ │ └── gamification.py # Achievement, Duel, HealthLog, MealPlan
│ ├── api/
│ │ ├── limiter.py # Shared SlowAPI limiter
│ │ ├── schemas.py # Pydantic request/response models
│ │ └── routes/
│ │ ├── recipes.py # CRUD + поиск + AI-адаптация + достижения
│ │ ├── users.py # Auth, JWT, email verify, password reset
│ │ ├── categories.py
│ │ ├── spices.py
│ │ ├── meal_plan.py # PRO: генерация недельного плана
│ │ ├── payments.py # Stripe webhook + checkout
│ │ ├── duels.py # Дуэли рецептов
│ │ ├── health_log.py # Дневник здоровья + сканер холодильника
│ │ └── import_recipe.py
│ ├── services/
│ │ ├── auth.py # JWT tokens, password hashing
│ │ ├── ai_assistant.py # Claude API (адаптация, Q&A)
│ │ ├── achievements.py # 8 достижений, event-based система
│ │ ├── email.py # Async email (aiosmtplib, HTML-шаблоны)
│ │ ├── nutrition.py # Расчёт КБЖУ из ингредиентов
│ │ ├── meal_planner.py # Генерация меню с учётом КБЖУ
│ │ ├── recipe_importer.py # yt-dlp + Whisper + Claude
│ │ ├── search.py # Elasticsearch full-text
│ │ ├── health_analytics.py # Аналитика и дефициты нутриентов
│ │ └── recommender.py # Похожие рецепты, тренды, сезон, холодильник
│ ├── workers/
│ │ ├── celery_app.py # Celery + beat schedule
│ │ └── tasks.py # Дуэли, реиндексация, импорт
│ ├── alembic/ # 3 миграции БД
│ ├── seeds/
│ │ ├── seed_data.py # 1000 рецептов с нутриентами
│ │ └── run_seeds.py
│ └── tests/ # pytest, coverage > 80%

├── frontend/ # React 18 веб-приложение
│ └── src/
│ ├── pages/
│ │ ├── Home/ # Лендинг с hero, поиском, трендами
│ │ ├── RecipeList/ # Каталог: поиск + 12 фильтров + пагинация
│ │ ├── Recipe/ # Детальный рецепт: шаги, нутриенты, AI-чат
│ │ ├── Categories/ # Браузер категорий
│ │ ├── WorldMap/ # D3 интерактивная карта кухонь мира
│ │ ├── Roulette/ # Рулетка рецептов
│ │ ├── Duel/ # Дуэли рецептов
│ │ ├── Spices/ # База специй с поиском
│ │ ├── MealPlan/ # PRO: генерация недельного плана
│ │ ├── HealthLog/ # Дневник здоровья
│ │ ├── Profile/ # Профиль, достижения, избранное
│ │ ├── PRO/ # Страница подписки / Stripe Checkout
│ │ ├── Auth/ # Вход / регистрация
│ │ ├── VerifyEmail/ # Подтверждение email по токену
│ │ └── ResetPassword/ # Сброс пароля по токену
│ ├── components/
│ │ ├── NutritionDonut/ # Donut-чарт КБЖУ
│ │ ├── VitaminRadar/ # Radar-чарт витаминов
│ │ ├── SeasonWheel/ # Колесо сезонности
│ │ ├── AIChat/ # Чат с AI-ассистентом
│ │ └── RecipeCard/ # Карточка рецепта
│ └── api/ # Типизированный API клиент

├── mobile/ # Expo 51 мобильное приложение
│ └── app/
│ ├── (tabs)/ # Главная, поиск, план питания, профиль
│ ├── recipe/[id].tsx # Детальный рецепт
│ ├── fridge-scan.tsx # Сканер холодильника (реальная камера)
│ ├── duel.tsx # Дуэли рецептов
│ ├── auth.tsx # Авторизация
│ └── _layout.tsx # Root layout + Sentry init

├── bot/ # Telegram Bot
│ ├── main.py # aiogram 3.7, webhook + polling
│ ├── handlers/ # 16 команд
│ ├── config.py # Настройки + Sentry DSN
│ └── requirements.txt

├── docker-compose.yml # 9 сервисов: API, Worker, Beat, PG, Redis, ES, MinIO, Bot, Flower
├── start.sh # Полный запуск + миграции + seeds + ES reindex
├── stop.sh
└── .env.example # Шаблон переменных окружения

Структура проекта

vegrecipes/
├── backend/ # FastAPI приложение
│ ├── main.py # App, CORS, Sentry, rate limiter
│ ├── config.py # Pydantic settings
│ ├── database.py # SQLAlchemy async engine
│ ├── models/
│ │ ├── recipe.py # Recipe, Category, Ingredient, Nutrition, Steps
│ │ ├── user.py # User, RefreshToken (+ email verify / password reset)
│ │ ├── spice.py # Spice, SpiceNutrition, SpiceCombo
│ │ └── gamification.py # Achievement, Duel, HealthLog, MealPlan
│ ├── api/
│ │ ├── limiter.py # Shared SlowAPI limiter
│ │ ├── schemas.py # Pydantic request/response models
│ │ └── routes/
│ │ ├── recipes.py # CRUD + поиск + AI-адаптация + достижения
│ │ ├── users.py # Auth, JWT, email verify, password reset
│ │ ├── categories.py
│ │ ├── spices.py
│ │ ├── meal_plan.py # PRO: генерация недельного плана
│ │ ├── payments.py # Stripe webhook + checkout
│ │ ├── duels.py # Дуэли рецептов
│ │ ├── health_log.py # Дневник здоровья + сканер холодильника
│ │ └── import_recipe.py
│ ├── services/
│ │ ├── auth.py # JWT tokens, password hashing
│ │ ├── ai_assistant.py # Claude API (адаптация, Q&A)
│ │ ├── achievements.py # 8 достижений, event-based система
│ │ ├── email.py # Async email (aiosmtplib, HTML-шаблоны)
│ │ ├── nutrition.py # Расчёт КБЖУ из ингредиентов
│ │ ├── meal_planner.py # Генерация меню с учётом КБЖУ
│ │ ├── recipe_importer.py # yt-dlp + Whisper + Claude
│ │ ├── search.py # Elasticsearch full-text
│ │ ├── health_analytics.py # Аналитика и дефициты нутриентов
│ │ └── recommender.py # Похожие рецепты, тренды, сезон, холодильник
│ ├── workers/
│ │ ├── celery_app.py # Celery + beat schedule
│ │ └── tasks.py # Дуэли, реиндексация, импорт
│ ├── alembic/ # 3 миграции БД
│ ├── seeds/
│ │ ├── seed_data.py # 1000 рецептов с нутриентами
│ │ └── run_seeds.py
│ └── tests/ # pytest, coverage > 80%

├── frontend/ # React 18 веб-приложение
│ └── src/
│ ├── pages/
│ │ ├── Home/ # Лендинг с hero, поиском, трендами
│ │ ├── RecipeList/ # Каталог: поиск + 12 фильтров + пагинация
│ │ ├── Recipe/ # Детальный рецепт: шаги, нутриенты, AI-чат
│ │ ├── Categories/ # Браузер категорий
│ │ ├── WorldMap/ # D3 интерактивная карта кухонь мира
│ │ ├── Roulette/ # Рулетка рецептов
│ │ ├── Duel/ # Дуэли рецептов
│ │ ├── Spices/ # База специй с поиском
│ │ ├── MealPlan/ # PRO: генерация недельного плана
│ │ ├── HealthLog/ # Дневник здоровья
│ │ ├── Profile/ # Профиль, достижения, избранное
│ │ ├── PRO/ # Страница подписки / Stripe Checkout
│ │ ├── Auth/ # Вход / регистрация
│ │ ├── VerifyEmail/ # Подтверждение email по токену
│ │ └── ResetPassword/ # Сброс пароля по токену
│ ├── components/
│ │ ├── NutritionDonut/ # Donut-чарт КБЖУ
│ │ ├── VitaminRadar/ # Radar-чарт витаминов
│ │ ├── SeasonWheel/ # Колесо сезонности
│ │ ├── AIChat/ # Чат с AI-ассистентом
│ │ └── RecipeCard/ # Карточка рецепта
│ └── api/ # Типизированный API клиент

├── mobile/ # Expo 51 мобильное приложение
│ └── app/
│ ├── (tabs)/ # Главная, поиск, план питания, профиль
│ ├── recipe/[id].tsx # Детальный рецепт
│ ├── fridge-scan.tsx # Сканер холодильника (реальная камера)
│ ├── duel.tsx # Дуэли рецептов
│ ├── auth.tsx # Авторизация
│ └── _layout.tsx # Root layout + Sentry init

├── bot/ # Telegram Bot
│ ├── main.py # aiogram 3.7, webhook + polling
│ ├── handlers/ # 16 команд
│ ├── config.py # Настройки + Sentry DSN
│ └── requirements.txt

├── docker-compose.yml # 9 сервисов: API, Worker, Beat, PG, Redis, ES, MinIO, Bot, Flower
├── start.sh # Полный запуск + миграции + seeds + ES reindex
├── stop.sh
└── .env.example # Шаблон переменных окружения

API Endpoints

Рецепты (13 endpoints)

Метод

URL

Описание

GET

/api/v1/recipes

Список с пагинацией, 12 фильтров, сортировка

POST

/api/v1/recipes

Создать рецепт (auth)

GET

/api/v1/recipes/{id}

Детальный просмотр

PUT

/api/v1/recipes/{id}

Обновить (автор)

DELETE

/api/v1/recipes/{id}

Удалить (автор)

GET

/api/v1/recipes/top

Топ-100 по рейтингу

GET

/api/v1/recipes/trending

Трендовые за 7 дней

GET

/api/v1/recipes/seasonal

Сезонные по месяцу

GET

/api/v1/recipes/random

Случайный рецепт

GET

/api/v1/recipes/autocomplete

Elasticsearch автодополнение

POST

/api/v1/recipes/{id}/adapt

AI-адаптация под предпочтения

POST

/api/v1/recipes/{id}/ask

Вопрос к рецепту (AI-ассистент)

GET

/api/v1/recipes/{id}/similar

Похожие рецепты

POST

/api/v1/recipes/{id}/favorite

Добавить/убрать из избранного

POST

/api/v1/recipes/{id}/rate

Оценить рецепт



Пользователи и Auth (11 endpoints)

Метод

URL

Описание

POST

/api/v1/users/register

Регистрация (5/min rate limit)

POST

/api/v1/users/login

Вход (10/min rate limit)

POST

/api/v1/users/refresh

Обновление JWT токена

GET

/api/v1/users/me

Профиль текущего пользователя

POST

/api/v1/users/request-verification

Запросить письмо верификации

GET

/api/v1/users/verify-email

Подтвердить email по токену

POST

/api/v1/users/forgot-password

Запросить сброс пароля (3/min)

POST

/api/v1/users/reset-password

Установить новый пароль

GET

/api/v1/users/me/achievements

Мои достижения

GET

/api/v1/users/me/favorites

Избранные рецепты



PRO и Payments (4 endpoints)

Метод

URL

Описание

POST

/api/v1/payments/create-checkout

Stripe Checkout Session

POST

/api/v1/payments/webhook

Stripe webhook с проверкой подписи

POST

/api/v1/meal-plan/generate

Генерация недельного плана (PRO)

GET

/api/v1/meal-plan/my

Мои планы питания



Остальные endpoints

Модуль

Количество

Описание

Категории

3

CRUD категорий

Специи

5

База специй, поиск, комбо

Дуэли

5

Активная дуэль, голосование, история

Дневник здоровья

6

Логирование, аналитика, сканер холодильника

Импорт

1

Импорт из TikTok/YouTube через yt-dlp + Whisper

Карта мира

1

Кухни мира с геоданными

Геймификация

8 достижений

Достижение

Условие

🌱 Первый шаг

Добавить первый рецепт в избранное

❤️ Коллекционер

10 рецептов в избранном

🏆 Гурман

50 рецептов в избранном

⭐ Критик

Оценить первый рецепт

🎯 Эксперт

Оценить 20 рецептов

📔 Начало пути

Первая запись в дневнике здоровья

🔥 Неделя здоровья

7 дней подряд в дневнике

💪 Месяц здоровья

30 дней подряд в дневнике



Дуэли рецептов

Еженедельные голосования через Celery Beat. Пользователи голосуют за лучший рецепт в категории. Результаты обновляются автоматически.

Telegram Bot — команды

Команда

Описание

/start

Приветствие и главное меню

/random

Случайный рецепт

/search <запрос>

Поиск рецептов

/top

Топ-10 рецептов

/trending

Трендовые рецепты

/seasonal

Сезонные рецепты

/categories

Список категорий

/duel

Текущая дуэль

/vote <id>

Проголосовать в дуэли

/log <блюдо>

Записать приём пищи

/mylog

Мой дневник за сегодня

/plan

Недельный план (PRO)

/pro

Информация о PRO-подписке

/favorites

Мои избранные рецепты

/achievements

Мои достижения

/help

Справка по командам

Тесты

cd backend
pip install -r requirements.txt
pip install aiosqlite
pytest --cov=. --cov-report=term-missing

Покрытие > 80% для всех API endpoint.

Масштаб проекта

Метрика

Значение

Рецептов в seed-данных

1 000

API endpoints

52

Страниц (Web)

15

Экранов (Mobile)

12

Команд (Bot)

16

Моделей БД

14

Сервисов в docker-compose

9

Строк кода (est.)

~12 000

v1.1 — Security & Bug Fixes

🔒 Security & Bug Fixes — 31 issues resolved

Full code review of the backend revealed and fixed 9 critical, 15 important and 7 minor issues.
🔴 Critical (9)

#

File

Fix

1

config.py

Hardcoded SECRET_KEY = "dev-secret-key» — now raises ValueError on startup in production

2

routes/duels.py

Unlimited vote stuffing — voting now requires auth and blocks duplicate votes per user

3

routes/duels.py

Duel creation was open to anonymous users — now requires authentication

4

services/recipe_importer.py

SSRF via user-supplied URL — added strict urlparse validation + RFC1918 IP blocklist

5

services/recipe_importer.py

Blocking subprocess.run in async context — replaced with asyncio.create_subprocess_exec

6

workers/tasks.py

create_engine called on every Celery task — moved to module level with connection pool

7

routes/recipes.py

PUT /recipes/{id} returned 404 for author's own drafts (is_published=False)

8

routes/recipes.py

PUT /recipes/{id} silently ignored ingredients and steps — now replaces them correctly

9

routes/recipes.py

Pagination total was wrong — count_query now applies all the same filters as the main query



🟡 Important (15)

  • Auth: refresh_token was passed as query param → now accepted in request body (was leaking to access logs)
  • Auth: Refresh tokens could be used as access tokens — decode_token now rejects them by default
  • Payments: Stripe SDK sync calls were blocking the event loop → wrapped in asyncio.to_thread()
  • Payments: invoice.payment_failed webhook was using wrong field — PRO subscription was never deactivated on failed payment
  • Recommender: N+1 queries in fridge_match — added selectinload(Recipe.ingredients (was up to 500 extra queries)
  • Health log: /fridge-suggest and /fridge-ai were open without auth — anyone could trigger DB-heavy queries and burn Anthropic API budget
  • Recipes: /ask endpoint (Claude API) had no authentication or input length limit
  • Achievements: Race condition on concurrent achievement creation — wrapped in IntegrityError handler
  • World map: /api/v1/world-map created DB sessions manually bypassing DI — now uses Depends(get_db)
  • Elasticsearch: Client was never closed on shutdown — added await es.close() to lifespan
  • Meal planner: Calories were calculated as nutrition.calories * servings / 4 — nutrition is per 100g, formula was meaningless
  • Celery: create_weekly_duels used schedule=604800 (7 days from worker start) → crontab(day_of_week=0, hour=0) for Sunday midnight UTC
  • Celery: asyncio.run() inside Celery tasks is unreliable with gevent/eventlet pools → replaced with new_event_loop()
  • Email: TLS was only set for port 587 — port 465 (implicit TLS) was sending unencrypted

🟢 Minor (7)

  • schemas.py: new_password field made required (was = "", empty string passed validation)
  • models/recipe.py: DifficultyEnum converted to int enum so it's actually usable in queries
  • services/recommender.py: get_trending used updated_at >= since — new recipes have updated_at=NULL and were excluded; fixed with COALESCE(updated_at, created_at)
  • routes/users.py: PATCH /me params moved from query string to request body
  • alembic/0001_initial.py: downgrade() now drops tables in correct FK dependency order
  • routes/recipes.py: transliterate import moved to module level (was re-imported on every request)
  • ES client shutdown added in lifespan

📱 Mobile

  • Added metro.config.js to limit file watchers and address EMFILE errors on macOS
  • Removed broken expo-health ~1.1.1 dependency
  • Added .gitignore

🖼️ Scripts

  • Added scripts/comfyui/workflow_flux.json — FLUX.1-schnell GGUF workflow for ComfyUI API
  • Improved photo generation prompts: unified lighting, composition, and strict no-props enforcement
  • Added scripts/fetch_photos.py with Unsplash deduplication support

v1.2 — Security Hardening & Developer Experience

🔐 Security Hardening

  • docker-compose.yml — все пароли PostgreSQL и MinIO теперь через переменные окружения (${POSTGRES_PASSWORD:-...}), не хардкод
  • Elasticsearch — xpack.security.enabled=true по умолчанию
  • .env.example — SECRET_KEY заменён на инструкцию генерации (python -c "import secrets; print(secrets.token_urlsafe(64))")
  • backend/config.py — валидация ANTHROPIC_API_KEY, STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET при старте в production режиме

🛡️ Rate Limiting & Reliability

  • bot/handlers/recipes.py — Redis rate limiting для поиска: 5 запросов / 60 сек на пользователя
  • backend/workers/tasks.py — time_limit=600, max_retries=3 для долгих Celery задач
  • docker-compose.yml — healthcheck для backend и bot; bot ждёт service_healthy перед стартом

🐳 Docker

  • .dockerignore добавлен для всех сервисов: backend/, frontend/, bot/, mobile/
  • Исключены: .env, __pycache__, node_modules, .DS_Store, logs/, dist/, build/, .venv

🔧 Fixes

  • start.sh — удалён hardcoded путь разработчика; безопасный парсинг BOT_TOKEN через source

📖 Documentation

  • DEVELOPMENT.md — полное руководство по настройке проекта для новых разработчиков (prerequisites, quick start, env vars, tests, Telegram bot, mobile)

v1.3 — Bug Fixes: Auth & Profile

🐛 Bug Fixes

[high] Refresh token сломан на web и mobile
  • frontend/src/api/client.ts — interceptor слал null body + query param ?token=... вместо тела запроса
  • mobile/src/api/client.ts — interceptor слал { refresh_token } вместо { token }
  • Оба клиента теперь шлют { token } в теле запроса, как ожидает бэкенд
  • После истечения access token сессия корректно возобновляется без редиректа на /auth
[medium] Обновление профиля не сохранялось
  • frontend/src/api/index.ts — updateMe слал null body + query params вместо JSON body
  • mobile/src/api/index.ts — то же самое
  • display_name и bio теперь корректно сохраняются
[medium] Email verification link принимался бессрочно
  • backend/models/user.py — добавлено поле email_verify_expires
  • backend/api/routes/users.py — срок 7 дней устанавливается при регистрации и повторной отправке; истёкший токен возвращает 400 и очищается из БД
  • 0004_email_verify_expires.py — Alembic-миграция

Сделано с ❤️ и без мяса 🥗

Другие проекты GitHub

Проекты

Реализованные

Портфолио и услуги

Made on
Tilda