diff --git a/.aws-sam/build.toml b/.aws-sam/build.toml new file mode 100644 index 00000000..e5bd197c --- /dev/null +++ b/.aws-sam/build.toml @@ -0,0 +1,12 @@ +# This file is auto generated by SAM CLI build command + +[function_build_definitions.447f7c9f-164f-4a26-bf6a-38da6b6910b6] +codeuri = "/Users/cycrpto/Desktop/tuk_sandol_team/sandol" +runtime = "python3.11" +architecture = "x86_64" +handler = "app.handler" +manifest_hash = "" +packagetype = "Zip" +functions = ["FastApiFunction"] + +[layer_build_definitions] diff --git a/__init__.py b/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/events/event.json b/events/event.json new file mode 100644 index 00000000..3ace41e7 --- /dev/null +++ b/events/event.json @@ -0,0 +1,20 @@ +{ + "resource": "/{proxy+}", + "path": "/", + "httpMethod": "GET", + "headers": { + "Content-Type": "application/json" + }, + "queryStringParameters": {}, + "pathParameters": { + "proxy": "/" + }, + "stageVariables": null, + "requestContext": { + "resourcePath": "/{proxy+}", + "httpMethod": "GET", + "path": "/prod/" + }, + "body": null, + "isBase64Encoded": false +} diff --git a/events/meal/meal_event.json b/events/meal/meal_event.json new file mode 100644 index 00000000..e574bb7f --- /dev/null +++ b/events/meal/meal_event.json @@ -0,0 +1,20 @@ +{ + "resource": "/{proxy+}", + "path": "/meal/view", + "httpMethod": "POST", + "headers": { + "Content-Type": "application/json" + }, + "queryStringParameters": {}, + "pathParameters": { + "proxy": "meal/view" + }, + "stageVariables": null, + "requestContext": { + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "path": "/prod/meal/view" + }, + "body": "{\"intent\":{\"id\":\"viqkzodj54puyo8ai5e6m49w\",\"name\":\"블록 이름\"},\"userRequest\":{\"timezone\":\"Asia/Seoul\",\"params\":{\"ignoreMe\":\"true\"},\"block\":{\"id\":\"viqkzodj54puyo8ai5e6m49w\",\"name\":\"블록 이름\"},\"utterance\":\"발화 내용\",\"lang\":null,\"user\":{\"id\":\"702912\",\"type\":\"accountId\",\"properties\":{}}},\"bot\":{\"id\":\"5e0f180affa74800014bd33d\",\"name\":\"봇 이름\"},\"action\":{\"name\":\"u0o68ejcea\",\"clientExtra\":null,\"params\":{\"meal\":\"view\"},\"id\":\"lwnhtra9gidojk68b6xr41o4\",\"detailParams\":{\"meal\":{\"origin\":\"view\",\"value\":\"view\",\"groupName\":\"\"}}}}", + "isBase64Encoded": false +} diff --git a/main.py b/main.py deleted file mode 100644 index b64173ed..00000000 --- a/main.py +++ /dev/null @@ -1,5 +0,0 @@ -"""테스트 서버를 실행시키기 위한 파일입니다.""" -import uvicorn - -if __name__ == "__main__": - uvicorn.run("sandol.app:app", host="0.0.0.0", port=5600, reload=True) diff --git a/packaged.yaml b/packaged.yaml new file mode 100644 index 00000000..89c2feee --- /dev/null +++ b/packaged.yaml @@ -0,0 +1,24 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Resources: + FastApiFunction: + Type: AWS::Serverless::Function + Properties: + Handler: app.handler + Runtime: python3.11 + CodeUri: s3://sandol-deploy-bucket/773a22c99daca4bf37a9bcfe7c23d54f + MemorySize: 128 + Timeout: 300 + Policies: + - AWSLambdaBasicExecutionRole + Environment: + Variables: + ENV: prod + Events: + ApiEvent: + Type: Api + Properties: + Path: /{proxy+} + Method: ANY + Metadata: + SamResourceId: FastApiFunction diff --git a/samconfig.toml b/samconfig.toml new file mode 100644 index 00000000..d04c0642 --- /dev/null +++ b/samconfig.toml @@ -0,0 +1,31 @@ +# More information about the configuration file can be found here: +# https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html +version = 0.1 + +[default] +[default.global.parameters] +stack_name = "untitled" + +[default.build.parameters] +parallel = true + +[default.validate.parameters] +lint = true + +[default.deploy.parameters] +capabilities = "CAPABILITY_IAM" +confirm_changeset = true +#resolve_s3 = true +resolve_image_repos = true + +[default.package.parameters] +#resolve_s3 = true + +[default.sync.parameters] +watch = true + +[default.local_start_api.parameters] +warm_containers = "EAGER" + +[default.local_start_lambda.parameters] +warm_containers = "EAGER" diff --git a/sandol/Dockerfile b/sandol/Dockerfile new file mode 100644 index 00000000..8edd6ff7 --- /dev/null +++ b/sandol/Dockerfile @@ -0,0 +1,18 @@ +# Use the official Python image from the Docker Hub +FROM tiangolo/uvicorn-gunicorn-fastapi:python3.11 + +# Set the working directory +WORKDIR /app + +# Copy the requirements.txt file and install dependencies +COPY requirements.txt /app/ +RUN pip install --no-cache-dir -r /app/requirements.txt + +# Copy the entire 'sandol' directory to the working directory +COPY . /app + +# Expose port 80 +EXPOSE 80 + +# Command to run the FastAPI application +CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "80"] diff --git a/sandol/api_server/kakao/tests/__init__.py b/sandol/api_server/kakao/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/sandol/api_server/meal.py b/sandol/api_server/meal.py index abe530ae..7cdb1c43 100644 --- a/sandol/api_server/meal.py +++ b/sandol/api_server/meal.py @@ -10,17 +10,17 @@ from fastapi import APIRouter from fastapi.responses import JSONResponse -from sandol.api_server.kakao import Payload, ValidationPayload -from sandol.api_server.kakao.response import ( +from api_server.kakao import Payload, ValidationPayload +from api_server.kakao.response import ( KakaoResponse, QuickReply, ActionEnum, ValidationResponse ) -from sandol.api_server.kakao.response.components import SimpleTextComponent -from sandol.api_server.utils import ( +from api_server.kakao.response.components import SimpleTextComponent +from api_server.utils import ( meal_error_response_maker, split_string, meal_response_maker, make_meal_cards, check_tip_and_e ) -from sandol.crawler import ( +from crawler import ( get_registration, Restaurant, get_meals ) diff --git a/sandol/api_server/settings.py b/sandol/api_server/settings.py index c5e894a3..f6d3c2d2 100644 --- a/sandol/api_server/settings.py +++ b/sandol/api_server/settings.py @@ -1,7 +1,7 @@ """응답에 사용되는 상수들을 정의합니다.""" -from sandol.api_server.kakao.response import QuickReply -from sandol.api_server.kakao.response.components import TextCardComponent -from sandol.api_server.kakao.response.interactiron import ActionEnum +from api_server.kakao.response import QuickReply +from api_server.kakao.response.components import TextCardComponent +from api_server.kakao.response.interactiron import ActionEnum # 도움말 QuickReply HELP = QuickReply( diff --git a/sandol/api_server/utils.py b/sandol/api_server/utils.py index 0f37b134..bed12c02 100644 --- a/sandol/api_server/utils.py +++ b/sandol/api_server/utils.py @@ -8,14 +8,14 @@ from functools import wraps import traceback -from sandol.api_server.settings import CAFETRIA_REGISTER_QUICK_REPLY_LIST -from sandol.api_server.kakao.response.components.card import ItemCardComponent -from sandol.api_server.kakao.response import KakaoResponse -from sandol.api_server.kakao.response.components import ( +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 sandol.crawler import Restaurant -from sandol.crawler.ibookcrawler import BookTranslator -from sandol.crawler.ibookdownloader import BookDownloader +from crawler import Restaurant +from crawler.ibookcrawler import BookTranslator +from crawler.ibookdownloader import BookDownloader def split_string(s: str) -> list[str]: diff --git a/sandol/app.py b/sandol/app.py index 23c480ee..118caac8 100644 --- a/sandol/app.py +++ b/sandol/app.py @@ -1,24 +1,21 @@ -"""Sandol의 메인 애플리케이션 파일입니다.""" -from fastapi import FastAPI, HTTPException, Request, status # noqa: F401 # pylint: disable=W0611 -from fastapi.responses import JSONResponse # noqa: F401 - -from sandol.api_server.meal import meal_api -from sandol.api_server.utils import error_message -from sandol.api_server.kakao.response import KakaoResponse - +from fastapi import FastAPI, Request +from fastapi.responses import JSONResponse +from mangum import Mangum +from api_server.meal import meal_api +from api_server.utils import error_message +from api_server.kakao.response import KakaoResponse app = FastAPI() app.include_router(meal_api) - @app.exception_handler(Exception) -async def http_exception_handler(request: Request, exc: Exception): # pylint: disable=W0613 +async def http_exception_handler(request: Request, exc: Exception): return JSONResponse( KakaoResponse().add_component(error_message(exc)).get_dict() ) - @app.get("/") async def root(): - """루트 페이지입니다.""" - return "Hello Sandol" + return {"test": "Hello Sandol"} + +handler = Mangum(app) diff --git a/sandol/crawler/__pycache__/settings.cpython-311.pyc b/sandol/crawler/__pycache__/settings.cpython-311.pyc deleted file mode 100644 index 5b2d30d9..00000000 Binary files a/sandol/crawler/__pycache__/settings.cpython-311.pyc and /dev/null differ diff --git a/requirements.txt b/sandol/requirements.txt similarity index 65% rename from requirements.txt rename to sandol/requirements.txt index 3e162be3..aecd0312 100644 --- a/requirements.txt +++ b/sandol/requirements.txt @@ -1,3 +1,5 @@ +annotated-types==0.7.0 +anyio==4.4.0 beautifulsoup4==4.12.3 blinker==1.7.0 bs4==0.0.2 @@ -7,12 +9,14 @@ click==8.1.7 colorama==0.4.6 et-xmlfile==1.1.0 exceptiongroup==1.2.0 -Flask==3.0.2 +fastapi==0.109.2 +h11==0.14.0 +httptools==0.6.1 idna==3.6 importlib_metadata==7.1.0 iniconfig==2.0.0 itsdangerous==2.1.2 -Jinja2==3.1.3 +mangum==0.17.0 MarkupSafe==2.1.5 numpy==1.26.4 openpyxl==3.1.2 @@ -20,17 +24,27 @@ packaging==24.0 pandas==2.2.2 platformdirs==4.2.0 pluggy==1.4.0 +pydantic==2.7.2 +pydantic_core==2.18.3 pytest==8.1.1 python-dateutil==2.9.0.post0 +python-dotenv==1.0.1 pytz==2024.1 -six==1.16.0 +PyYAML==6.0.1 requests==2.31.0 +six==1.16.0 +sniffio==1.3.1 soupsieve==2.5 +starlette==0.36.3 tomli==2.0.1 types-requests==2.32.0.20240523 +typing_extensions==4.12.0 tzdata==2024.1 urllib3==2.2.1 +uvicorn==0.30.0 +uvloop==0.19.0 +watchfiles==0.22.0 +websockets==12.0 Werkzeug==3.0.1 yapf==0.40.2 zipp==3.18.1 - diff --git a/template.yaml b/template.yaml new file mode 100644 index 00000000..6ab156f4 --- /dev/null +++ b/template.yaml @@ -0,0 +1,22 @@ +AWSTemplateFormatVersion: '2010-09-09' +Transform: AWS::Serverless-2016-10-31 +Resources: + FastApiFunction: + Type: AWS::Serverless::Function + Properties: + Handler: app.handler + Runtime: python3.11 + CodeUri: ./sandol + MemorySize: 128 + Timeout: 300 + Policies: + - AWSLambdaBasicExecutionRole + Environment: + Variables: + ENV: "prod" + Events: + ApiEvent: + Type: Api + Properties: + Path: /{proxy+} + Method: ANY diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/unit/test_handler.py b/tests/unit/test_handler.py new file mode 100644 index 00000000..ab6d6a0a --- /dev/null +++ b/tests/unit/test_handler.py @@ -0,0 +1,72 @@ +import json + +import pytest + +from hello_world import app + + +@pytest.fixture() +def apigw_event(): + """ Generates API GW Event""" + + return { + "body": '{ "test": "body"}', + "resource": "/{proxy+}", + "requestContext": { + "resourceId": "123456", + "apiId": "1234567890", + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "accountId": "123456789012", + "identity": { + "apiKey": "", + "userArn": "", + "cognitoAuthenticationType": "", + "caller": "", + "userAgent": "Custom User Agent String", + "user": "", + "cognitoIdentityPoolId": "", + "cognitoIdentityId": "", + "cognitoAuthenticationProvider": "", + "sourceIp": "127.0.0.1", + "accountId": "", + }, + "stage": "prod", + }, + "queryStringParameters": {"foo": "bar"}, + "headers": { + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "Accept-Language": "en-US,en;q=0.8", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Mobile-Viewer": "false", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "CloudFront-Viewer-Country": "US", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Upgrade-Insecure-Requests": "1", + "X-Forwarded-Port": "443", + "Host": "1234567890.execute-api.us-east-1.amazonaws.com", + "X-Forwarded-Proto": "https", + "X-Amz-Cf-Id": "aaaaaaaaaae3VYQb9jd-nvCd-de396Uhbp027Y2JvkCPNLmGJHqlaA==", + "CloudFront-Is-Tablet-Viewer": "false", + "Cache-Control": "max-age=0", + "User-Agent": "Custom User Agent String", + "CloudFront-Forwarded-Proto": "https", + "Accept-Encoding": "gzip, deflate, sdch", + }, + "pathParameters": {"proxy": "/examplepath"}, + "httpMethod": "POST", + "stageVariables": {"baz": "qux"}, + "path": "/examplepath", + } + + +def test_lambda_handler(apigw_event, mocker): + + ret = app.lambda_handler(apigw_event, "") + data = json.loads(ret["body"]) + + assert ret["statusCode"] == 200 + assert "message" in ret["body"] + assert data["message"] == "hello world" diff --git a/zappa_settings.json b/zappa_settings.json deleted file mode 100644 index df707751..00000000 --- a/zappa_settings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "dev": { - "app_function": "sandol.app.app", - "profile_name": "default", - "project_name": "tuk-sandol-team", - "runtime": "python3.9", - "s3_bucket": "sandol-bucket" - "lambda_function_name": "Kakao-Sandol", - "aws_region": "ap-northeast-2", - "endpoint_configuration":["REGIONAL"] - } -}