harden telegram webapp production readiness
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
from datetime import date, timedelta
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
from fastapi import APIRouter, Depends, Header, HTTPException, Request, status
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.api.deps import (
|
||||
get_current_telegram_user,
|
||||
get_or_create_telegram_user,
|
||||
require_internal_api_token,
|
||||
)
|
||||
from app.core.config import settings
|
||||
from app.db.session import get_session
|
||||
from app.models.car import Car
|
||||
@@ -29,41 +34,14 @@ def username_from_telegram(telegram_id: int, username: str | None = None) -> str
|
||||
return str(telegram_id) if not username else str(telegram_id)
|
||||
|
||||
|
||||
async def upsert_telegram_user(
|
||||
session: AsyncSession,
|
||||
*,
|
||||
telegram_id: int,
|
||||
username: str | None = None,
|
||||
first_name: str | None = None,
|
||||
last_name: str | None = None,
|
||||
locale: str | None = None,
|
||||
currency: str | None = None,
|
||||
) -> User:
|
||||
result = await session.execute(select(User).where(User.telegram_id == telegram_id))
|
||||
user = result.scalar_one_or_none()
|
||||
payload = {
|
||||
"telegram_id": telegram_id,
|
||||
"username": username_from_telegram(telegram_id, username),
|
||||
"first_name": first_name,
|
||||
"last_name": last_name,
|
||||
"locale": locale,
|
||||
"currency": currency,
|
||||
}
|
||||
if user is None:
|
||||
user = User(**{key: value for key, value in payload.items() if value is not None})
|
||||
session.add(user)
|
||||
else:
|
||||
for field, value in payload.items():
|
||||
if value is not None:
|
||||
setattr(user, field, value)
|
||||
await session.commit()
|
||||
await session.refresh(user)
|
||||
return user
|
||||
|
||||
|
||||
@router.post("", response_model=UserRead)
|
||||
async def upsert_user(payload: UserUpsert, session: AsyncSession = Depends(get_session)) -> User:
|
||||
return await upsert_telegram_user(session, **payload.model_dump())
|
||||
async def upsert_user(
|
||||
payload: UserUpsert,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
x_internal_api_token: str | None = Header(default=None, alias="X-Internal-API-Token"),
|
||||
) -> User:
|
||||
require_internal_api_token(x_internal_api_token)
|
||||
return await get_or_create_telegram_user(session, **payload.model_dump())
|
||||
|
||||
|
||||
@router.get("/auth/config", response_model=AuthConfig)
|
||||
@@ -71,6 +49,8 @@ async def auth_config() -> AuthConfig:
|
||||
return AuthConfig(
|
||||
bot_username=settings.bot_username or "seoulmate_officialbot",
|
||||
vapid_public_key=settings.vapid_public_key or None,
|
||||
app_env=settings.app_env,
|
||||
allow_dev_auth=settings.allow_dev_auth and not settings.is_production,
|
||||
)
|
||||
|
||||
|
||||
@@ -80,7 +60,7 @@ async def webapp_auth(
|
||||
) -> User:
|
||||
user_data = verify_webapp_init_data(payload.init_data, settings.bot_token)
|
||||
telegram_id = int(user_data["id"])
|
||||
return await upsert_telegram_user(
|
||||
return await get_or_create_telegram_user(
|
||||
session,
|
||||
telegram_id=telegram_id,
|
||||
username=user_data.get("username"),
|
||||
@@ -96,7 +76,7 @@ async def telegram_login(
|
||||
) -> User:
|
||||
values = verify_login_widget(payload.model_dump(), settings.bot_token)
|
||||
telegram_id = int(values["id"])
|
||||
return await upsert_telegram_user(
|
||||
return await get_or_create_telegram_user(
|
||||
session,
|
||||
telegram_id=telegram_id,
|
||||
username=values.get("username"),
|
||||
@@ -105,21 +85,35 @@ async def telegram_login(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/me", response_model=UserRead)
|
||||
async def current_user_profile(current_user: User = Depends(get_current_telegram_user)) -> User:
|
||||
return current_user
|
||||
|
||||
|
||||
@router.get("/telegram/{telegram_id}", response_model=UserRead)
|
||||
async def get_user_by_telegram_id(
|
||||
telegram_id: int, session: AsyncSession = Depends(get_session)
|
||||
telegram_id: int,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
x_internal_api_token: str | None = Header(default=None, alias="X-Internal-API-Token"),
|
||||
) -> User:
|
||||
require_internal_api_token(x_internal_api_token)
|
||||
result = await session.execute(select(User).where(User.telegram_id == telegram_id))
|
||||
return result.scalar_one()
|
||||
user = result.scalar_one_or_none()
|
||||
if user is None:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
return user
|
||||
|
||||
|
||||
@router.patch("/{user_id}/preferences", response_model=UserRead)
|
||||
async def update_preferences(
|
||||
user_id: int, payload: UserPreferencesUpdate, session: AsyncSession = Depends(get_session)
|
||||
user_id: int,
|
||||
payload: UserPreferencesUpdate,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
current_user: User = Depends(get_current_telegram_user),
|
||||
) -> User:
|
||||
user = await session.get(User, user_id)
|
||||
if user is None:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
if current_user.id != user_id:
|
||||
raise HTTPException(status_code=403, detail="Forbidden")
|
||||
user = current_user
|
||||
for field, value in payload.model_dump(exclude_none=True).items():
|
||||
setattr(user, field, value)
|
||||
await session.commit()
|
||||
@@ -133,10 +127,10 @@ async def save_push_subscription(
|
||||
payload: PushSubscriptionCreate,
|
||||
request: Request,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
current_user: User = Depends(get_current_telegram_user),
|
||||
) -> None:
|
||||
user = await session.get(User, user_id)
|
||||
if user is None:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
if current_user.id != user_id:
|
||||
raise HTTPException(status_code=403, detail="Forbidden")
|
||||
result = await session.execute(
|
||||
select(PushSubscription).where(
|
||||
PushSubscription.user_id == user_id,
|
||||
@@ -166,8 +160,15 @@ async def save_push_subscription(
|
||||
async def due_reminders(
|
||||
user_id: int,
|
||||
days: int = 30,
|
||||
limit: int = 50,
|
||||
offset: int = 0,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
current_user: User = Depends(get_current_telegram_user),
|
||||
) -> list[ReminderRead]:
|
||||
if current_user.id != user_id:
|
||||
raise HTTPException(status_code=403, detail="Forbidden")
|
||||
limit = min(max(limit, 1), 200)
|
||||
offset = max(offset, 0)
|
||||
today = date.today()
|
||||
horizon = today + timedelta(days=max(1, min(days, 180)))
|
||||
stmt = (
|
||||
@@ -183,6 +184,8 @@ async def due_reminders(
|
||||
)
|
||||
)
|
||||
.order_by(ServiceEntry.next_due_date.asc().nulls_last())
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
)
|
||||
rows = (await session.execute(stmt)).all()
|
||||
reminders: list[ReminderRead] = []
|
||||
|
||||
Reference in New Issue
Block a user