Files
food-ai/backend
dbastrikin 288bb1c375 fix: compute expires_at in SQL instead of generated column
TIMESTAMPTZ + INTERVAL is STABLE (depends on timezone), not IMMUTABLE,
so PostgreSQL rejects it in GENERATED ALWAYS AS STORED columns.
Fix: remove generated column and compute expires_at inline in each query
as (added_at + storage_days * INTERVAL '1 day').

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 23:24:42 +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