from uuid import UUID from fastapi import APIRouter, Depends, File, Query, UploadFile from common.enums import ErrorType from common.models.gmodel import PageParams, UserInfo from router.v1.validator.dependencies import IsValidAccessToken, RemoveNoneResponse from services.item_service import ItemService from services.lps_sync_service import LpsSyncService from .protocol import ( LowestPriceEntry, Req_CheckCodes, Req_CreateItem, Req_UpdateItem, Res_CheckCodes, Res_DeleteItem, Res_Item, Res_ItemCategories, Res_ItemImage, Res_ItemList, Res_LowestPriceResult, Res_LowestPriceTrigger, ) # 라우터(컨트롤러). 인증(Depends(IsValidAccessToken))으로 UserInfo 를 받아 company_id 로 스코프. router = APIRouter(prefix="/v1/item", tags=["Item"], responses={404: {"description": "Not found"}}) @router.get(path="/list", response_model=Res_ItemList, summary="상품 목록") async def list_items( service: ItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken), search: str | None = Query(None, description="상품명/상품코드 검색"), category: str | None = Query(None, description="카테고리 필터"), pg: PageParams = Depends(), ): return RemoveNoneResponse(await service.list_items(user_info.company_id, search, category, pg)) @router.get(path="/categories", response_model=Res_ItemCategories, summary="상품 카테고리 목록(distinct)") async def list_item_categories(service: ItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)): return RemoveNoneResponse(await service.list_categories(user_info.company_id)) @router.post(path="/create", response_model=Res_Item, summary="상품 등록") async def create_item(req: Req_CreateItem, service: ItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)): return RemoveNoneResponse( await service.create_item(user_info.company_id, user_info.user_id, req) ) @router.post(path="/check-codes", response_model=Res_CheckCodes, summary="코드 중복 사전검사(업로드 즉시)") async def check_item_codes(req: Req_CheckCodes, service: ItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)): return RemoveNoneResponse(await service.check_codes(user_info.company_id, req.codes)) @router.post(path="/image", response_model=Res_ItemImage, summary="상품 이미지 업로드(Azure Blob)") async def upload_item_image( service: ItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken), file: UploadFile = File(...), ): return RemoveNoneResponse(await service.upload_image(user_info.company_id, file)) @router.get(path="/{item_id}", response_model=Res_Item, summary="상품 조회") async def get_item(item_id: UUID, service: ItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)): return RemoveNoneResponse(await service.get_item(user_info.company_id, str(item_id))) @router.patch(path="/update/{item_id}", response_model=Res_Item, summary="상품 수정") async def update_item( item_id: UUID, req: Req_UpdateItem, service: ItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken) ): return RemoveNoneResponse(await service.update_item(user_info.company_id, str(item_id), req, user_info.user_id, user_info.role)) @router.delete(path="/delete/{item_id}", response_model=Res_DeleteItem, summary="상품 삭제") async def delete_item(item_id: UUID, service: ItemService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken)): return RemoveNoneResponse(await service.delete_item(user_info.company_id, str(item_id), user_info.user_id, user_info.role)) @router.post(path="/{item_id}/lowest-price", response_model=Res_LowestPriceTrigger, summary="최저가 수집 요청") async def trigger_lowest_price( item_id: UUID, service: ItemService = Depends(), lps: LpsSyncService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken), ): """상품 1건을 LPS 에 즉시 검색 요청(수동 트리거, manual 우선순위). 결과는 GET lowest-price 폴링.""" got = await service.get_item(user_info.company_id, str(item_id)) # 존재/소유(company 스코프) 확인 if got.item is None: return RemoveNoneResponse(got) status, message = await lps.request_search_for_item(got.item) res = Res_LowestPriceTrigger(item_id=str(item_id), status=status, message=message) if status == "unavailable": res.result.SetResult(ErrorType.LPS_UNAVAILABLE) return RemoveNoneResponse(res) @router.get(path="/{item_id}/lowest-price", response_model=Res_LowestPriceResult, summary="최저가 수집 결과") async def get_lowest_price( item_id: UUID, service: ItemService = Depends(), lps: LpsSyncService = Depends(), user_info: UserInfo = Depends(IsValidAccessToken), ): """대표 최저가(items.internet_lowest_price) + 최근 수집 이력(최신순). 조회 전에 lps_db 증분 동기화를 한 번 수행해 5분 크론을 기다리지 않는다(멱등·저비용).""" got = await service.get_item(user_info.company_id, str(item_id)) if got.item is None: return RemoveNoneResponse(got) err, history = await lps.lowest_price_view(item_id) res = Res_LowestPriceResult( item_id=str(item_id), lowest_price=got.item.internet_lowest_price, results=[LowestPriceEntry.model_validate(h) for h in history], message="" if history else "수집 이력이 없습니다", ) if err != ErrorType.SUCCESS: res.result.SetResult(err) return RemoveNoneResponse(res)