feat: Sprint 1 foundation - FastAPI scaffold, DB schema, auth, seed data, mock WhatsApp, Docker

This commit is contained in:
root
2026-07-23 12:04:14 +00:00
parent 8c755f3db7
commit 32a4c50b23
34 changed files with 1390 additions and 0 deletions
View File
+53
View File
@@ -0,0 +1,53 @@
"""Authentication router — register, login, refresh, me."""
from __future__ import annotations
from typing import Annotated
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.core.security import get_current_user
from app.models.user import User
from app.schemas.auth import (
LoginRequest,
RefreshRequest,
RegisterRequest,
TokenResponse,
UserOut,
)
from app.services import auth as auth_service
router = APIRouter(prefix="/api/auth", tags=["auth"])
@router.post("/register", response_model=UserOut, status_code=201)
async def register(
body: RegisterRequest,
db: Annotated[AsyncSession, Depends(get_db)],
) -> User:
return await auth_service.register(db, body)
@router.post("/login", response_model=TokenResponse)
async def login(
body: LoginRequest,
db: Annotated[AsyncSession, Depends(get_db)],
) -> TokenResponse:
access, refresh, _user = await auth_service.login(db, body.email, body.password)
return TokenResponse(access_token=access, refresh_token=refresh)
@router.post("/refresh", response_model=TokenResponse)
async def refresh(
body: RefreshRequest,
db: Annotated[AsyncSession, Depends(get_db)],
) -> TokenResponse:
access, refresh = await auth_service.refresh_access_token(db, body.refresh_token)
return TokenResponse(access_token=access, refresh_token=refresh)
@router.get("/me", response_model=UserOut)
async def me(current_user: Annotated[User, Depends(get_current_user)]) -> User:
return current_user
+14
View File
@@ -0,0 +1,14 @@
"""Health-check endpoint."""
from __future__ import annotations
from fastapi import APIRouter
from app.schemas.health import HealthResponse
router = APIRouter(tags=["health"])
@router.get("/health", response_model=HealthResponse)
async def health_check() -> HealthResponse:
return HealthResponse()
+45
View File
@@ -0,0 +1,45 @@
"""Mock WhatsApp endpoint for testing command parsing."""
from __future__ import annotations
from datetime import datetime, timezone
from typing import Annotated
from fastapi import APIRouter, Depends
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.models.whatsapp_log import WhatsAppLog
from app.schemas.whatsapp import MockWhatsAppLogEntry, MockWhatsAppRequest, MockWhatsAppResponse
router = APIRouter(prefix="/api/whatsapp", tags=["whatsapp"])
@router.post("/mock", response_model=MockWhatsAppResponse)
async def mock_whatsapp(
body: MockWhatsAppRequest,
db: Annotated[AsyncSession, Depends(get_db)],
) -> MockWhatsAppResponse:
"""Accept a mock WhatsApp command and log it."""
log = WhatsAppLog(
command=body.command,
from_number=body.from_number,
ticket_id=body.ticket_id,
received_at=datetime.now(timezone.utc),
)
db.add(log)
await db.flush()
return MockWhatsAppResponse()
@router.get("/mock-log", response_model=list[MockWhatsAppLogEntry])
async def mock_whatsapp_log(
db: Annotated[AsyncSession, Depends(get_db)],
limit: int = 50,
) -> list[MockWhatsAppLogEntry]:
"""Return recent mock WhatsApp submissions."""
result = await db.execute(
select(WhatsAppLog).order_by(WhatsAppLog.received_at.desc()).limit(limit)
)
return list(result.scalars().all())