Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Contact Forms — backend-сервис лендинга разработчика

Портфолио Богачева Николая + REST API формы обратной связи с AI (GigaChat), SMTP, rate limiting, метриками и Docker/Nginx.

Important

Для запуска не нужны ключи GigaChat, SMTP и metrics-токены.
Скопируйте .env.example.env и поднимите compose.
Секреты (если понадобятся) клиент добавляет сам в .env / выпускает токен CLI.


1. Описание проекта и запуск

Что это

  • POST /api/contact — валидация, PostgreSQL, AI-анализ, email owner + user
  • GET /api/health — проверки db / redis / ai_configured / smtp_configured
  • GET /api/metrics — агрегаты (только с API-токеном metrics:read)
  • Статический лендинг (RU/EN, dark/light) через Nginx

Архитектура: Router → Service → Repository (SQLAlchemy ORM).
Строковые поля в БД без NULL: не передано → "".

Требования

Что Версия / примечание
Docker Desktop / Engine с Compose v2
Свободный порт 80 (или задайте HTTP_PORT в .env)
Git клонирование репозитория
Опционально GigaChat key, SMTP app-password — только для «полного» E2E

Не требуется: Python на хосте, локальный Postgres/Redis, npm, заранее выпущенные токены.

Быстрый старт (без ключей)

git clone <repo-url> contact-forms
cd contact-forms

# Linux / macOS
cp .env.example .env

# Windows PowerShell
Copy-Item .env.example .env

docker compose up --build -d
docker compose ps

Порядок старта контейнеров:

  1. db, redis — healthcheck (pg_isready, PING)
  2. api — entrypoint ждёт DB + Redis, alembic upgrade head, uvicorn; healthcheck GET /api/health
  3. nginx — стартует только когда api healthy

Без секретов сервис уже полноценно отвечает:

Проверка Ожидание
GET /api/health db=true, redis=true; status часто degraded (нет AI/SMTP keys)
POST /api/contact 201, ai.provider=fallback, email.owner/user=skipped
GET / лендинг 200

URL после старта:

URL Назначение
http://localhost/ лендинг
http://localhost/api/health health
http://localhost/docs OpenAPI (если ENABLE_DOCS=true)
http://localhost/redoc ReDoc
http://localhost/api/metrics метрики (нужен токен — см. ниже)

Проверка после запуска

# статус контейнеров (api/nginx должны быть healthy)
docker compose ps

# health
curl -s http://localhost/api/health

# contact (fallback AI, email skipped без SMTP)
curl -s -X POST http://localhost/api/contact \
  -H "Content-Type: application/json" \
  -d "{\"name\":\"Иван Иванов\",\"phone\":\"+79001234567\",\"email\":\"ivan@example.com\",\"comment\":\"Интересует сотрудничество по FastAPI\",\"locale\":\"ru\"}"

# логи api при проблемах
docker compose logs api --tail 100

Windows PowerShell (health):

Invoke-RestMethod http://localhost/api/health

Остановка / пересборка

docker compose down          # остановить, volumes сохранить
docker compose down -v       # + удалить БД/логи volumes
docker compose up --build -d # пересборка после правок кода/Dockerfile
docker compose up -d --force-recreate api   # только api после смены .env

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

Шаблон: .env.example.env.gitignore).

Группа Переменные Обязательно для старта?
App ENABLE_DOCS, CORS_ORIGINS, LOG_*, HTTP_PORT нет (есть defaults)
Postgres POSTGRES_*, DATABASE_URL нет (compose defaults)
Redis / RL REDIS_URL, RATE_LIMIT_* нет
Resources DB_MEM_LIMIT, API_MEM_LIMIT, … нет
SMTP SMTP_*, OWNER_EMAIL, EMAIL_ENABLED нет (без них email=skipped)
AI AI_*, GIGACHAT_* нет (без ключа → fallback)

Compose переопределяет DATABASE_URL / REDIS_URL на внутренние хосты db / redis.

Ресурсы контейнеров

Как в mail-manager: deploy.resources.limits в docker-compose.yml.

Сервис Default RAM Default CPU
db 512M 1.0
redis 128M 0.5 (+ maxmemory 64mb LRU)
api 512M 1.0
nginx 128M 0.5

Переопределение в .env: API_MEM_LIMIT=768M, DB_CPU_LIMIT=1.5 и т.д.

На Docker Compose без Swarm лимиты deploy.resources применяются в Docker Compose v2 / Desktop; при отсутствии поддержки они игнорируются, стек всё равно стартует.

Metrics-токен (опционально, генерируется у себя)

Токен не лежит в репозитории. После первого запуска:

docker compose exec api python -m scripts.create_api_token --name local --scope metrics:read
# скрипт один раз печатает cf_live_... — сохраните локально

curl -s http://localhost/api/metrics -H "Authorization: Bearer <token>"
# или
curl -s http://localhost/api/metrics -H "X-API-Key: <token>"

В БД хранится только SHA-256 hash.

Локальные unit-тесты (опционально, Python 3.12 на хосте)

cd backend
python -m pip install -r requirements.txt
python -m pytest -q

Внешние сервисы для unit-тестов не нужны.

E2E smoke против уже поднятого стека:

python backend/scripts/e2e_smoke.py http://localhost

2. Технологии и зачем

Технология Зачем
Python 3.12 + FastAPI async REST, OpenAPI, DI
Pydantic v2 строгая валидация входа
SQLAlchemy 2 async + asyncpg ORM-only доступ к данным
Alembic миграции при старте api
PostgreSQL 16 contacts / utm / ai / email / metrics
Redis 7 rate limit; при падении — memory fallback
aiosmtplib + Jinja2 SMTP как в mail-manager
GigaChat SDK AI из РФ; offline fallback обязателен
Nginx :80, static + reverse proxy
Docker multi-stage non-root, образ из backend/

Принципы: KISS, REST, слои, graceful degrade, секреты только в env.


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

contact-forms/
├── backend/
│   ├── app/
│   │   ├── main.py              # app factory + lifespan
│   │   ├── api/                 # routers + deps
│   │   ├── core/                # config, logs, errors, RL, security
│   │   ├── db/                  # engine, models
│   │   ├── repositories/        # ORM data access
│   │   ├── schemas/             # Pydantic DTO
│   │   ├── services/            # contact, AI, email, metrics
│   │   │   └── ai/              # gigachat, yandex, fallback, prompts
│   │   ├── clients/smtp_client.py
│   │   └── templates/email/
│   ├── alembic/
│   ├── scripts/
│   │   ├── create_api_token.py
│   │   └── e2e_smoke.py
│   ├── tests/                   # unit / offline suite
│   ├── Dockerfile
│   ├── entrypoint.sh            # wait db+redis → migrate → uvicorn
│   └── requirements.txt
├── frontend/                    # portfolio landing
├── nginx/
├── docs/                        # доп. материалы (AI branches и т.п.)
├── docker-compose.yml
├── .env.example
├── postman/
└── README.md

Слои: API → Services → Repositories → Clients.


4. API Endpoints

Base path: /api

POST /api/contact (public)

Даже при сбое AI/SMTP → 201, если contact записан; статусы интеграций в теле.

Request

{
  "name": "Иван Иванов",
  "phone": "+79001234567",
  "email": "ivan@example.com",
  "comment": "Интересует сотрудничество по FastAPI",
  "locale": "ru",
  "utm": {
    "source": "hh",
    "medium": "referral",
    "campaign": "portfolio",
    "term": "",
    "content": "",
    "landing_path": "/?utm_source=hh",
    "referrer": "https://hh.ru/"
  }
}
Поле Правила
name 2–100, буквы/пробелы/дефис/апостроф
phone 10–15 цифр, optional +
email EmailStr
comment 10–2000
locale ru | en
utm.* optional → "" если нет
website honeypot (если заполнено — 201 без side effects)

201 Response

{
  "id": "uuid",
  "message": "Обращение принято",
  "ai": {
    "used": false,
    "provider": "fallback",
    "sentiment": "positive",
    "category": "collaboration",
    "priority": "normal",
    "suggested_reply": ""
  },
  "email": { "owner": "skipped", "user": "skipped" }
}
Code Когда
201 contact сохранён
422 validation
429 rate limit (Retry-After)
503 БД недоступна
500 unexpected (detail generic)

GET /api/health (public)

{
  "status": "ok|degraded|error",
  "version": "1.0.0",
  "checks": {
    "db": true,
    "redis": true,
    "ai_configured": false,
    "smtp_configured": false
  }
}
status смысл
ok db + redis + ai_configured
degraded db ok, но redis/ai не в ideal state
error db недоступна

GET /api/metrics (token)

Заголовки: Authorization: Bearer <token> или X-API-Key: <token>.
Scope: metrics:read. Источник — metrics_daily.

Code Когда
200 ok
401 нет токена
403 неверный / expired / bad scope

OpenAPI: /docs, /redoc (ENABLE_DOCS).
Postman: postman/Contact_Forms_API.postman_collection.json.


5. AI и SMTP (опционально)

Провайдеры AI

Provider Роль
GigaChat primary, РФ, freemium
YandexGPT optional
rule-based fallback always-on

AI_PROVIDER=gigachat|yandex|fallback
Контракт: sentiment, sentiment_score, category, priority, summary, suggested_reply.
ai.used=true только при успешном ответе внешнего API.

Ветки промптов (match/case): job / collaboration / question / spam / other / generic — см. docs/gigachat_prompt_branches.md и backend/app/services/ai/prompts.py.

Получение и подстановка ключа GigaChat

Note

Без ключа проект уже работает (fallback). Этот блок — только если нужен живой GigaChat.

  1. developers.sber.ru → Сбер ID → проект GigaChat API.
  2. Scope физлица: GIGACHAT_API_PERS.
  3. Скопируйте Authorization key (не OAuth access token).
  4. В .env:
AI_ENABLED=true
AI_PROVIDER=gigachat
GIGACHAT_CREDENTIALS=ВАШ_AUTHORIZATION_KEY_ОДНОЙ_СТРОКОЙ
GIGACHAT_MODEL=GigaChat
GIGACHAT_SCOPE=GIGACHAT_API_PERS
GIGACHAT_VERIFY_SSL_CERTS=false
  1. Применить:
docker compose up -d --force-recreate api
  1. Проверка:
docker compose exec api python -c "from app.core.config import get_settings; s=get_settings(); print(s.ai_configured, len(s.gigachat_credentials or ''))"
curl -s http://localhost/api/health   # ai_configured: true
Ситуация ai.used ai.provider
Key ok true gigachat
Key пустой / ошибка / timeout false fallback

Частые ошибки: забыли recreate api; key с кавычками/Bearer; SSL → GIGACHAT_VERIFY_SSL_CERTS=false для local; scope.

SMTP (как почтовый менеджер)

EMAIL_ENABLED=true
SMTP_HOST=smtp.yandex.ru
SMTP_PORT=465
SMTP_USERNAME=your@yandex.ru
SMTP_PASSWORD=app-password
SMTP_FROM=your@yandex.ru
SMTP_USE_SSL=true
OWNER_EMAIL=you@example.com
  1. Пароль приложения у провайдера.
  2. Вставить в .env (пустые user/pass = email skipped).
  3. docker compose up -d --force-recreate api.

Письма: owner (AI summary) + user copy. Статусы: sent|failed|skipped.


6. Ошибки, логи, устойчивость

JSON ошибок

{
  "detail": "Человекочитаемое сообщение",
  "code": "rate_limit_exceeded",
  "request_id": "uuid"
}
Ситуация HTTP code
Pydantic 422 validation_error
Rate limit 429 rate_limit_exceeded
Metrics token 401/403 unauthorized / forbidden
DB down 503 database_unavailable
Unknown 500 internal_error

AI/SMTP partial failure → 201 + честные статусы.

Логи

Volume api_logs/app/logs в контейнере:

  • access.log — method, path, status, ms, ip, request_id
  • app.log / error.log
docker compose exec api ls -la /app/logs
docker compose logs -f api

Секреты не логируются. Ответ дополняется X-Request-ID.

Устойчивость

Сценарий Поведение
Redis down memory rate limiter, health degraded
GigaChat down/timeout fallback, contact 201
SMTP fail contact saved, email failed
Honeypot 201 без side effects
DB down at request 503
DB/Redis slow at boot entrypoint ждёт до ~60s, иначе api не стартует
Body limit Nginx (см. conf)

Troubleshooting

Симптом Действие
port is already allocated смените HTTP_PORT=8080 в .env
api не healthy docker compose logs api — DB/Redis/migrate
nginx Up, api restarting дождаться migrate; проверить .env syntax
contact 429 подождать окно RL или docker compose exec redis redis-cli FLUSHDB (local only)
ai_configured=false пустой GIGACHAT_CREDENTIALS — ожидаемо без ключа
email always skipped не заданы SMTP_USERNAME / SMTP_PASSWORD

7. Что можно улучшить

  • BackgroundTasks / очередь email после commit
  • Prometheus + OpenTelemetry
  • Captcha / Turnstile при росте спама
  • Admin UI contacts
  • Multi-worker + только Redis RL
  • Кэш OAuth-токена GigaChat
  • Playwright e2e лендинга
  • Helm/K8s при cloud deploy

Лицензия

См. LICENSE (если файл добавлен в репозиторий).

About

contact forms

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages