node -> python 변경 진행중

This commit is contained in:
bumpsoo 2024-07-01 02:13:51 +09:00
commit 660f85f145
5 changed files with 285 additions and 0 deletions

79
main.py Normal file
View file

@ -0,0 +1,79 @@
from datetime import datetime
import json
from typing import Dict, Any, Final, List, Optional
import requests
import os
import time
from dataclasses import dataclass
import menu
import schedule
# Load environment variables (recommended for sensitive data)
# SLACK_WEBHOOK_URL = os.environ['SLACK_WEBHOOK_URL']
#EXTERNAL_API_URL = os.environ['EXTERNAL_API_URL']
PARAM_ERROR: Final[Dict[str, str]] = {
'statusCode': '200', 'error': 'wrong parameter'
}
MAX_RETRY: Final[int] = 5
RETRY_INTERVAL: Final[int] = 15
@dataclass
class Param:
slack_url: str
date: str
count: int
def __parse_param(evt: Dict[str, Any]) -> Optional[Param]:
if 'slack_url' not in evt:
return None
try:
date = evt['date']
except:
date = datetime.now().strftime('%Y%m%d')
cnt = evt.get('count') or 0
return Param(str(evt.get('slack_url')), date, cnt + 1)
def retry(p: Param, lambda_arn: str, schedule_role_arn: str) -> Dict[str, str]:
schedule.one_time_schedule(lambda_arn, schedule_role_arn, RETRY_INTERVAL, p)
return {'statusCode': '200', 'body': f'retry {p.count}'}
def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, str]:
os.environ['TZ'] = 'Asia/Seoul'
time.tzset()
lambda_arn = os.environ['LAMBDA_ARN']
schedule_role_arn = os.environ['SCHEDULE_ROLE_ARN']
param = __parse_param(event)
if param is None:
return PARAM_ERROR
menus = menu.menu(param.date)
menus_without_img = [ x for x in menus if x.menu_image is None ]
if len(menus) == 0:
return retry(param, lambda_arn, schedule_role_arn)
menus_without_img: List[menu.Menu] = [ x for x in menus if x.menu_image is None ]
if len(menus_without_img) > 0 and param.count < MAX_RETRY:
return retry(param, lambda_arn, schedule_role_arn)
# Extract relevant data for your Slack message (adjust as needed)
message_content: str = f"*Important Data from External API*\n"
message_content += f"`\n{json.dumps(api_data, indent=4)}\n`"
# Construct payload for the Slack webhook
slack_payload: Dict[str, Any] = {
"text": message_content,
"blocks": [
{"type": "section", "text": {"type": "mrkdwn", "text": message_content}}
]
}
# Send the message to Slack
slack_response: requests.Response = requests.post(
'',
#SLACK_WEBHOOK_URL,
data=json.dumps(slack_payload),
headers={'Content-Type': 'application/json'}
)
slack_response.raise_for_status()
return {
'statusCode': '200',
'body': json.dumps('Data successfully sent to Slack.')
}

95
main.tf Normal file
View file

@ -0,0 +1,95 @@
# Provider Configuration
provider "aws" {
region = "ap-northeast-2"
}
# Locals for Constants (replace values as needed)
locals {
prefix = "bumpsoo-menu"
image_bucket_name = "${local.prefix}-img-bucket"
lambda_role_name = "${local.prefix}-lambda-role"
lambda_function_name = "${local.prefix}-lambda"
lambda_filename = "artifacts.zip" # Zip file containing Lambda code
lambda_handler = "lambda_function.lambda_handler" # Replace with your handler
weekday_rule_name = "${local.prefix}-weekday-image-upload"
}
# S3 Bucket (Publicly Accessible)
resource "aws_s3_bucket" "image_bucket" {
bucket = local.image_bucket_name
acl = "public-read"
# Policy for public read access to objects
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "PublicReadGetObject"
Effect = "Allow"
Principal = "*"
Action = "s3:GetObject"
Resource = "arn:aws:s3:::${aws_s3_bucket.image_bucket.bucket}/*"
}
]
})
}
# IAM Role for Lambda (EventBridge Permissions)
resource "aws_iam_role" "lambda_role" {
name = local.lambda_role_name
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Principal = {
Service = "lambda.amazonaws.com"
}
Effect = "Allow"
}
]
})
# Policy to allow EventBridge rule creation/management
inline_policy {
name = "lambda_eventbridge_policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"events:PutRule",
"events:PutTargets"
]
Resource = "*"
}
]
})
}
}
# Lambda Function
resource "aws_lambda_function" "image_lambda" {
function_name = local.lambda_function_name
filename = local.lambda_filename
role = aws_iam_role.lambda_role.arn
handler = local.lambda_handler
runtime = "python3.11"
}
# EventBridge Rule
resource "aws_cloudwatch_event_rule" "weekday_rule" {
name = local.weekday_rule_name
description = "Trigger Lambda at 10 AM on weekdays"
schedule_expression = "cron(0 10 ? * MON-FRI *)" # 10 AM every workday in KST timezone
}
# EventBridge Target (Lambda)
resource "aws_cloudwatch_event_target" "lambda_target" {
rule = aws_cloudwatch_event_rule.weekday_rule.name
target_id = "lambda"
arn = aws_lambda_function.image_lambda.arn
}

63
menu.py Normal file
View file

@ -0,0 +1,63 @@
# menu repository
from dataclasses import dataclass
from typing import Any, Dict, Final, List, Optional
import requests
import slack
BASE: Final[str] = 'https://sfmn.shinsegaefood.com/'
URL: Final[str] = f'{BASE}selectTodayMenu2.do'
HEADER: Final[Dict[str, str]] = {
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1',
'Content-Type': 'application/json',
'Referer': 'https://sfmn.shinsegaefood.com/selectTodayMenu.do',
}
STORE: Final[str] = '06379'
@dataclass
class Menu:
meal_time: str
meal_type: str
rep_menu: str
detailed_menu: str
calory: str
menu_image: Optional[str]
def __post_init__(self):
if self.menu_image:
self.menu_image = f"https://store.shinsegaefood.com/{self.menu_image}"
def to_slack_block(self, date: str) -> Dict[str, Any]:
blocks = [slack.markdown(f'*{date} 일자 메뉴*')]
return {}
# date should be this form 20240601
def menu(date: str) -> List[Menu]:
menus: List[Menu] = []
res = requests.post(URL, headers=HEADER, json={
'menuDate': date,
'storeCd': STORE,
'cafeCd': '01',
'dispBaseCd': '0',
'userLang': 'K'
})
if res.status_code != 200:
return menus
data: List[Dict[str, str]] = []
try:
data = res.json()['model']['model']
except:
return menus
for each in data:
try:
menus.append(Menu(
each['MEAL_TYPE_NM'],
each['DINNER_TYPE_NM'],
each['REP_TYPE_NM'],
each['MENU_DESC'],
each['TOT_CALORY'],
each.get('MEAL_TYPE_NM'),
))
except:
pass
return menus

38
schedule.py Normal file
View file

@ -0,0 +1,38 @@
import boto3
from datetime import datetime, timedelta
import json
import main
def one_time_schedule(
lambda_arn: str,
schedule_role_arn: str,
minutes: int,
param: main.Param
) -> str:
client = boto3.client('scheduler')
schedule_time = datetime.now() + timedelta(minutes = minutes)
schedule_expression = f"at({schedule_time.isoformat()})"
schedule_name = f"menu-trigger-{schedule_time.strftime('%Y%m%d%H%M%S')}"
rule_response = client.create_schedule(
Name = schedule_name,
ScheduleExpression = schedule_expression,
ScheduleExpressionTimezone = 'Asia/Seoul',
State = 'ENABLED',
Target = {
'Arn': lambda_arn,
'RoleArn': schedule_role_arn,
'Input': json.dumps({
'slack_url': param.slack_url,
'date': param.date,
'count': param.count + 1
}),
'RetryPolicy': {'MaximumRetryAttempts': 0}
},
)
client.update_schedule(
Name = schedule_name,
ActionAfterCompletion = 'DELETE'
)
return rule_response['RuleArn']

10
slack.py Normal file
View file

@ -0,0 +1,10 @@
from typing import Any, Dict
def markdown(text: str) -> Dict[str, Any]:
return {
'type': 'section',
'text': {
'type': 'mrkdwn',
'text': text
}
}