From 99e55c0bc28a5e086b489c13296c6fb532ef7499 Mon Sep 17 00:00:00 2001 From: lekss361 Date: Mon, 18 May 2026 00:01:35 +0300 Subject: [PATCH] feat(sf-b3): POST /pilot/request + pilot_requests table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавляет endpoint для приёма заявок на пилот (lead-gen). INSERT в pilot_requests, response {id, created_at, status}. Telegram-уведомление — TODO (creds не настроены, #307 SF-B3). Migration: data/sql/118_pilot_requests.sql --- backend/app/api/v1/pilot.py | 74 +++++++++++++++++++++++++++++++++ backend/app/main.py | 2 + data/sql/118_pilot_requests.sql | 21 ++++++++++ 3 files changed, 97 insertions(+) create mode 100644 backend/app/api/v1/pilot.py create mode 100644 data/sql/118_pilot_requests.sql diff --git a/backend/app/api/v1/pilot.py b/backend/app/api/v1/pilot.py new file mode 100644 index 00000000..7ff2b4e4 --- /dev/null +++ b/backend/app/api/v1/pilot.py @@ -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", + } diff --git a/backend/app/main.py b/backend/app/main.py index 7874ee48..d37c91ea 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -25,6 +25,7 @@ from app.api.v1 import ( custom_pois, parcels, photos, + pilot, trade_in, ) from app.core.config import settings @@ -99,6 +100,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.get("/health") diff --git a/data/sql/118_pilot_requests.sql b/data/sql/118_pilot_requests.sql new file mode 100644 index 00000000..7bdb6e63 --- /dev/null +++ b/data/sql/118_pilot_requests.sql @@ -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;