Files
food-ai/backend
dbastrikin 0f533ccaeb fix: enforce English canonical text in AI-generated dish and recipe data
RecognizeDish() and buildRecipePrompt() were generating text in the
user's language and storing it in base tables, violating the project
rule that base tables always hold English canonical text.

- RecognizeDish(): hardcode English for dish_name; enrichDishInBackground()
  now correctly translates FROM English into all other languages
- buildRecipePrompt(): remove langName lookup, hardcode English for all
  text fields; drop unused locale import

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 16:57:34 +02:00
..

FoodAI Backend

Go REST API с авторизацией через Firebase, JWT и PostgreSQL. Включает инфраструктуру импорта и перевода справочных данных (ингредиенты, рецепты).

Стек

  • Go 1.23 — язык
  • chi — HTTP-роутер
  • pgx / pgxpool — PostgreSQL-драйвер
  • goose — миграции
  • golang-jwt/v5 — JWT
  • Firebase Admin SDK — верификация токенов
  • cobra — CLI для команд импорта
  • generative-ai-go — Gemini API (переводы)

Требования

  • Go 1.23+
  • Docker & Docker Compose
  • goose (go install github.com/pressly/goose/v3/cmd/goose@latest)
  • Файл firebase-credentials.json (сервисный аккаунт Firebase)

Быстрый старт

1. Переменные окружения

cp .env.example .env

Отредактируйте .env:

Переменная Описание По умолчанию
DATABASE_URL DSN подключения к PostgreSQL postgres://food_ai:food_ai_local@localhost:5432/food_ai?sslmode=disable
FIREBASE_CREDENTIALS_FILE Путь к JSON-ключу сервисного аккаунта Firebase ./firebase-credentials.json
JWT_SECRET Секрет для подписи JWT
JWT_ACCESS_DURATION Время жизни access-токена 15m
JWT_REFRESH_DURATION Время жизни refresh-токена 720h
PORT Порт сервера 8080
ALLOWED_ORIGINS CORS-разрешённые источники http://localhost:3000
SPOONACULAR_API_KEY Ключ Spoonacular API (нужен для команд import)
GEMINI_API_KEY Ключ Gemini API (нужен для команд translate)

2. Запуск через Docker Compose

Поднимает PostgreSQL и собирает приложение:

make docker-up

3. Запуск локально (только БД в Docker)

# Запустить только PostgreSQL
docker compose up -d postgres

# Применить миграции
make migrate-up

# Запустить сервер
make run

Команды

Сервер и тесты

Команда Описание
make run Запустить сервер в режиме разработки
make test Unit-тесты
make test-integration Интеграционные тесты (требует Docker)
make lint Проверка через golangci-lint
make docker-up Поднять PostgreSQL + приложение
make docker-down Остановить контейнеры
make docker-logs Логи приложения
make migrate-up Применить миграции
make migrate-down Откатить последнюю миграцию
make migrate-status Статус миграций
make migrate-create name=<name> Создать новую миграцию

Импорт данных (требует SPOONACULAR_API_KEY)

Команда Описание
make import-ingredients Импортировать ~1 000 ингредиентов
make import-recipes Импортировать ~5 000 рецептов
make import-recipes-full Импортировать ~10 000 рецептов

Перевод (требует GEMINI_API_KEY)

Команда Описание
make translate-recipes Перевести рецепты на русский
make translate-ingredients Перевести топ-200 ингредиентов
make import-all Полный пайплайн: ингредиенты → рецепты → переводы

Все команды импорта идемпотентны (ON CONFLICT DO UPDATE) — можно запускать повторно. Для возобновления прерванного импорта используйте флаги --skip-queries / --offset.

CLI напрямую

# Тестовый прогон (без сохранения в БД)
go run ./cmd/import import ingredients --limit 50 --dry-run
go run ./cmd/import import recipes --count 100 --dry-run

# Возобновление импорта
go run ./cmd/import import ingredients --limit 1000 --skip-queries 10
go run ./cmd/import import recipes --count 5000 --offset 2000

# Перевод части рецептов
go run ./cmd/import translate recipes --limit 1000
go run ./cmd/import translate ingredients --top 50

API

Публичные эндпоинты

Метод Путь Описание
GET /health Проверка состояния сервера и БД
POST /auth/login Вход через Firebase ID-токен
POST /auth/refresh Обновление JWT по refresh-токену
POST /auth/logout Выход (инвалидация refresh-токена)

Защищённые эндпоинты (Bearer JWT)

Метод Путь Описание
GET /profile Получить профиль пользователя
PUT /profile Обновить профиль пользователя

Примеры запросов

Логин:

curl -X POST http://localhost:8080/auth/login \
  -H "Content-Type: application/json" \
  -d '{"firebase_token": "<FIREBASE_ID_TOKEN>"}'

Получить профиль:

curl http://localhost:8080/profile \
  -H "Authorization: Bearer <ACCESS_TOKEN>"

Обновить профиль:

curl -X PUT http://localhost:8080/profile \
  -H "Authorization: Bearer <ACCESS_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"height_cm": 180, "weight_kg": 75.5, "age": 28, "gender": "male", "activity": "moderate", "goal": "maintain"}'

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

backend/
├── cmd/
│   ├── server/         # HTTP-сервер (точка входа)
│   └── import/         # CLI для импорта и перевода данных (Cobra)
├── internal/
│   ├── auth/           # Firebase-верификация, JWT, сервис и хэндлер авторизации
│   ├── config/         # Конфигурация через переменные окружения
│   ├── database/       # Подключение к PostgreSQL (pgxpool)
│   ├── ingredient/     # Модель, репозиторий, сервис импорта ингредиентов
│   ├── middleware/     # RequestID, Logging, Recovery, CORS, Auth
│   ├── recipe/         # Модель, репозиторий, сервис импорта рецептов
│   ├── server/         # Роутер (chi)
│   ├── spoonacular/    # HTTP-клиент Spoonacular API (интерфейс + реализация)
│   ├── testutil/       # Вспомогательные утилиты для тестов
│   ├── translation/    # Gemini-переводчик, сервис батчевого перевода
│   └── user/           # Модель, репозиторий, сервис, хэндлер, расчёт калорий
├── migrations/
│   ├── 001_create_users.sql
│   ├── 002_create_ingredient_mappings.sql  # GIN-индекс по aliases
│   └── 003_create_recipes.sql              # FTS + GIN по ingredients/tags
├── .env.example
├── docker-compose.yml
├── Dockerfile
└── Makefile

Схема БД

ingredient_mappings

Канонический справочник ингредиентов. Каждая запись — один вид продукта.

Поле Тип Описание
id UUID Первичный ключ
canonical_name varchar Нормализованное EN-название (chicken_breast)
canonical_name_ru varchar Русское название (куриная грудка)
spoonacular_id integer Уникальный ID из Spoonacular
aliases JSONB Массив альтернативных названий (EN + RU)
category varchar produce, dairy, meat, seafood, grains, spices, canned, frozen, beverages, other
default_unit varchar Единица измерения по умолчанию (g, ml)
calories_per_100g decimal Нутриенты на 100 г
storage_days integer Типичный срок хранения (дни)

Индексы: GIN по aliases (поиск @>), canonical_name, category, UNIQUE по spoonacular_id.

recipes

Каталог рецептов. Заполняется из Spoonacular, переводится через Gemini.

Поле Тип Описание
id UUID Первичный ключ
source enum spoonacular, ai, user
spoonacular_id integer Уникальный ID из Spoonacular
title / title_ru varchar Название EN/RU
difficulty enum easy (≤30 мин), medium (≤60 мин), hard
ingredients JSONB Массив {spoonacular_id, mapping_id, name, amount, unit}
steps JSONB Массив {number, description, description_ru, timer_seconds}
tags JSONB ["vegetarian", "gluten-free", "meal:dinner", ...]
calories_per_serving decimal Нутриенты на порцию
avg_rating / review_count decimal/int Рейтинг (обновляется при отзывах)

Индексы: GIN по ingredients (поиск по mapping_id), GIN по tags, FTS по title + title_ru.

Тесты

# Unit-тесты (~69 тестов, ~13 сек)
make test

# Интеграционные тесты (PostgreSQL в Docker через testcontainers)
make test-integration
Пакет Unit Integration
auth 17 10
ingredient 9 5
middleware 10
recipe 12 7
translation 6
user 14 12