import * as cdk from 'aws-cdk-lib'; import * as s3 from 'aws-cdk-lib/aws-s3'; import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment'; import * as acm from 'aws-cdk-lib/aws-certificatemanager'; import * as route53 from 'aws-cdk-lib/aws-route53'; import * as targets from 'aws-cdk-lib/aws-route53-targets'; import { Construct } from 'constructs'; export interface WebStackProps extends cdk.StackProps { certificateArn?: string; } export class WebStack extends cdk.Stack { public readonly distributionUrl: string; public readonly bucketName: string; constructor(scope: Construct, id: string, props?: WebStackProps) { super(scope, id, props); const bucketName = `syncrow-web-${this.account}-${this.region}`; const webBucket = new s3.Bucket(this, 'SyncrowWebBucket', { bucketName, websiteIndexDocument: 'index.html', websiteErrorDocument: 'index.html', publicReadAccess: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ACLS, removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, }); // Use existing wildcard certificate in us-east-1 (required for CloudFront) const webCertificate = props?.certificateArn ? acm.Certificate.fromCertificateArn(this, 'WildcardCertificate', props.certificateArn) : acm.Certificate.fromCertificateArn(this, 'WildcardCertificate', 'arn:aws:acm:us-east-1:482311766496:certificate/b3ea57be-9bf0-4c66-8b01-9672ef1e8530'); // Get the hosted zone const hostedZone = route53.HostedZone.fromLookup(this, 'SyncrowZone', { domainName: 'syncrow.me', }); const distribution = new cloudfront.Distribution(this, 'SyncrowWebDistribution', { defaultBehavior: { origin: new origins.S3Origin(webBucket), viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED, }, domainNames: ['app.syncrow.me'], certificate: webCertificate, defaultRootObject: 'index.html', errorResponses: [ { httpStatus: 404, responseHttpStatus: 200, responsePagePath: '/index.html', }, { httpStatus: 403, responseHttpStatus: 200, responsePagePath: '/index.html', }, ], }); // Create Route 53 record for app.syncrow.me new route53.ARecord(this, 'WebAliasRecord', { zone: hostedZone, recordName: 'app', target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution)), }); new s3deploy.BucketDeployment(this, 'SyncrowWebDeployment', { sources: [s3deploy.Source.asset('./build/web')], destinationBucket: webBucket, distribution, distributionPaths: ['/*'], }); this.distributionUrl = 'https://app.syncrow.me'; this.bucketName = bucketName; new cdk.CfnOutput(this, 'WebsiteUrl', { value: this.distributionUrl, description: 'Web Application URL', exportName: `${this.stackName}-WebsiteUrl`, }); new cdk.CfnOutput(this, 'CloudFrontUrl', { value: `https://${distribution.distributionDomainName}`, description: 'CloudFront Distribution URL', exportName: `${this.stackName}-CloudFrontUrl`, }); new cdk.CfnOutput(this, 'BucketName', { value: this.bucketName, description: 'S3 Bucket Name', exportName: `${this.stackName}-BucketName`, }); new cdk.CfnOutput(this, 'WildcardCertificateArn', { value: webCertificate.certificateArn, description: 'Wildcard SSL Certificate ARN (us-east-1)', exportName: `${this.stackName}-WildcardCertificateArn`, }); } }