Merge pull request 'feat(sf-b3): POST /pilot/request — lead gen + pilot_requests table (Telegram TODO)' (#330) from feat/sf-b3-pilot-leads into main
Some checks failed
Deploy / changes (push) Successful in 5s
Deploy / build-frontend (push) Has been skipped
Deploy / build-worker (push) Successful in 26s
Deploy / build-backend (push) Successful in 28s
Deploy / deploy (push) Failing after 34s

This commit is contained in:
lekss361 2026-05-17 21:14:28 +00:00
commit dbeed80b26
3 changed files with 97 additions and 0 deletions

View file

@ -0,0 +1,74 @@
"""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, EmailStr, 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)
email: EmailStr | None = None
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",
}

View file

@ -25,6 +25,7 @@ from app.api.v1 import (
custom_pois,
parcels,
photos,
pilot,
trade_in,
users,
)
@ -100,6 +101,7 @@ app.include_router(
tags=["admin", "etl"],
)
app.include_router(trade_in.router, prefix="/api/v1/trade-in", tags=["trade-in"])
app.include_router(pilot.router, prefix="/api/v1/pilot", tags=["pilot"])
app.include_router(users.router, prefix="/api/v1", tags=["users"])

View file

@ -0,0 +1,21 @@
BEGIN;
CREATE TABLE IF NOT EXISTS pilot_requests (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
phone text,
email text,
company text,
message text,
source text, -- 'landing', 'analyze_page', etc
user_agent text,
created_at timestamptz NOT NULL DEFAULT NOW(),
notified_at timestamptz -- when Telegram sent
);
CREATE INDEX IF NOT EXISTS pilot_requests_created_idx
ON pilot_requests (created_at DESC);
COMMENT ON TABLE pilot_requests IS '#307 SF-B3 lead generation, опционально notified в Telegram.';
COMMIT;