지역 정보(맛집·관광지·축제)는 행정구역 코드(local_contents.region_code) 단위로 캐시돼 "그 시군구에 있는 것"을 줬다. 양양군 업장 옆 5km 속초 관광지는 빠지고 같은 군 반대편 30km 맛집이 붙는 구조라, 캔버스의 지역 정보 섹션은 늘 "준비 중"이었다. 업장 좌표로 TourAPI 를 직접 물어 업장 단위(place_contents)에 담고, 캔버스는 스크린샷으로 받은 형식(도보 시간 필터 + 카드 캐러셀)으로 통일했다.
78 lines
3.8 KiB
Python
78 lines
3.8 KiB
Python
import uuid
|
|
|
|
from fastapi import APIRouter, Depends, Query
|
|
|
|
from common.enums import LocalContentStatus
|
|
from common.models.gmodel import UserInfo
|
|
from router.v1.validator.dependencies import RequireOwner, RemoveNoneResponse
|
|
from services.local_content_service import LocalContentService
|
|
from .protocol import (
|
|
ReqHidePlaceContent, ReqPublishLocalContent, ReqUpdateLocalContent,
|
|
ResLocalContentList, ResLocalGuide, ResPlaceContentList, ResSyncPlace, ResWeather,
|
|
)
|
|
|
|
router = APIRouter(prefix="/v1/admin/local-content", tags=["LocalContent"])
|
|
weather_router = APIRouter(prefix="/v1/local", tags=["LocalContent"])
|
|
|
|
|
|
@weather_router.get("/weather", response_model=ResWeather, summary="현재 날씨 조회 (cache-aside)")
|
|
async def get_weather(
|
|
region_code: str = Query(min_length=1, max_length=10),
|
|
latitude: float = Query(ge=-90, le=90),
|
|
longitude: float = Query(ge=-180, le=180),
|
|
service: LocalContentService = Depends(),
|
|
):
|
|
return RemoveNoneResponse(await service.get_weather(region_code, latitude, longitude))
|
|
|
|
|
|
@weather_router.get("/guide", response_model=ResLocalGuide, summary="업장 주변 가이드(맛집·명소·축제·코스) — 에디터 캔버스용")
|
|
async def get_guide(
|
|
place_id: uuid.UUID = Query(),
|
|
service: LocalContentService = Depends(),
|
|
):
|
|
"""날씨와 같은 공개 조회다 — 운영자가 숨기지 않은 공공데이터만 나가므로 인증을 요구하지 않는다."""
|
|
return RemoveNoneResponse(await service.get_guide(place_id))
|
|
|
|
|
|
@router.get("/place/{place_id}", response_model=ResPlaceContentList, summary="업장 주변정보 목록 (숨김 포함)")
|
|
async def list_place_contents(place_id: uuid.UUID, service: LocalContentService = Depends(), _user=Depends(RequireOwner)):
|
|
return RemoveNoneResponse(await service.list_place_contents(place_id))
|
|
|
|
|
|
@router.post("/place/{place_id}/sync", response_model=ResSyncPlace, summary="업장 주변정보 재수집 (TourAPI 반경)")
|
|
async def sync_place(place_id: uuid.UUID, service: LocalContentService = Depends(), _user=Depends(RequireOwner)):
|
|
"""빌드가 매번 하는 것과 같은 수집을 운영자가 직접 누른다. 무인 갱신(스케줄러)은 아직 없다."""
|
|
return RemoveNoneResponse(await service.sync_place_by_id(place_id))
|
|
|
|
|
|
@router.post("/place-content/{place_content_id}/hide", response_model=ResPlaceContentList, summary="주변정보 숨김/해제")
|
|
async def hide_place_content(
|
|
place_content_id: uuid.UUID, req: ReqHidePlaceContent,
|
|
service: LocalContentService = Depends(), _user=Depends(RequireOwner),
|
|
):
|
|
"""숨긴 항목은 재수집이 되살리지 않는다. 다음 빌드부터 발행본에서 빠진다."""
|
|
return RemoveNoneResponse(await service.set_hidden(place_content_id, req.hidden))
|
|
|
|
|
|
@router.get("", response_model=ResLocalContentList)
|
|
async def list_contents(
|
|
status: LocalContentStatus | None = Query(None), region_code: str | None = Query(None),
|
|
service: LocalContentService = Depends(), _user: UserInfo = Depends(RequireOwner),
|
|
):
|
|
return RemoveNoneResponse(await service.list(status.value if status else None, region_code))
|
|
|
|
|
|
@router.post("/publish", response_model=ResLocalContentList)
|
|
async def publish(req: ReqPublishLocalContent, service: LocalContentService = Depends(), user=Depends(RequireOwner)):
|
|
return RemoveNoneResponse(await service.publish(req.content_ids, user.user_id))
|
|
|
|
|
|
@router.patch("/{content_id}", response_model=ResLocalContentList)
|
|
async def update_content(content_id: str, req: ReqUpdateLocalContent, service: LocalContentService = Depends(), _user=Depends(RequireOwner)):
|
|
return RemoveNoneResponse(await service.update(content_id, req))
|
|
|
|
|
|
@router.post("/{content_id}/end", response_model=ResLocalContentList)
|
|
async def end_content(content_id: str, service: LocalContentService = Depends(), _user=Depends(RequireOwner)):
|
|
return RemoveNoneResponse(await service.end(content_id))
|