from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status from pydantic import ValidationError from sqlalchemy import desc, select from sqlalchemy.ext.asyncio import AsyncSession from app.core.database import AsyncSessionLocal, get_db from app.core.security import get_current_user from app.models.reading import Reading from app.models.uploaded_image import UploadedImage from app.models.user import User from app.schemas.quota import QuotaResponse from app.schemas.reading import BaziInput, ReadingCreate, ReadingDetail, ReadingSummary from app.services.image_service import ImageService from app.services.quota_service import QuotaService from app.services.reading_service import ReadingService router = APIRouter() async def generate_reading_task(reading_id: str) -> None: async with AsyncSessionLocal() as session: await ReadingService().generate(session, reading_id) @router.get("/quota", response_model=QuotaResponse) async def get_reading_quota(user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)): return await QuotaService().get_quota(db, user.id) @router.post("", response_model=ReadingDetail, status_code=status.HTTP_201_CREATED) async def create_reading( payload: ReadingCreate, background_tasks: BackgroundTasks, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): input_data = dict(payload.input_data) image_id = payload.image_id if payload.reading_type in {"palm", "face"}: image_result = await db.execute(select(UploadedImage).where(UploadedImage.id == image_id, UploadedImage.user_id == user.id)) image = image_result.scalar_one_or_none() if image is None: raise HTTPException(status_code=404, detail="Image not found") elif payload.reading_type == "bazi": try: input_data = BaziInput.model_validate(input_data).model_dump() except ValidationError as exc: raise HTTPException(status_code=422, detail=exc.errors()) from exc await QuotaService().consume(db, user.id) reading = Reading( user_id=user.id, reading_type=payload.reading_type, image_id=image_id, input_data=input_data, status="pending", ) db.add(reading) await db.flush() await db.refresh(reading) await db.commit() background_tasks.add_task(generate_reading_task, reading.id) return reading @router.get("", response_model=list[ReadingSummary]) async def list_readings(user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)): result = await db.execute( select(Reading).where(Reading.user_id == user.id).order_by(desc(Reading.created_at)).limit(80) ) readings = result.scalars().all() return [ ReadingSummary( id=reading.id, reading_type=reading.reading_type, status=reading.status, created_at=reading.created_at, overall_summary=(reading.report_data or {}).get("overall_summary") if reading.report_data else None, ) for reading in readings ] @router.get("/{reading_id}", response_model=ReadingDetail) async def get_reading(reading_id: str, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)): return await _get_owned_reading(db, reading_id, user.id) @router.delete("/{reading_id}") async def delete_reading(reading_id: str, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)): reading = await _get_owned_reading(db, reading_id, user.id) image = None if reading.image_id: image_result = await db.execute(select(UploadedImage).where(UploadedImage.id == reading.image_id)) image = image_result.scalar_one_or_none() await db.delete(reading) await db.flush() if image: ImageService().delete(image.storage_key) await db.delete(image) return {"status": "deleted"} async def _get_owned_reading(db: AsyncSession, reading_id: str, user_id: str) -> Reading: result = await db.execute(select(Reading).where(Reading.id == reading_id, Reading.user_id == user_id)) reading = result.scalar_one_or_none() if reading is None: raise HTTPException(status_code=404, detail="Reading not found") return reading