The file no longer contains Firebase-specific code — only the TokenVerifier interface. Rename to reflect its actual purpose. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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 |