o2o-site-AEO/solution/backend/router/v1/media/media.py
Mina Choi 1dc4f5fbf5 merge: origin/main(에이전트 여러 동작 · 사진 도구 · 카톡 승인 알림) 을 feature/template-catalog 에 병합
충돌은 이 브랜치의 주석 정리 커밋(11d30bb)과 main 의 기능 변경이 겹친 백엔드 9개 파일 —
main 쪽을 그대로 썼다(기능이 main 에만 있다). AGENTS.md 는 양쪽 링크를 모두 두고,
DEVLOG 는 요약본 위에 main 의 09-28 에이전트 항목 셋을 붙였다.

site eslint 통과 · vitest 123 passed · frontend tsc·eslint 통과 · 백엔드 py_compile 통과
(백엔드 pytest 는 이 기계에 테스트 DB 계정이 없어 실행하지 못함)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 13:51:53 +09:00

67 lines
2.9 KiB
Python

from typing import Optional
from uuid import UUID
from fastapi import APIRouter, Depends, Query
from common.models.gmodel import UserInfo
from router.v1.validator.dependencies import IsValidAccessToken, RemoveNoneResponse
from services.media_service import MediaService
from .protocol import Res_MediaList
# 사진 라우터.
router = APIRouter(prefix="/v1/place/{place_id}/media", tags=["Media"], responses={404: {"description": "Not found"}})
@router.get(
path="/list",
response_model=Res_MediaList,
summary="사진 목록",
description="publishable_only=true 면 ★ 사이트에 실제로 나갈 수 있는 것(승인 + alt 있음)만 — "
"빌드 스냅샷과 같은 기준이다. unit_id 를 주면 그 객실·메뉴 사진만. "
"★ 응답에는 항상 source_type(owner/crawl)과 origin_url 이 실린다 — "
"크롤링 이미지 재게시 권리가 미결이라(docs/DECISIONS.md 1-2) 결론에 따라 "
"CRAWL 을 통째로 걸러내야 하고, 화면이 출처를 모르면 그 판단을 못 한다.",
)
async def list_media(
place_id: UUID,
service: MediaService = Depends(),
user_info: UserInfo = Depends(IsValidAccessToken),
unit_id: Optional[UUID] = Query(None, description="객실·메뉴 단위 사진만"),
publishable_only: bool = Query(False),
):
return RemoveNoneResponse(await service.list_media(user_info, str(place_id), unit_id, publishable_only))
@router.post(
path="/{media_id}/hide",
response_model=Res_MediaList,
summary="사진 내리기",
description="그 사진을 REJECTED 로 내려 발행본에서 뺀다. ★ 지우지 않는다 — origin_url·source_type 이 "
"남아 있어야 재게시 권리(docs/DECISIONS.md 1-2) 결론이 났을 때 되짚을 수 있고, "
"잘못 내렸을 때 되돌릴 수도 있다. 응답은 갱신된 목록이다.",
)
async def hide_media(
place_id: UUID,
media_id: UUID,
service: MediaService = Depends(),
user_info: UserInfo = Depends(IsValidAccessToken),
):
return RemoveNoneResponse(await service.hide_media(user_info, str(place_id), str(media_id)))
@router.post(
path="/{media_id}/primary",
response_model=Res_MediaList,
summary="대표 사진 지정",
description="목록 맨 앞으로 올린다. ★ 대표 사진은 별도 칸이 아니라 **목록의 첫 장**이다"
"(site_payload.primary_media) — 칸을 따로 두면 검색 결과에 뜨는 그림과 화면 첫 장이 갈린다. "
"객실·메뉴 전용 사진(unit_id 가 있는 것)은 대표가 될 수 없다.",
)
async def set_primary_media(
place_id: UUID,
media_id: UUID,
service: MediaService = Depends(),
user_info: UserInfo = Depends(IsValidAccessToken),
):
return RemoveNoneResponse(await service.set_primary(user_info, str(place_id), str(media_id)))