Skip to content

Commit

Permalink
Merge branch 'main' into feature/#241
Browse files Browse the repository at this point in the history
  • Loading branch information
AsukaNamiki authored Jan 4, 2025
2 parents 0743e7e + 1aa3fe0 commit 1a2b3f3
Show file tree
Hide file tree
Showing 21 changed files with 2,106 additions and 245 deletions.
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ name = "backend"
version = "0.1.0"
description = "Add your description here"
dependencies = [
"pytest>=8.3.3",
"boto3>=1.35.57",
"moto>=5.0.20",
"ruff>=0.7.3",
"pytest>=8.3.3",
"boto3>=1.35.57",
"moto>=5.0.20",
"ruff>=0.8.4",
]
readme = "README.md"
requires-python = ">= 3.8"
Expand Down
2 changes: 1 addition & 1 deletion requirements-dev.lock
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ requests==2.32.3
# via responses
responses==0.25.3
# via moto
ruff==0.7.3
ruff==0.8.4
# via backend
s3transfer==0.10.3
# via boto3
Expand Down
2 changes: 1 addition & 1 deletion requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ requests==2.32.3
# via responses
responses==0.25.3
# via moto
ruff==0.7.3
ruff==0.8.4
# via backend
s3transfer==0.10.3
# via boto3
Expand Down
4 changes: 2 additions & 2 deletions src/backend/lambda/bouquet_create/bouquet_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
dynamodb = boto3.resource("dynamodb")
s3 = boto3.client("s3")

FLOWER_BUCKET_NAME = os.environ["FLOWER_BUCKET_NAME"]
ORIGINAL_IMAGE_BUCKET_NAME = os.environ["ORIGINAL_IMAGE_BUCKET_NAME"]
BOUQUET_BUCKET_NAME = os.environ["BOUQUET_BUCKET_NAME"]
GENERATIVE_AI_TABLE_NAME = os.environ["GENERATIVE_AI_TABLE_NAME"]
BOUQUET_TABLE_NAME = os.environ["BOUQUET_TABLE_NAME"]
Expand Down Expand Up @@ -847,7 +847,7 @@ def load_image(self, key):
Returns:
Image: ロードされた画像
"""
obj = s3.get_object(Bucket=FLOWER_BUCKET_NAME, Key=key)
obj = s3.get_object(Bucket=ORIGINAL_IMAGE_BUCKET_NAME, Key=key)
img = Image.open(obj["Body"]).convert("RGBA")
return img

Expand Down
1 change: 1 addition & 0 deletions src/backend/lambda/diary_create/diary_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def validate_input(body):
)



def save_to_dynamodb(user_id, date, content, is_deleted=False):
"""
DynamoDB に日記のアイテムを保存し、生成した diary_id を返す関数。
Expand Down
2 changes: 1 addition & 1 deletion src/backend/lambda/flower_get/flower_get.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def get_img_from_s3(flower_id: str) -> str:
str: 画像のバイナリデータ(Base64エンコード済み)
"""
s3 = boto3.client("s3")
bucket_name = os.environ["BUCKET_NAME"]
bucket_name = os.environ["FLOWER_BUCKET_NAME"]
s3_key = f"flowers/{flower_id}.png"

try:
Expand Down
278 changes: 278 additions & 0 deletions src/backend/lambda/get_diary_data/get_diary_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
import base64
import json
import logging
import os
from datetime import datetime, timezone
from typing import Any, Dict, Optional

import boto3
from botocore.exceptions import ClientError

logger = logging.getLogger(__name__)
formatter = logging.Formatter(
"[%(asctime)s - %(levelname)s - %(filename)s(func:%(funcName)s, line:%(lineno)d)] %(message)s"
)
handler = logging.StreamHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)

dynamodb = boto3.resource("dynamodb", region_name="ap-northeast-1")


def create_response(status_code: int, body: dict) -> dict:
"""
HTTPレスポンスを生成します。
Args:
status_code (int): HTTPステータスコード。
body (dict): レスポンスボディ。
Returns:
dict: フォーマット済みのHTTPレスポンス。
"""
return {
"statusCode": status_code,
"body": json.dumps(body),
"headers": {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
}


def validate_date(date: str) -> bool:
"""
日付文字列の形式を検証します。
Args:
date (str): 検証する日付文字列。
Returns:
bool: 日付が有効な場合はTrue。
"""
import re

return bool(re.match(r"^\d{4}-\d{2}-\d{2}$", date))


def get_flower_id(user_id: str, date: str) -> Optional[str]:
"""
DynamoDBからflower_idを取得します。
Args:
user_id (str): ユーザーID。
date (str): 日付文字列。
Returns:
Optional[str]: flower_idまたはNone。
"""
table_name = os.getenv("GENERATIVE_AI_TABLE_NAME")
if not table_name:
logger.error("GENERATIVE_AI_TABLE_NAME is not defined")
raise ValueError("GENERATIVE_AI_TABLE_NAME is not defined")

table = dynamodb.Table(table_name)

try:
response = table.get_item(Key={"user_id": user_id, "date": date})
return response.get("Item", {}).get("flower_id")
except ClientError as e:
logger.error(f"DynamoDB client error: {e.response['Error']['Message']}")
raise


def get_image(flower_id: str) -> Optional[str]:
"""
S3から画像を取得します。
Args:
flower_id (str): Flower ID。
Returns:
Optional[str]: Base64エンコードされた画像データまたはNone。
"""
s3 = boto3.client("s3")
bucket_name = os.getenv("FLOWER_BUCKET_NAME")
if not bucket_name:
logger.error("FLOWER_BUCKET_NAME is not defined")
raise ValueError("FLOWER_BUCKET_NAME is not defined")

s3_key = f"flowers/{flower_id}.png"

try:
response = s3.get_object(Bucket=bucket_name, Key=s3_key)
body = response["Body"].read()
return base64.b64encode(body).decode("utf-8")
except ClientError as e:
if e.response["Error"].get("Code") == "NoSuchKey":
logger.info(f"image not found: {s3_key}")
return ""


def get_title(user_id: str, date: str) -> Optional[str]:
"""
DynamoDBからタイトルを取得します。
Args:
user_id (str): ユーザーID。
date (str): 日付文字列。
Returns:
Optional[str]: タイトルまたはNone。
"""
generative_ai_table_name = os.getenv("GENERATIVE_AI_TABLE_NAME")
if not generative_ai_table_name:
logger.error("GENERATIVE_AI_TABLE_NAME is not defined")
raise ValueError("GENERATIVE_AI_TABLE_NAME is not defined")

generative_ai_table = dynamodb.Table(generative_ai_table_name)

try:
response = generative_ai_table.get_item(Key={"user_id": user_id, "date": date})
return response.get("Item", {}).get("title")
except ClientError as e:
logger.info(f"DynamoDB client error: {e.response['Error']['Message']}")
return ""


def get_body(user_id: str, date: str) -> Optional[str]:
"""
DynamoDBから本文を取得します。
Args:
user_id (str): ユーザーID。
date (str): 日付文字列。
Returns:
Optional[str]: 本文またはNone。
"""
diary_table_name = os.getenv("GENERATIVE_AI_TABLE_NAME")
if not diary_table_name:
logger.error("TABLE_NAME is not defined")
raise ValueError("TABLE_NAME is not defined")

diary_table = dynamodb.Table(diary_table_name)

try:
response = diary_table.get_item(Key={"user_id": user_id, "date": date})
return response.get("Item", {}).get("body")
except ClientError as e:
logger.error(f"DynamoDB client error: {e.response['Error']['Message']}")
raise


def get_current_week() -> (int, int):
"""
現在の年と週番号を取得します。
Returns:
tuple: 現在の年とISO週番号。
"""
current_date = datetime.now(timezone.utc)
current_year, current_week, _ = current_date.isocalendar()
return current_year, current_week


def check_bouquet_created(user_id: str, current_year: int, current_week: int) -> bool:
"""
現在の週にブーケが作成されたか確認します。
Args:
user_id (str): ユーザーID。
current_year (int): 現在の年。
current_week (int): 現在のISO週番号。
Returns:
bool: ブーケが作成されている場合はTrue、それ以外はFalse。
"""
bouquet_table_name = os.getenv("BOUQUET_TABLE_NAME")
bouquet_table = dynamodb.Table(bouquet_table_name)
try:
bouquet_table.get_item(
Key={"user_id": user_id, "year_week": f"{current_year}-{current_week}"}
)
return True
except ClientError:
return False


def count_flowers_in_week(user_id: str, current_year: int, current_week: int) -> int:
"""
現在の週にS3にある花の数をカウントします。
Args:
user_id (str): ユーザーID。
current_year (int): 現在の年。
current_week (int): 現在のISO週番号。
Returns:
int: 花の数。
"""
s3 = boto3.client("s3")
bucket_name = os.getenv("FLOWER_BUCKET_NAME")
prefix = f"{user_id}/{current_year}-{current_week}"

try:
response = s3.list_objects_v2(Bucket=bucket_name, Prefix=prefix)
return len(response.get("Contents", []))
except ClientError as e:
logger.error(f"S3 error: {e.response['Error']['Message']}")
raise


def lambda_handler(event: Dict[str, Any], context: Any) -> Dict[str, Any]:
"""
Lambda関数: 画像、タイトル、本文をDynamoDBから取得し、
ブーケ作成可能かどうかをチェックします。
Args:
event (Dict[str, Any]): Lambdaイベントデータ。
context (Any): Lambdaコンテキストオブジェクト。
Returns:
Dict[str, Any]: API Gateway互換のレスポンス。
"""
try:
query_params = event.get("queryStringParameters", {})
if not query_params or "date" not in query_params:
return create_response(
400, {"error": "Required parameter is missing: date"}
)

user_id = event["requestContext"]["authorizer"]["claims"]["sub"]
date = query_params["date"]
if not validate_date(date):
return create_response(400, {"error": "Invalid date format"})

flower_id = get_flower_id(user_id, date)
image = get_image(flower_id) if flower_id else None
title = get_title(user_id, date)
body = get_body(user_id, date)

current_year, current_week = get_current_week()
bouquet_created = check_bouquet_created(user_id, current_year, current_week)
logger.info(f"bouquet create : {bouquet_created}")
flower_count = count_flowers_in_week(user_id, current_year, current_week)
logger.info(f"flower count: {flower_count}")
can_create_bouquet = not bouquet_created and flower_count >= 5
logger.info(f"can create bouquet: {can_create_bouquet}")

response_data = {
"image": image,
"title": title or "",
"body": body or "",
"can_create_bouquet": can_create_bouquet,
}

return create_response(200, response_data)

except Exception as e:
logger.error(f"Unexpected error: {str(e)}")
return create_response(
500,
{
"error": "Internal server error",
"details": str(e) if os.getenv("DEBUG") else "Please contact support",
},
)
Loading

0 comments on commit 1a2b3f3

Please sign in to comment.