gendesign/backend/app/api/v1/pilot.py
lekss361 31e686e4d3 fix(pilot): replace pydantic EmailStr with str+regex (email-validator dep missing → backend startup ImportError → deploy unhealthy)
Root cause: PR #330 SF-B3 introduced pydantic EmailStr import in
backend/app/api/v1/pilot.py at module level. EmailStr requires the
email-validator package which is not in pyproject deps, so backend import
crashed:

  pydantic.errors.PydanticImportError: `email-validator` is not installed

Result: backend container started but uvicorn import failed → /health
endpoint never bound → docker-compose marked container unhealthy → deploy
exited 1.

Fix: drop EmailStr, use plain `str | None` with a minimal regex pattern.
Strict email validation can run on the frontend or CRM side.
2026-05-18 00:24:45 +03:00

77 lines
2.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Pilot request lead-gen endpoint.
POST /api/v1/pilot/request — принимает заявку на пилот (лид с лендинга или страницы анализа),
сохраняет в таблицу pilot_requests.
Telegram-уведомление — TODO (creds не настроены, см. #307 SF-B3).
"""
from __future__ import annotations
import logging
from typing import Annotated, Any, Literal
from fastapi import APIRouter, Depends, Request
from pydantic import BaseModel, Field
from sqlalchemy import text
from sqlalchemy.orm import Session
from app.core.db import get_db
logger = logging.getLogger(__name__)
router = APIRouter()
class PilotRequestInput(BaseModel):
name: str = Field(min_length=2, max_length=200)
phone: str | None = Field(default=None, max_length=50)
# Note: was EmailStr but `email-validator` package not in deps → ImportError
# on backend startup → container unhealthy → whole deploy fails. Using plain
# str + minimal regex; full validation can happen on frontend/CRM side.
email: str | None = Field(default=None, max_length=200, pattern=r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
company: str | None = Field(default=None, max_length=200)
message: str | None = Field(default=None, max_length=2000)
source: Literal["landing", "analyze_page", "other"] = "landing"
@router.post("/request")
async def create_pilot_request(
payload: PilotRequestInput,
request: Request,
db: Annotated[Session, Depends(get_db)],
) -> dict[str, Any]:
"""Сохраняет заявку на пилот в pilot_requests."""
user_agent = request.headers.get("user-agent")
row = (
db.execute(
text(
"""
INSERT INTO pilot_requests (name, phone, email, company, message, source, user_agent)
VALUES (:name, :phone, :email, :company, :message, :source, :user_agent)
RETURNING CAST(id AS text), created_at
"""
),
{
"name": payload.name,
"phone": payload.phone,
"email": str(payload.email) if payload.email else None,
"company": payload.company,
"message": payload.message,
"source": payload.source,
"user_agent": user_agent,
},
)
.mappings()
.one()
)
db.commit()
logger.info("pilot_request saved id=%s source=%s", row["id"], payload.source)
return {
"id": row["id"],
"created_at": row["created_at"].isoformat(),
"status": "received",
}