diff --git a/src/backend/lambda/bouquet_create/bouquet_create.py b/src/backend/lambda/bouquet_create/bouquet_create.py index 8a3ff670..5c4c225e 100644 --- a/src/backend/lambda/bouquet_create/bouquet_create.py +++ b/src/backend/lambda/bouquet_create/bouquet_create.py @@ -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"] @@ -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 diff --git a/src/backend/lambda/diary_create/diary_create.py b/src/backend/lambda/diary_create/diary_create.py index 73a77dce..ae89c525 100644 --- a/src/backend/lambda/diary_create/diary_create.py +++ b/src/backend/lambda/diary_create/diary_create.py @@ -47,7 +47,7 @@ def get_img_from_s3(flower_id): Exception: S3 操作が失敗した場合に発生。 """ s3 = boto3.client("s3") - bucket_name = os.environ["FLOWER_IMAGE_BUCKET_NAME"] + bucket_name = os.environ["ORIGINAL_IMAGE_BUCKET_NAME"] s3_key = f"flowers/{flower_id}.png" try: diff --git a/src/backend/lambda/flower_get/flower_get.py b/src/backend/lambda/flower_get/flower_get.py index 9fd11428..0499770d 100644 --- a/src/backend/lambda/flower_get/flower_get.py +++ b/src/backend/lambda/flower_get/flower_get.py @@ -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: diff --git a/src/backend/lib/backend-stack.ts b/src/backend/lib/backend-stack.ts index 88f8e161..84e7d6a3 100644 --- a/src/backend/lib/backend-stack.ts +++ b/src/backend/lib/backend-stack.ts @@ -53,13 +53,14 @@ export class BackendStack extends cdk.Stack { }) // Diary機能コンストラクトのスタック化 const diary = new Diary(this, 'Diary', { + flowerBucket: flower.flowerBucket, userPool: auth.userPool, api: api.api, cognitoAuthorizer: api.cognitoAuthorizer, table: flower.table, generativeAiTable: flower.generativeAiTable, flowerSelectFunction: flower.flowerSelectFunction, - flowerImageBucket: flower.flowerImageBucket, + originalImageBucket: flower.originalImageBucket, }) const bouquet = new Bouquet(this, 'Bouquet', { @@ -68,7 +69,7 @@ export class BackendStack extends cdk.Stack { api: api.api, generativeAiTable: flower.generativeAiTable, cognitoAuthorizer: api.cognitoAuthorizer, - flowerImageBucket: flower.flowerImageBucket, + originalImageBucket: flower.originalImageBucket, }) const settings = new Settings(this, 'Settings', { diff --git a/src/backend/lib/constructs/bouquet.ts b/src/backend/lib/constructs/bouquet.ts index 7414c752..253c0d68 100644 --- a/src/backend/lib/constructs/bouquet.ts +++ b/src/backend/lib/constructs/bouquet.ts @@ -12,7 +12,7 @@ export interface BouquetProps { api: apigateway.RestApi generativeAiTable: dynamodb.Table cognitoAuthorizer: apigateway.CognitoUserPoolsAuthorizer - flowerImageBucket: s3.Bucket + originalImageBucket: s3.Bucket } export class Bouquet extends Construct { @@ -66,13 +66,13 @@ export class Bouquet extends Construct { environment: { GENERATIVE_AI_TABLE_NAME: props.generativeAiTable.tableName, BOUQUET_TABLE_NAME: bouquetTable.tableName, - FLOWER_BUCKET_NAME: props.flowerImageBucket.bucketName, + ORIGINAL_IMAGE_BUCKET_NAME: props.originalImageBucket.bucketName, BOUQUET_BUCKET_NAME: bouquetBucket.bucketName, }, }) props.generativeAiTable.grantReadData(BouquetCreate) bouquetTable.grantWriteData(BouquetCreate) - props.flowerImageBucket.grantRead(BouquetCreate) + props.originalImageBucket.grantRead(BouquetCreate) bouquetBucket.grantPut(BouquetCreate) // /bouquet APIの設定 diff --git a/src/backend/lib/constructs/diary.ts b/src/backend/lib/constructs/diary.ts index df56daeb..852a7f2f 100644 --- a/src/backend/lib/constructs/diary.ts +++ b/src/backend/lib/constructs/diary.ts @@ -14,7 +14,8 @@ export interface DiaryProps { table: dynamodb.Table generativeAiTable: dynamodb.Table flowerSelectFunction: lambda.Function - flowerImageBucket: s3.Bucket + originalImageBucket: s3.Bucket + flowerBucket: s3.Bucket } export class Diary extends Construct { @@ -36,14 +37,16 @@ export class Diary extends Construct { logRetention: 14, environment: { TABLE_NAME: props.table.tableName, - FLOWER_IMAGE_BUCKET_NAME: props.flowerImageBucket.bucketName, + ORIGINAL_IMAGE_BUCKET_NAME: props.originalImageBucket.bucketName, FLOWER_SELECT_FUNCTION_NAME: props.flowerSelectFunction.functionName, + FLOWER_BUKCET_NAME: props.flowerBucket.bucketName, }, timeout: cdk.Duration.seconds(30), }) props.table.grantWriteData(diaryCreateFunction) props.flowerSelectFunction.grantInvoke(diaryCreateFunction) - props.flowerImageBucket.grantRead(diaryCreateFunction) + props.originalImageBucket.grantRead(diaryCreateFunction) + props.flowerBucket.grantPut(diaryCreateFunction) // 日記編集用Lambda関数の定義 const diaryEditFunction = new lambda.Function(this, 'diaryEditLambda', { diff --git a/src/backend/lib/constructs/flower.ts b/src/backend/lib/constructs/flower.ts index a264f7b3..d6f0a759 100644 --- a/src/backend/lib/constructs/flower.ts +++ b/src/backend/lib/constructs/flower.ts @@ -15,10 +15,11 @@ export interface FlowerProps { } export class Flower extends Construct { - public readonly flowerImageBucket: s3.Bucket + public readonly originalImageBucket: s3.Bucket public readonly table: dynamodb.Table public readonly generativeAiTable: dynamodb.Table public readonly flowerSelectFunction: lambda.Function + public readonly flowerBucket: s3.Bucket constructor(scope: Construct, id: string, props: FlowerProps) { super(scope, id) @@ -51,8 +52,14 @@ export class Flower extends Construct { pointInTimeRecovery: true, }) - // 花の画像保存用S3バケットの作成 - const flowerImageBucket = new s3.Bucket(this, 'flowerImageBucket', { + // 元画像保存用S3バケットの作成 + const originalImageBucket = new s3.Bucket(this, 'originalImageBucket', { + enforceSSL: true, + serverAccessLogsPrefix: 'log/', + }) + + // ユーザーごとの花画像保存用S3バケットの作成 + const flowerBucket = new s3.Bucket(this, 'flowerBucket', { enforceSSL: true, serverAccessLogsPrefix: 'log/', }) @@ -70,13 +77,15 @@ export class Flower extends Construct { environment: { DIARY_TABLE_NAME: table.tableName, GENERATIVE_AI_TABLE_NAME: generativeAiTable.tableName, - FLOWER_BUCKET_NAME: flowerImageBucket.bucketName, + ORIGINAL_IMAGE_BUCKET_NAME: originalImageBucket.bucketName, + FLOWER_BUCKET_NAME: flowerBucket.bucketName, }, timeout: cdk.Duration.seconds(60), }) generativeAiTable.grantWriteData(flowerSelectFunction) + flowerBucket.grantPut(flowerSelectFunction) table.grantStreamRead(flowerSelectFunction) - flowerImageBucket.grantPut(flowerSelectFunction) + originalImageBucket.grantPut(flowerSelectFunction) const difyApiKey = ssm.StringParameter.fromStringParameterAttributes(this, 'DifyApiKey', { parameterName: 'DIFY_API_KEY', }) @@ -93,12 +102,13 @@ export class Flower extends Construct { handler: 'flower_get.lambda_handler', code: lambda.Code.fromAsset('lambda/flower_get'), environment: { - BUCKET_NAME: flowerImageBucket.bucketName, + ORIGINAL_BUCKET_NAME: originalImageBucket.bucketName, GENERATIVE_AI_TABLE_NAME: generativeAiTable.tableName, + FLOWER_BUCKET_NAME: flowerBucket.bucketName, }, timeout: cdk.Duration.seconds(10), }) - flowerImageBucket.grantRead(flowerGetFunction) + originalImageBucket.grantRead(flowerGetFunction) generativeAiTable.grantReadData(flowerGetFunction) // flower API の設定 const flowerApi = props.api.root.addResource('flower') @@ -138,9 +148,10 @@ export class Flower extends Construct { authorizer: props.cognitoAuthorizer, }) - this.flowerImageBucket = flowerImageBucket + this.originalImageBucket = originalImageBucket this.table = table this.generativeAiTable = generativeAiTable this.flowerSelectFunction = flowerSelectFunction + this.flowerBucket = flowerBucket } } diff --git a/src/backend/test/__snapshots__/snapshot.test.ts.snap b/src/backend/test/__snapshots__/snapshot.test.ts.snap index d9ef9e57..fdda086c 100644 --- a/src/backend/test/__snapshots__/snapshot.test.ts.snap +++ b/src/backend/test/__snapshots__/snapshot.test.ts.snap @@ -1940,12 +1940,12 @@ exports[`Snapshot test 1`] = ` "BOUQUET_TABLE_NAME": { "Ref": "BouquetBouquetTableE43AF35D", }, - "FLOWER_BUCKET_NAME": { - "Ref": "FlowerflowerImageBucket46E60C76", - }, "GENERATIVE_AI_TABLE_NAME": { "Ref": "FlowergenerativeAiTable021268D8", }, + "ORIGINAL_IMAGE_BUCKET_NAME": { + "Ref": "FloweroriginalImageBucket5E40682A", + }, }, }, "Handler": "bouquet_create.lambda_handler", @@ -2050,7 +2050,7 @@ exports[`Snapshot test 1`] = ` "Resource": [ { "Fn::GetAtt": [ - "FlowerflowerImageBucket46E60C76", + "FloweroriginalImageBucket5E40682A", "Arn", ], }, @@ -2060,7 +2060,7 @@ exports[`Snapshot test 1`] = ` [ { "Fn::GetAtt": [ - "FlowerflowerImageBucket46E60C76", + "FloweroriginalImageBucket5E40682A", "Arn", ], }, @@ -2611,12 +2611,15 @@ exports[`Snapshot test 1`] = ` }, "Environment": { "Variables": { - "FLOWER_IMAGE_BUCKET_NAME": { - "Ref": "FlowerflowerImageBucket46E60C76", + "FLOWER_BUKCET_NAME": { + "Ref": "FlowerflowerBucket9981D467", }, "FLOWER_SELECT_FUNCTION_NAME": { "Ref": "FlowerflowerSelectFunctionD7EEBADA", }, + "ORIGINAL_IMAGE_BUCKET_NAME": { + "Ref": "FloweroriginalImageBucket5E40682A", + }, "TABLE_NAME": { "Ref": "FlowerdiaryContentsTableCA7C6940", }, @@ -2749,7 +2752,7 @@ exports[`Snapshot test 1`] = ` "Resource": [ { "Fn::GetAtt": [ - "FlowerflowerImageBucket46E60C76", + "FloweroriginalImageBucket5E40682A", "Arn", ], }, @@ -2759,7 +2762,7 @@ exports[`Snapshot test 1`] = ` [ { "Fn::GetAtt": [ - "FlowerflowerImageBucket46E60C76", + "FloweroriginalImageBucket5E40682A", "Arn", ], }, @@ -2769,6 +2772,31 @@ exports[`Snapshot test 1`] = ` }, ], }, + { + "Action": [ + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging", + "s3:Abort*", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "FlowerflowerBucket9981D467", + "Arn", + ], + }, + "/*", + ], + ], + }, + }, ], "Version": "2012-10-17", }, @@ -3375,6 +3403,71 @@ exports[`Snapshot test 1`] = ` "Type": "AWS::DynamoDB::Table", "UpdateReplacePolicy": "Delete", }, + "FlowerflowerBucket9981D467": { + "DeletionPolicy": "Retain", + "Properties": { + "AccessControl": "LogDeliveryWrite", + "LoggingConfiguration": { + "LogFilePrefix": "log/", + }, + "OwnershipControls": { + "Rules": [ + { + "ObjectOwnership": "ObjectWriter", + }, + ], + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "FlowerflowerBucketPolicy784D0728": { + "Properties": { + "Bucket": { + "Ref": "FlowerflowerBucket9981D467", + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false", + }, + }, + "Effect": "Deny", + "Principal": { + "AWS": "*", + }, + "Resource": [ + { + "Fn::GetAtt": [ + "FlowerflowerBucket9981D467", + "Arn", + ], + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "FlowerflowerBucket9981D467", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::S3::BucketPolicy", + }, "FlowerflowerGetFunctionA4C511EA": { "DependsOn": [ "FlowerflowerGetFunctionServiceRoleDefaultPolicy62A02DD7", @@ -3389,12 +3482,15 @@ exports[`Snapshot test 1`] = ` }, "Environment": { "Variables": { - "BUCKET_NAME": { - "Ref": "FlowerflowerImageBucket46E60C76", + "FLOWER_BUCKET_NAME": { + "Ref": "FlowerflowerBucket9981D467", }, "GENERATIVE_AI_TABLE_NAME": { "Ref": "FlowergenerativeAiTable021268D8", }, + "ORIGINAL_BUCKET_NAME": { + "Ref": "FloweroriginalImageBucket5E40682A", + }, }, }, "Handler": "flower_get.lambda_handler", @@ -3454,7 +3550,7 @@ exports[`Snapshot test 1`] = ` "Resource": [ { "Fn::GetAtt": [ - "FlowerflowerImageBucket46E60C76", + "FloweroriginalImageBucket5E40682A", "Arn", ], }, @@ -3464,7 +3560,7 @@ exports[`Snapshot test 1`] = ` [ { "Fn::GetAtt": [ - "FlowerflowerImageBucket46E60C76", + "FloweroriginalImageBucket5E40682A", "Arn", ], }, @@ -3510,71 +3606,6 @@ exports[`Snapshot test 1`] = ` }, "Type": "AWS::IAM::Policy", }, - "FlowerflowerImageBucket46E60C76": { - "DeletionPolicy": "Retain", - "Properties": { - "AccessControl": "LogDeliveryWrite", - "LoggingConfiguration": { - "LogFilePrefix": "log/", - }, - "OwnershipControls": { - "Rules": [ - { - "ObjectOwnership": "ObjectWriter", - }, - ], - }, - }, - "Type": "AWS::S3::Bucket", - "UpdateReplacePolicy": "Retain", - }, - "FlowerflowerImageBucketPolicyD55EE863": { - "Properties": { - "Bucket": { - "Ref": "FlowerflowerImageBucket46E60C76", - }, - "PolicyDocument": { - "Statement": [ - { - "Action": "s3:*", - "Condition": { - "Bool": { - "aws:SecureTransport": "false", - }, - }, - "Effect": "Deny", - "Principal": { - "AWS": "*", - }, - "Resource": [ - { - "Fn::GetAtt": [ - "FlowerflowerImageBucket46E60C76", - "Arn", - ], - }, - { - "Fn::Join": [ - "", - [ - { - "Fn::GetAtt": [ - "FlowerflowerImageBucket46E60C76", - "Arn", - ], - }, - "/*", - ], - ], - }, - ], - }, - ], - "Version": "2012-10-17", - }, - }, - "Type": "AWS::S3::BucketPolicy", - }, "FlowerflowerSelectFunctionD7EEBADA": { "DependsOn": [ "FlowerflowerSelectFunctionServiceRoleDefaultPolicy7F8366A3", @@ -3593,11 +3624,14 @@ exports[`Snapshot test 1`] = ` "Ref": "FlowerdiaryContentsTableCA7C6940", }, "FLOWER_BUCKET_NAME": { - "Ref": "FlowerflowerImageBucket46E60C76", + "Ref": "FlowerflowerBucket9981D467", }, "GENERATIVE_AI_TABLE_NAME": { "Ref": "FlowergenerativeAiTable021268D8", }, + "ORIGINAL_IMAGE_BUCKET_NAME": { + "Ref": "FloweroriginalImageBucket5E40682A", + }, }, }, "Handler": "flower_select.lambda_handler", @@ -3668,6 +3702,31 @@ exports[`Snapshot test 1`] = ` }, ], }, + { + "Action": [ + "s3:PutObject", + "s3:PutObjectLegalHold", + "s3:PutObjectRetention", + "s3:PutObjectTagging", + "s3:PutObjectVersionTagging", + "s3:Abort*", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "FlowerflowerBucket9981D467", + "Arn", + ], + }, + "/*", + ], + ], + }, + }, { "Action": "dynamodb:ListStreams", "Effect": "Allow", @@ -3703,7 +3762,7 @@ exports[`Snapshot test 1`] = ` [ { "Fn::GetAtt": [ - "FlowerflowerImageBucket46E60C76", + "FloweroriginalImageBucket5E40682A", "Arn", ], }, @@ -3782,6 +3841,71 @@ exports[`Snapshot test 1`] = ` "Type": "AWS::DynamoDB::Table", "UpdateReplacePolicy": "Delete", }, + "FloweroriginalImageBucket5E40682A": { + "DeletionPolicy": "Retain", + "Properties": { + "AccessControl": "LogDeliveryWrite", + "LoggingConfiguration": { + "LogFilePrefix": "log/", + }, + "OwnershipControls": { + "Rules": [ + { + "ObjectOwnership": "ObjectWriter", + }, + ], + }, + }, + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Retain", + }, + "FloweroriginalImageBucketPolicyFA65F301": { + "Properties": { + "Bucket": { + "Ref": "FloweroriginalImageBucket5E40682A", + }, + "PolicyDocument": { + "Statement": [ + { + "Action": "s3:*", + "Condition": { + "Bool": { + "aws:SecureTransport": "false", + }, + }, + "Effect": "Deny", + "Principal": { + "AWS": "*", + }, + "Resource": [ + { + "Fn::GetAtt": [ + "FloweroriginalImageBucket5E40682A", + "Arn", + ], + }, + { + "Fn::Join": [ + "", + [ + { + "Fn::GetAtt": [ + "FloweroriginalImageBucket5E40682A", + "Arn", + ], + }, + "/*", + ], + ], + }, + ], + }, + ], + "Version": "2012-10-17", + }, + }, + "Type": "AWS::S3::BucketPolicy", + }, "IdentityIdentityPool01DA4AB2": { "DependsOn": [ "AuthDiaryUserPoolClientC42D6E78", diff --git a/src/backend/test/pytest/test_diary_create.py b/src/backend/test/pytest/test_diary_create.py index f72dbe00..b0ec9da5 100644 --- a/src/backend/test/pytest/test_diary_create.py +++ b/src/backend/test/pytest/test_diary_create.py @@ -38,14 +38,14 @@ def test_get_img_from_s3(mock_boto_client): "Body": MagicMock(read=lambda: b"fake_image_data") } - os.environ["FLOWER_IMAGE_BUCKET_NAME"] = "TEST_BUCKET" + os.environ["ORIGINAL_IMAGE_BUCKET_NAME"] = "TEST_BUCKET" # テスト実行 flower_id = "test_flower_id" image_data = get_img_from_s3(flower_id) # アサーション s3_client_mock.get_object.assert_called_once_with( - Bucket=os.environ["FLOWER_IMAGE_BUCKET_NAME"], Key=f"flowers/{flower_id}.png" + Bucket=os.environ["ORIGINAL_IMAGE_BUCKET_NAME"], Key=f"flowers/{flower_id}.png" ) assert image_data == base64.b64encode(b"fake_image_data").decode("utf-8") diff --git a/src/backend/test/pytest/test_flower_get.py b/src/backend/test/pytest/test_flower_get.py index fa9c0ea7..e57726e9 100644 --- a/src/backend/test/pytest/test_flower_get.py +++ b/src/backend/test/pytest/test_flower_get.py @@ -3,7 +3,7 @@ from unittest.mock import MagicMock, patch os.environ["AWS_DEFAULT_REGION"] = "us-east-1" -os.environ["BUCKET_NAME"] = "bucket" +os.environ["FLOWER_BUCKET_NAME"] = "bucket" import pytest from flower_get.flower_get import (