diff --git a/first-cdk-deployment/first_cdk_deployment/first_cdk_deployment_stack.py b/first-cdk-deployment/first_cdk_deployment/first_cdk_deployment_stack.py index 7a43f163c..773f33001 100644 --- a/first-cdk-deployment/first_cdk_deployment/first_cdk_deployment_stack.py +++ b/first-cdk-deployment/first_cdk_deployment/first_cdk_deployment_stack.py @@ -1,42 +1,33 @@ -# from aws_cdk import ( -# # Duration, -# Stack, -# # aws_sqs as sqs, -# ) -# from constructs import Construct - -# class FirstCdkDeploymentStack(Stack): - -# def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: -# super().__init__(scope, construct_id, **kwargs) - -# # The code that defines your stack goes here - -# # example resource -# # queue = sqs.Queue( -# # self, "FirstCdkDeploymentQueue", -# # visibility_timeout=Duration.seconds(300), -# # ) - - from aws_cdk import ( Stack, aws_s3 as s3, aws_cloudfront as cloudfront, aws_cloudfront_origins as origins, aws_s3_deployment as s3deploy, + aws_iam as iam, CfnOutput, RemovalPolicy ) from constructs import Construct -import os class FirstCdkDeploymentStack(Stack): def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: super().__init__(scope, construct_id, **kwargs) - # Create an S3 bucket to store the website files - website_bucket = s3.Bucket( + # Create resources + website_bucket = self._create_website_bucket() + oac = self._create_origin_access_control() + distribution = self._create_cloudfront_distribution(website_bucket, oac) + + # Add bucket policy after distribution is created + self._add_bucket_policy(website_bucket, distribution) + + # Output CloudFront URL + self._create_cloudfront_output(distribution) + + def _create_website_bucket(self) -> s3.Bucket: + """Create and configure S3 bucket for website hosting""" + return s3.Bucket( self, "ShopReactBucket", block_public_access=s3.BlockPublicAccess.BLOCK_ALL, @@ -44,38 +35,77 @@ def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None: auto_delete_objects=True ) - # Create CloudFront distribution - distribution = cloudfront.Distribution( + def _create_origin_access_control(self) -> cloudfront.CfnOriginAccessControl: + """Create Origin Access Control for CloudFront""" + return cloudfront.CfnOriginAccessControl( + self, + "OAC", + origin_access_control_config=cloudfront.CfnOriginAccessControl.OriginAccessControlConfigProperty( + name="S3OriginAccessControl", + origin_access_control_origin_type="s3", + signing_behavior="always", + signing_protocol="sigv4" + ) + ) + + def _create_cloudfront_distribution( + self, + bucket: s3.Bucket, + oac: cloudfront.CfnOriginAccessControl + ) -> cloudfront.Distribution: + """Create and configure CloudFront distribution""" + return cloudfront.Distribution( self, "ShopReactDistribution", default_behavior=cloudfront.BehaviorOptions( - origin=origins.S3Origin(website_bucket), - viewer_protocol_policy=cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, - cache_policy=cloudfront.CachePolicy.CACHING_OPTIMIZED, - ), - default_root_object="index.html", - error_responses=[ - cloudfront.ErrorResponse( - http_status=404, - response_http_status=200, - response_page_path="/index.html" + origin=origins.S3BucketOrigin( + bucket, + origin_access_control_id=oac.ref ) - ] + ), + default_root_object="index.html", # Added for SPA support + error_responses=self._get_error_responses() # Added for SPA support ) - # Deploy site contents to S3 - s3deploy.BucketDeployment( - self, - "DeployShopReact", - sources=[s3deploy.Source.asset(os.path.join(os.path.dirname(__file__), "..", "..", "dist"))], - destination_bucket=website_bucket, - distribution=distribution, - distribution_paths=["/*"] + def _add_bucket_policy( + self, + bucket: s3.Bucket, + distribution: cloudfront.Distribution + ) -> None: + """Add bucket policy to allow CloudFront access""" + bucket.add_to_resource_policy( + iam.PolicyStatement( + actions=["s3:GetObject"], + resources=[bucket.arn_for_objects("*")], + principals=[iam.ServicePrincipal("cloudfront.amazonaws.com")], + conditions={ + "StringEquals": { + "AWS:SourceArn": f"arn:aws:cloudfront::{Stack.of(self).account}:distribution/{distribution.distribution_id}" + } + } + ) ) - # Output the CloudFront URL + def _create_cloudfront_output(self, distribution: cloudfront.Distribution) -> None: + """Create CloudFront URL output""" CfnOutput( self, "DistributionDomainName", - value=distribution.distribution_domain_name + value=distribution.distribution_domain_name, + description="CloudFront Distribution Domain Name" ) + + def _get_error_responses(self) -> list[cloudfront.ErrorResponse]: + """Configure error responses for SPA support""" + return [ + cloudfront.ErrorResponse( + http_status=403, + response_http_status=200, + response_page_path="/index.html" + ), + cloudfront.ErrorResponse( + http_status=404, + response_http_status=200, + response_page_path="/index.html" + ) + ] diff --git a/src/components/pages/PageProducts/components/Products.tsx b/src/components/pages/PageProducts/components/Products.tsx index 68eec6d69..9f986554d 100755 --- a/src/components/pages/PageProducts/components/Products.tsx +++ b/src/components/pages/PageProducts/components/Products.tsx @@ -32,6 +32,7 @@ export default function Products() { {product.title} + Available: {count} {formatAsPrice(product.price)} diff --git a/src/queries/products.ts b/src/queries/products.ts index b24ce6d54..dd8f8651a 100644 --- a/src/queries/products.ts +++ b/src/queries/products.ts @@ -50,7 +50,7 @@ export function useRemoveProductCache() { export function useUpsertAvailableProduct() { return useMutation((values: AvailableProduct) => - axios.put(`${API_PATHS.bff}/product`, values, { + axios.put(`${API_PATHS.product}/product`, values, { headers: { Authorization: `Basic ${localStorage.getItem("authorization_token")}`, }, @@ -60,7 +60,7 @@ export function useUpsertAvailableProduct() { export function useDeleteAvailableProduct() { return useMutation((id: string) => - axios.delete(`${API_PATHS.bff}/product/${id}`, { + axios.delete(`${API_PATHS.product}/product/${id}`, { headers: { Authorization: `Basic ${localStorage.getItem("authorization_token")}`, },