feat(sf-b3): POST /pilot/request — lead gen + pilot_requests table (Telegram TODO) #330
3 changed files with 97 additions and 0 deletions
74
backend/app/api/v1/pilot.py
Normal file
74
backend/app/api/v1/pilot.py
Normal 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",
|
||||||
|
}
|
||||||
|
|
@ -25,6 +25,7 @@ from app.api.v1 import (
|
||||||
custom_pois,
|
custom_pois,
|
||||||
parcels,
|
parcels,
|
||||||
photos,
|
photos,
|
||||||
|
pilot,
|
||||||
trade_in,
|
trade_in,
|
||||||
users,
|
users,
|
||||||
)
|
)
|
||||||
|
|
@ -100,6 +101,7 @@ app.include_router(
|
||||||
tags=["admin", "etl"],
|
tags=["admin", "etl"],
|
||||||
)
|
)
|
||||||
app.include_router(trade_in.router, prefix="/api/v1/trade-in", tags=["trade-in"])
|
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"])
|
app.include_router(users.router, prefix="/api/v1", tags=["users"])
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
21
data/sql/118_pilot_requests.sql
Normal file
21
data/sql/118_pilot_requests.sql
Normal 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;
|
||||||
Loading…
Add table
Reference in a new issue