Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/feature/cafeteria-1' into featur…
Browse files Browse the repository at this point in the history
…e/deploy
  • Loading branch information
Cycrypto committed Jun 5, 2024
2 parents c653681 + cea5252 commit 2abff6a
Show file tree
Hide file tree
Showing 11 changed files with 775 additions and 594 deletions.
Binary file added data.xlsx
Binary file not shown.
149 changes: 105 additions & 44 deletions sandol/api_server/meal.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@
from api_server.kakao.response import (
KakaoResponse, QuickReply, ActionEnum, ValidationResponse
)
from api_server.kakao.response.components import SimpleTextComponent
from api_server.kakao.response.components import SimpleTextComponent, ItemCardComponent, ImageTitle
from api_server.utils import (
meal_error_response_maker, split_string,
meal_response_maker, make_meal_cards,
check_tip_and_e
)
from api_server.settings import NAVER_MAP_URL_DICT
from crawler import (
get_registration, Restaurant, get_meals
)
from crawler.settings import KST

meal_api = APIRouter(prefix="/meal")

Expand All @@ -40,45 +42,6 @@ async def get_payload_and_restaurant(request: Request):
return payload, restaurant


@meal_api.post("/register/{meal_type}")
async def meal_register(meal_type: str, request: Request):
"""식단 정보를 등록합니다.
중식 등록 및 석식 등록 스킬을 처리합니다.
중식 및 석식 등록 발화시 호출되는 API입니다.
Args:
meal_type (str): 중식 또는 석식을 나타내는 문자열입니다.
lunch, dinner 2가지 중 하나의 문자열이어야 합니다.
"""

payload, restaurant = await get_payload_and_restaurant(request)

# 카카오에서 전달받은 menu 파라미터를 구분자를 기준으로 분리해 리스트로 변환
assert payload.detail_params is not None
menu_list = split_string(
payload.detail_params["menu"].origin)

# TODO(Seokyoung_Hong): 메뉴 등록 개수 제한기능 필요시 활성화
# if len(getattr(restaurant, f"temp_{meal_type}", []) + menu_list) > 5:
# return meal_error_response_maker("메뉴는 5개까지만 등록할 수 있습니다.").get_json()

# 메뉴를 등록
for menu in menu_list:
try:
restaurant.add_menu(meal_type, menu)
except ValueError as e:
if str(e) != "해당 메뉴는 이미 메뉴 목록에 존재합니다.":
raise e

restaurant.save_temp_menu()

lunch, dinner = make_meal_cards([restaurant], is_temp=True)
response = meal_response_maker(lunch, dinner)

return JSONResponse(response.get_dict())


@meal_api.post("/register/delete/{meal_type}")
async def meal_delete(meal_type: str, request: Request):
"""삭제할 메뉴를 선택하는 API입니다.
Expand Down Expand Up @@ -161,6 +124,45 @@ async def meal_menu_delete(request: Request):
return JSONResponse(response.get_dict())


@meal_api.post("/register/{meal_type}")
async def meal_register(meal_type: str, request: Request):
"""식단 정보를 등록합니다.
중식 등록 및 석식 등록 스킬을 처리합니다.
중식 및 석식 등록 발화시 호출되는 API입니다.
Args:
meal_type (str): 중식 또는 석식을 나타내는 문자열입니다.
lunch, dinner 2가지 중 하나의 문자열이어야 합니다.
"""

payload, restaurant = await get_payload_and_restaurant(request)

# 카카오에서 전달받은 menu 파라미터를 구분자를 기준으로 분리해 리스트로 변환
assert payload.detail_params is not None
menu_list = split_string(
payload.detail_params["menu"].origin)

# TODO(Seokyoung_Hong): 메뉴 등록 개수 제한기능 필요시 활성화
# if len(getattr(restaurant, f"temp_{meal_type}", []) + menu_list) > 5:
# return meal_error_response_maker("메뉴는 5개까지만 등록할 수 있습니다.").get_json()

# 메뉴를 등록
for menu in menu_list:
try:
restaurant.add_menu(meal_type, menu)
except ValueError as e:
if str(e) != "해당 메뉴는 이미 메뉴 목록에 존재합니다.":
raise e

restaurant.save_temp_menu()

lunch, dinner = make_meal_cards([restaurant], is_temp=True)
response = meal_response_maker(lunch, dinner)

return JSONResponse(response.get_dict())


@meal_api.post("/submit")
@check_tip_and_e
async def meal_submit(request: Request):
Expand Down Expand Up @@ -213,7 +215,7 @@ async def meal_view(request: Request):
target_cafeteria = getattr(cafeteria, "value", None)

# 식당 정보를 가져옵니다.
cafeteria_list: list[Restaurant] = get_meals()
cafeteria_list: list[Restaurant] = await get_meals()

# cafeteria 값이 있을 경우 해당 식당 정보로 필터링

Expand All @@ -226,12 +228,18 @@ async def meal_view(request: Request):
else:
restaurants = cafeteria_list

standard_time = datetime.now() - timedelta(days=1)
standard_time = standard_time.replace(hour=19, minute=0, second=0, microsecond=0)
# 어제 7시를 기준으로 식당 정보를 필터링
standard_time = datetime.now(tz=KST) - timedelta(days=1)
standard_time = standard_time.replace(
hour=19, minute=0, second=0, microsecond=0)

af_standard: List[Restaurant] = []
bf_standard: List[Restaurant] = []
for r in restaurants:
if r.registration_time.tzinfo is None:
print("등록시간에 시간대 정보가 없어 9시간을 더해 KST로 변환합니다.")
temp = r.registration_time + timedelta(hours=9)
r.registration_time = temp.replace(tzinfo=KST)
if r.registration_time < standard_time:
bf_standard.append(r)
else:
Expand All @@ -257,19 +265,72 @@ async def meal_view(request: Request):
response.add_quick_reply(
label="모두 보기",
action="message",
message_text="테스트 학식",
message_text="테스트 학식", # TODO(Seokyoung_Hong): 베포 시 '테스트' 제거
)
for rest in cafeteria_list:
if rest.name != target_cafeteria:
response.add_quick_reply(
label=rest.name,
action="message",
# TODO(Seokyoung_Hong): 베포 시 '테스트' 제거
message_text=f"테스트 학식 {rest.name}",
)

return JSONResponse(response.get_dict())


@meal_api.post("/restaurant")
async def meal_restaurant(request: Request):
"""식당 정보를 반환하는 API입니다."""
payload = Payload.from_dict(await request.json())

restaurant_name: str = payload.action.client_extra["restaurant_name"]

# 식당 정보를 가져옵니다.
cafeteria_list: list[Restaurant] = await get_meals()

restaurant: Restaurant = list(
filter(lambda x: x.name == restaurant_name, cafeteria_list))[0]

item_card = ItemCardComponent([])
item_card.image_title = ImageTitle(
title=restaurant.name,
description="식당 정보"
)
item_card.add_item(
title="점심 시간",
description="~".join(restaurant.opening_time[0])
)
item_card.add_item(
title="저녁 시간",
description="~".join(restaurant.opening_time[1])
)
item_card.add_item(
title="위치",
description=restaurant.location
)
item_card.add_item(
title="가격",
description=f"{restaurant.price_per_person}원"
)
item_card.add_button(
label="메뉴 보기",
action="message",
# TODO(Seokyoung_Hong): 베포 시 '테스트' 제거
message_text=f"테스트 학식 {restaurant_name}"
)
url = NAVER_MAP_URL_DICT.get(restaurant_name, None)
if url:
item_card.add_button(
label="식당 위치 지도 보기",
action="webLink",
web_link_url=url
)
response = KakaoResponse().add_component(item_card)

return JSONResponse(response.get_dict())


@meal_api.post("/validation/menu")
async def validation_menu(request: Request):
"""메뉴 유효성 검사 API입니다."""
Expand Down
10 changes: 10 additions & 0 deletions sandol/api_server/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,13 @@
QuickReply(
"모든 메뉴 삭제", ActionEnum.BLOCK, block_id="6643a2ce0431eb378ea12748")
]

# 식당별 네이버 지도 URL
NAVER_MAP_URL_DICT = {
"미가식당": "https://naver.me/xEMZ6QdE",
"세미콘식당": "https://naver.me/xQ8Khcho",
"산돌식당": "https://naver.me/xEMZ6QdE",
"TIP 가가식당": "https://naver.me/GCaFzr8k",
"E동 레스토랑": "https://naver.me/GRO427Hk",
"수호식당": "https://naver.me/Gz18OrEt",
}
92 changes: 72 additions & 20 deletions sandol/api_server/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,42 @@
from functools import wraps
import traceback

import openpyxl

from api_server.settings import CAFETRIA_REGISTER_QUICK_REPLY_LIST
from api_server.kakao.response.components.card import ItemCardComponent
from api_server.kakao.response import KakaoResponse
from api_server.kakao.response.components import (
CarouselComponent, TextCardComponent, SimpleTextComponent)
from crawler import Restaurant
from crawler.settings import KST
from crawler.ibookcrawler import BookTranslator
from crawler.ibookdownloader import BookDownloader


def get_last_saved_date(filepath: str) -> datetime:
"""Excel 파일의 마지막으로 저장된 날짜를 반환합니다.
Excel 파일의 경로를 받아 마지막으로 저장된 날짜를 반환합니다.
만약 파일이 없는 등 예외가 발생할 경우 현재 시간에서 7일 전으로 설정합니다.
Args:
filepath (str): Excel 파일 경로
Returns:
datetime: 마지막으로 저장된 날짜
"""
try:
# Excel 파일 열기
workbook = openpyxl.load_workbook(filepath, read_only=True)
# 마지막으로 저장된 날짜 가져오기
last_saved = workbook.properties.modified
return last_saved.astimezone(KST)
except Exception as e: # pylint: disable=broad-except
print(f"Error: {e}")
return datetime.now(tz=KST) - timedelta(days=7)


def split_string(s: str) -> list[str]:
"""문자열을 구분자를 기준으로 분리하여 리스트로 반환합니다.
Expand Down Expand Up @@ -105,13 +131,30 @@ def make_meal_card(
# title="메뉴",
# description="식단 정보가 없습니다."
# )
# response.add_button(
# label="식당 정보 보기",
# action="block",
# block_id="665ca91ba99186411b75b8c9",
# extra={
# "restaurant_name": restaurant.name
# }
# )

# return response

description = "\n".join(menu_list) if menu_list else "식단 정보가 없습니다."

description += formatted_time
return TextCardComponent(title=title, description=description)
textcard = TextCardComponent(title=title, description=description)
textcard.add_button(
label="식당 정보 보기",
action="block",
block_id="665ca91ba99186411b75b8c9",
extra={
"restaurant_name": restaurant.name
}
)
return textcard


def make_meal_cards(
Expand Down Expand Up @@ -205,7 +248,7 @@ def error_message(message: str | BaseException) -> TextCardComponent:
def check_tip_and_e(func):
"""TIP 가가식당과 E동 레스토랑 정보를 업데이트하는 데코레이터
data.xlsx 파일의 수정 시간을 확인하여 이번일요일 이전에 업데이트된 경우
data.xlsx 파일의 수정 시간을 확인하여 지난수요일 이전에 업데이트된 경우
data.xlsx 파일을 다운로드하고, TIP 가가식당과 E동 레스토랑 정보를 업데이트합니다.
"""
@wraps(func)
Expand All @@ -214,28 +257,37 @@ async def wrapper(*args, **kwargs):
file_path = "/tmp/data.xlsx"

must_download = False
if os.path.exists(file_path):
file_mod_time = os.path.getmtime(file_path)
file_mod_datetime = datetime.fromtimestamp(file_mod_time)

today = datetime.now(tz=KST)
# 지난 주 수요일 날짜 계산
if today.weekday() >= 2:
last_wednesday = today - timedelta(days=today.weekday() - 2 + 7)
else:
must_download = True
last_wednesday = today - timedelta(days=today.weekday() - 2 + 14)

# 이번 주 일요일 날짜 계산
today = datetime.now()
start_of_week = today - timedelta(days=today.weekday() + 1)
start_of_week = start_of_week.replace(
# 시간, 분, 초, 마이크로초를 0으로 설정
last_wednesday = last_wednesday.replace(
hour=0, minute=0, second=0, microsecond=0)
start_of_day = today.replace(hour=0, minute=0, second=0, microsecond=0)

if os.path.exists(file_path):
ibook = BookTranslator()
tip = Restaurant.by_id("001")
registration_time = get_last_saved_date(file_path)

if registration_time < last_wednesday:
must_download = True
else:
must_download = True

# 파일 수정 시간이 이번 주 일요일 이후인지 확인
# if must_download or not file_mod_datetime > start_of_week:
# downloader = BookDownloader()
# downloader.get_file(file_path) # /tmp/data.xlsx에 파일 저장
#
# ibook = BookTranslator()
#
# # 식단 정보 test.json에 저장
# ibook.submit_tip_info()
# ibook.submit_e_info()
if must_download:
downloader = BookDownloader()
downloader.get_file(file_path) # data.xlsx에 파일 저장

if must_download or tip.registration_time < start_of_day:
# 식단 정보 test.json에 저장
ibook = BookTranslator()
ibook.submit_tip_info()
ibook.submit_e_info()
return await func(*args, **kwargs)
return wrapper
Loading

0 comments on commit 2abff6a

Please sign in to comment.