100 lines
4.8 KiB
Python
100 lines
4.8 KiB
Python
from uuid import UUID
|
|
|
|
from fastapi import APIRouter, HTTPException, Query
|
|
from sqlalchemy import func, select
|
|
from sqlalchemy.orm import selectinload
|
|
|
|
from ..dependencies import Db
|
|
from ..models import Bait, CatchReport, Fish, ModerationStatus, Rig, Spot, TackleItem, Waterbody
|
|
from ..schemas import BaitOut, FishOut, PaginatedTackleItemOut, RigOut, TackleItemOut, WaterbodyOut
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/api/v1/fishes", response_model=list[FishOut])
|
|
def fishes(db: Db, limit: int = Query(200, ge=1, le=500), offset: int = Query(0, ge=0)) -> list[Fish]:
|
|
return list(db.scalars(select(Fish).order_by(Fish.name_ru, Fish.id).offset(offset).limit(limit)))
|
|
|
|
|
|
@router.get("/api/v1/waterbodies", response_model=list[WaterbodyOut])
|
|
def waterbodies(db: Db, limit: int = Query(200, ge=1, le=500), offset: int = Query(0, ge=0)) -> list[Waterbody]:
|
|
return list(db.scalars(select(Waterbody).order_by(Waterbody.name_ru, Waterbody.id).offset(offset).limit(limit)))
|
|
|
|
|
|
@router.get("/api/v1/baits", response_model=list[BaitOut])
|
|
def baits(db: Db, limit: int = Query(200, ge=1, le=500), offset: int = Query(0, ge=0)) -> list[Bait]:
|
|
return list(db.scalars(select(Bait).order_by(Bait.name, Bait.id).offset(offset).limit(limit)))
|
|
|
|
|
|
def _item_missing_fields(item: TackleItem) -> list[str]:
|
|
return [field for field, value in (
|
|
("subcategory", item.subcategory), ("brand", item.brand),
|
|
("family", item.family), ("unlock_level", item.unlock_level),
|
|
("source_url", item.source_url), ("source_checked_at", item.source_checked_at),
|
|
) if value is None]
|
|
|
|
|
|
@router.get("/api/v1/tackle/items", response_model=PaginatedTackleItemOut)
|
|
def tackle_items(
|
|
db: Db,
|
|
category: str | None = Query(None, pattern="^(bait|lure|rod|reel|line|hook|rig|float|sinker|other)$"),
|
|
brand: str | None = None,
|
|
family: str | None = None,
|
|
unlock_level: int | None = Query(None, ge=0),
|
|
limit: int = Query(50, ge=1, le=100),
|
|
offset: int = Query(0, ge=0),
|
|
) -> PaginatedTackleItemOut:
|
|
query = select(TackleItem)
|
|
if category:
|
|
query = query.where(TackleItem.category == category)
|
|
if brand:
|
|
query = query.where(TackleItem.brand == brand)
|
|
if family:
|
|
query = query.where(TackleItem.family == family)
|
|
if unlock_level is not None:
|
|
query = query.where(TackleItem.unlock_level == unlock_level)
|
|
total = db.scalar(query.with_only_columns(func.count(TackleItem.id), maintain_column_froms=True).order_by(None)) or 0
|
|
items = list(db.scalars(query.order_by(TackleItem.name, TackleItem.id).offset(offset).limit(limit)))
|
|
return PaginatedTackleItemOut(
|
|
items=[TackleItemOut.model_validate(item).model_copy(update={"missing_fields": _item_missing_fields(item)}) for item in items],
|
|
total=total, limit=limit, offset=offset,
|
|
)
|
|
|
|
|
|
@router.get("/api/v1/tackle/items/{item_id}", response_model=TackleItemOut)
|
|
def tackle_item(item_id: UUID, db: Db) -> TackleItemOut:
|
|
item = db.get(TackleItem, item_id)
|
|
if item is None:
|
|
raise HTTPException(status_code=404, detail="tackle item not found")
|
|
return TackleItemOut.model_validate(item).model_copy(update={"missing_fields": _item_missing_fields(item)})
|
|
|
|
|
|
@router.get("/api/v1/tackle/rigs/{rig_id}", response_model=RigOut)
|
|
def rig_detail(rig_id: UUID, db: Db) -> RigOut:
|
|
rig = db.scalar(select(Rig).options(selectinload(Rig.components)).where(Rig.id == rig_id))
|
|
if rig is None:
|
|
raise HTTPException(status_code=404, detail="rig not found")
|
|
missing = [field for field, value in (
|
|
("source_url", rig.source_url), ("source_checked_at", rig.source_checked_at),
|
|
) if value is None]
|
|
return RigOut(
|
|
id=rig.id, name=rig.name, source_system=rig.source_system,
|
|
source_external_id=rig.source_external_id, source_url=rig.source_url,
|
|
source_checked_at=rig.source_checked_at, missing_fields=missing,
|
|
components=[{
|
|
"id": component.id, "role": component.role, "position": component.position,
|
|
"raw_value": component.raw_value, "tackle_item_id": component.tackle_item_id,
|
|
} for component in sorted(rig.components, key=lambda value: value.position)],
|
|
)
|
|
|
|
|
|
@router.get("/api/v1/public-spot-pages")
|
|
def public_spot_pages(db: Db, limit: int = Query(500, ge=1, le=500), offset: int = Query(0, ge=0)) -> list[str]:
|
|
rows = db.execute(select(Waterbody.slug, Spot.x, Spot.y, Fish.slug)
|
|
.select_from(CatchReport).join(Spot, CatchReport.spot_id == Spot.id)
|
|
.join(Waterbody, Spot.waterbody_id == Waterbody.id).join(Fish, CatchReport.fish_id == Fish.id)
|
|
.where(CatchReport.moderation_status == ModerationStatus.approved, CatchReport.deleted_at.is_(None))
|
|
.distinct().order_by(Waterbody.slug, Spot.x, Spot.y, Fish.slug).offset(offset).limit(limit))
|
|
return [path for water, x, y, fish in rows for path in
|
|
(f"/spots/{water}-{x}x{y}", f"/waterbodies/{water}/{fish}")]
|