191 lines
7.4 KiB
Python
191 lines
7.4 KiB
Python
from pathlib import Path
|
|
|
|
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Response, status
|
|
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.palm_report import PalmReport
|
|
from app.models.share_image_job import ShareImageJob
|
|
from app.models.uploaded_image import UploadedImage
|
|
from app.models.user import User
|
|
from app.schemas.share_image import ShareImageJobResponse
|
|
from app.schemas.report import ReportCreate, ReportDetail, ReportSummary
|
|
from app.services.image_service import ImageService
|
|
from app.services.report_service import ReportService
|
|
from app.services.share_poster_service import SharePosterService
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
async def generate_report_task(report_id: str) -> None:
|
|
async with AsyncSessionLocal() as session:
|
|
await ReportService().generate(session, report_id)
|
|
|
|
|
|
async def generate_share_image_task(job_id: str) -> None:
|
|
async with AsyncSessionLocal() as session:
|
|
result = await session.execute(select(ShareImageJob).where(ShareImageJob.id == job_id))
|
|
job = result.scalar_one()
|
|
job.status = "processing"
|
|
await session.flush()
|
|
|
|
try:
|
|
report_result = await session.execute(select(PalmReport).where(PalmReport.id == job.report_id))
|
|
report = report_result.scalar_one()
|
|
image_result = await session.execute(select(UploadedImage).where(UploadedImage.id == report.image_id))
|
|
image = image_result.scalar_one_or_none()
|
|
png = await SharePosterService().render_ai_or_fallback(report, image)
|
|
|
|
share_dir = Path("storage/share_images")
|
|
share_dir.mkdir(parents=True, exist_ok=True)
|
|
storage_key = f"{job.id}.png"
|
|
(share_dir / storage_key).write_bytes(png)
|
|
|
|
job.storage_key = storage_key
|
|
job.status = "completed"
|
|
job.error_message = None
|
|
except Exception as exc:
|
|
job.status = "failed"
|
|
job.error_message = str(exc)
|
|
await session.commit()
|
|
|
|
|
|
@router.post("", response_model=ReportDetail, status_code=status.HTTP_201_CREATED)
|
|
async def create_report(
|
|
payload: ReportCreate,
|
|
background_tasks: BackgroundTasks,
|
|
user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
image_result = await db.execute(
|
|
select(UploadedImage).where(UploadedImage.id == payload.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")
|
|
|
|
report = PalmReport(user_id=user.id, image_id=image.id, hand_side=payload.hand_side, status="pending")
|
|
db.add(report)
|
|
await db.flush()
|
|
await db.refresh(report)
|
|
background_tasks.add_task(generate_report_task, report.id)
|
|
return report
|
|
|
|
|
|
@router.get("/share-image-jobs/{job_id}", response_model=ShareImageJobResponse)
|
|
async def get_share_image_job(
|
|
job_id: str,
|
|
user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
job = await _get_owned_share_job(db, job_id, user.id)
|
|
return job
|
|
|
|
|
|
@router.get("/share-image-jobs/{job_id}/image")
|
|
async def get_share_image_job_image(
|
|
job_id: str,
|
|
user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
job = await _get_owned_share_job(db, job_id, user.id)
|
|
if job.status != "completed" or not job.storage_key:
|
|
raise HTTPException(status_code=400, detail="Share image is not ready")
|
|
path = Path("storage/share_images") / job.storage_key
|
|
if not path.exists():
|
|
raise HTTPException(status_code=404, detail="Share image file not found")
|
|
return Response(content=path.read_bytes(), media_type="image/png")
|
|
|
|
|
|
@router.get("", response_model=list[ReportSummary])
|
|
async def list_reports(user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
|
result = await db.execute(
|
|
select(PalmReport).where(PalmReport.user_id == user.id).order_by(desc(PalmReport.created_at)).limit(50)
|
|
)
|
|
reports = result.scalars().all()
|
|
return [
|
|
ReportSummary(
|
|
id=report.id,
|
|
status=report.status,
|
|
hand_side=report.hand_side,
|
|
created_at=report.created_at,
|
|
overall_summary=(report.report_data or {}).get("overall_summary") if report.report_data else None,
|
|
)
|
|
for report in reports
|
|
]
|
|
|
|
|
|
@router.get("/{report_id}", response_model=ReportDetail)
|
|
async def get_report(report_id: str, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
|
report = await _get_owned_report(db, report_id, user.id)
|
|
return report
|
|
|
|
|
|
@router.post("/{report_id}/share-image-jobs", response_model=ShareImageJobResponse, status_code=status.HTTP_201_CREATED)
|
|
async def create_share_image_job(
|
|
report_id: str,
|
|
background_tasks: BackgroundTasks,
|
|
user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
report = await _get_owned_report(db, report_id, user.id)
|
|
if report.status != "completed" or not report.report_data:
|
|
raise HTTPException(status_code=400, detail="Report is not ready")
|
|
job = ShareImageJob(user_id=user.id, report_id=report.id, status="pending")
|
|
db.add(job)
|
|
await db.flush()
|
|
await db.refresh(job)
|
|
background_tasks.add_task(generate_share_image_task, job.id)
|
|
return job
|
|
|
|
|
|
@router.get("/{report_id}/share-image")
|
|
async def get_report_share_image(
|
|
report_id: str,
|
|
user: User = Depends(get_current_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
report = await _get_owned_report(db, report_id, user.id)
|
|
if report.status != "completed" or not report.report_data:
|
|
raise HTTPException(status_code=400, detail="Report is not ready")
|
|
|
|
image_result = await db.execute(select(UploadedImage).where(UploadedImage.id == report.image_id))
|
|
image = image_result.scalar_one_or_none()
|
|
png = await SharePosterService().render_ai_or_fallback(report, image)
|
|
return Response(
|
|
content=png,
|
|
media_type="image/png",
|
|
headers={"Content-Disposition": f'inline; filename="palm-report-{report.id}.png"'},
|
|
)
|
|
|
|
|
|
@router.delete("/{report_id}")
|
|
async def delete_report(report_id: str, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)):
|
|
report = await _get_owned_report(db, report_id, user.id)
|
|
image_result = await db.execute(select(UploadedImage).where(UploadedImage.id == report.image_id))
|
|
image = image_result.scalar_one_or_none()
|
|
await db.delete(report)
|
|
await db.flush()
|
|
if image:
|
|
ImageService().delete(image.storage_key)
|
|
await db.delete(image)
|
|
return {"status": "deleted"}
|
|
|
|
|
|
async def _get_owned_report(db: AsyncSession, report_id: str, user_id: str) -> PalmReport:
|
|
result = await db.execute(select(PalmReport).where(PalmReport.id == report_id, PalmReport.user_id == user_id))
|
|
report = result.scalar_one_or_none()
|
|
if report is None:
|
|
raise HTTPException(status_code=404, detail="Report not found")
|
|
return report
|
|
|
|
|
|
async def _get_owned_share_job(db: AsyncSession, job_id: str, user_id: str) -> ShareImageJob:
|
|
result = await db.execute(select(ShareImageJob).where(ShareImageJob.id == job_id, ShareImageJob.user_id == user_id))
|
|
job = result.scalar_one_or_none()
|
|
if job is None:
|
|
raise HTTPException(status_code=404, detail="Share image job not found")
|
|
return job
|