80 lines
2.4 KiB
Python
80 lines
2.4 KiB
Python
from fastapi import APIRouter, Depends, status
|
|||
|
|
from pydantic import BaseModel, Field, field_validator
|
||
|
|
from sqlalchemy import select
|
||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
|
|
||
|
|
from app.api.timeutil import isoformat_utc
|
||
|
|
from app.auth.dependencies import get_current_user, get_optional_user
|
||
|
|
from app.db.models import BugReport, User
|
||
|
|
from app.db.session import get_session
|
||
|
|
|
||
|
|
router = APIRouter(prefix="/bug-reports", tags=["bug-reports"])
|
||
|
|
|
||
|
|
|
||
|
|
class BugReportCreate(BaseModel):
|
||
|
|
description: str = Field(min_length=1, max_length=5000)
|
||
|
|
contact: str | None = Field(default=None, max_length=256)
|
||
|
|
|
||
|
|
@field_validator("description")
|
||
|
|
@classmethod
|
||
|
|
def _description_not_blank(cls, value: str) -> str:
|
||
|
|
value = value.strip()
|
||
|
|
if not value:
|
||
|
|
raise ValueError("description must not be blank")
|
||
|
|
return value
|
||
|
|
|
||
|
|
@field_validator("contact")
|
||
|
|
@classmethod
|
||
|
|
def _contact_stripped(cls, value: str | None) -> str | None:
|
||
|
|
if value is None:
|
||
|
|
return None
|
||
|
|
value = value.strip()
|
||
|
|
return value or None
|
||
|
|
|
||
|
|
|
||
|
|
class BugReportResponse(BaseModel):
|
||
|
|
id: int
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("", response_model=BugReportResponse, status_code=status.HTTP_201_CREATED)
|
||
|
|
async def create_bug_report(
|
||
|
|
body: BugReportCreate,
|
||
|
|
user: User | None = Depends(get_optional_user),
|
||
|
|
session: AsyncSession = Depends(get_session),
|
||
|
|
) -> BugReportResponse:
|
||
|
|
report = BugReport(
|
||
|
|
description=body.description,
|
||
|
|
contact=body.contact,
|
||
|
|
user_id=user.id if user is not None else None,
|
||
|
|
)
|
||
|
|
session.add(report)
|
||
|
|
await session.commit()
|
||
|
|
return BugReportResponse(id=report.id)
|
||
|
|
|
||
|
|
|
||
|
|
class MyBugReportResponse(BaseModel):
|
||
|
|
id: int
|
||
|
|
description: str
|
||
|
|
status: str
|
||
|
|
created_at: str
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/mine", response_model=list[MyBugReportResponse])
|
||
|
|
async def list_my_bug_reports(
|
||
|
|
user: User = Depends(get_current_user),
|
||
|
|
session: AsyncSession = Depends(get_session),
|
||
|
|
) -> list[MyBugReportResponse]:
|
||
|
|
"""The one user-facing history view for bug reports (anonymous submissions have
|
||
|
|
no user to attribute this to, so this only ever covers ones filed while logged in)."""
|
||
|
|
reports = (
|
||
|
|
await session.scalars(
|
||
|
|
select(BugReport).where(BugReport.user_id == user.id).order_by(BugReport.id.desc())
|
||
|
|
)
|
||
|
|
).all()
|
||
|
|
return [
|
||
|
|
MyBugReportResponse(
|
||
|
|
id=r.id, description=r.description, status=r.status, created_at=isoformat_utc(r.created_at)
|
||
|
|
)
|
||
|
|
for r in reports
|
||
|
|
]
|