diff --git a/AWS Servisi.docx b/AWS Servisi.docx deleted file mode 100644 index 3195507cac659eb2d6dabb7885b4882e8cd03da8..0000000000000000000000000000000000000000 Binary files a/AWS Servisi.docx and /dev/null differ diff --git a/Arhitektura_sistema.docx b/Arhitektura_sistema.docx deleted file mode 100644 index 6886ba48e5a949eeee1dff056f1a397462e30b59..0000000000000000000000000000000000000000 Binary files a/Arhitektura_sistema.docx and /dev/null differ diff --git a/Hostovanje Front-a.txt b/Hostovanje Front-a.txt deleted file mode 100644 index 9580b13c41d6ff444101ba74ed07b01c7427ff23..0000000000000000000000000000000000000000 --- a/Hostovanje Front-a.txt +++ /dev/null @@ -1,37 +0,0 @@ -Hostovanje Fronta na S3 - -Napraviti bucket koji je public. - -Uraditi npm run build - -Ubaciti sve sto se nalazi u build folderu u root-ni folder bucket-a - -dodati Bucket Policy - -{ - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "PublicReadGetObject", - "Effect": "Allow", - "Principal": "*", - "Action": "s3:GetObject", - "Resource": "arn:aws:s3:::contact-app.client/*" - } - ] -} - -Otici u properties bucket-a i enable-ovati static website hosting. - --------- - -Otici na Cloud Front, kreirati Distribuciju - -Origin domain staviti link od hostovanog s3 bucketa - -Viewer Protocol Policy staviti na Redirect HTTP to HTTPS - -Enable-ovati WAF - --------- -Pipeline diff --git a/backendAWS/bin/backend_aws.ts b/backendAWS/bin/backend_aws.ts index 2105785f3ac0b38d803e06fe7f2a50dd9c907223..62db5f825f0c3873c9f81d26fe8badc567cbd658 100644 --- a/backendAWS/bin/backend_aws.ts +++ b/backendAWS/bin/backend_aws.ts @@ -1,24 +1,65 @@ -#!/usr/bin/env node -import 'source-map-support/register'; import * as cdk from 'aws-cdk-lib'; -import { BackendAwsStack } from '../lib/backend_aws-stack'; +import { AuthenticationStack } from '../lib/authentication_stack'; +import { StorageStack } from '../lib/storage_stack'; +import { LambdaStack } from '../lib/lambda_stack'; +import { ApiGatewayStack } from '../lib/apigateway_stack'; +import { FrontendStack } from '../lib/frontend_stack'; const app = new cdk.App(); -new BackendAwsStack(app, 'BackendAwsStack', { - /* If you don't specify 'env', this stack will be environment-agnostic. - * Account/Region-dependent features and context lookups will not work, - * but a single synthesized template can be deployed anywhere. */ - /* Uncomment the next line to specialize this stack for the AWS Account - * and Region that are implied by the current CLI configuration. */ +//Adding Tags for better overview of costs +cdk.Tags.of(app).add('Environment', 'Production'); +cdk.Tags.of(app).add('Project', 'ContactsApp'); + +const authStack = new AuthenticationStack( + app, + 'ContactApp-AuthenticationStack', + { + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + } +); +const storageStack = new StorageStack(app, 'ContactApp-StorageStack', { + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, +}); +const lambdaStack = new LambdaStack(app, 'ContactApp-LambdaStack', { + contactsTable: storageStack.contactsTable, + contactsBucket: storageStack.contactsBucket, env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION, }, +}); - /* Uncomment the next line if you know exactly what Account and Region you - * want to deploy the stack to. */ - //env: { account: '685287591242', region: 'eu-north-1' }, +new ApiGatewayStack(app, 'ContactApp-ApiGatewayStack', { + userPool: authStack.userPool, + createContactFunction: lambdaStack.createContactFunction, + updateContactFunction: lambdaStack.updateContactFunction, + deleteContactFunction: lambdaStack.deleteContactFunction, + getContactFunction: lambdaStack.getContactFunction, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, +}); + +/* new CloudFrontStack(app, 'ContactApp-CloudFrontStack', { + contactsBucket: storageStack.contactsBucket, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, +}); + */ - /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ +new FrontendStack(app, 'ContactApp-FrontendStack', { + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, }); diff --git a/backendAWS/lib/apigateway_stack.ts b/backendAWS/lib/apigateway_stack.ts new file mode 100644 index 0000000000000000000000000000000000000000..3bb0ce74c20e6ace4d0432d109aadce6b4ee5090 --- /dev/null +++ b/backendAWS/lib/apigateway_stack.ts @@ -0,0 +1,100 @@ +import * as cdk from 'aws-cdk-lib'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as cognito from 'aws-cdk-lib/aws-cognito'; +import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs'; +import { Cors, ResourceOptions } from 'aws-cdk-lib/aws-apigateway'; + +interface ApiGatewayStackProps extends cdk.StackProps { + userPool: cognito.UserPool; + createContactFunction: lambdaNodejs.NodejsFunction; + getContactFunction: lambdaNodejs.NodejsFunction; + deleteContactFunction: lambdaNodejs.NodejsFunction; + updateContactFunction: lambdaNodejs.NodejsFunction; +} + +export class ApiGatewayStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props: ApiGatewayStackProps) { + super(scope, id, props); + + // Api Gateway + const api = new apigateway.RestApi(this, 'ContactsAppApiGateway', { + restApiName: 'ContactsAppApiGateway', + description: 'API for managing contacts.', + }); + + cdk.Tags.of(api).add('Service', 'ApiGateway'); + + // Cognito Authorizer to API + const authorizer = new apigateway.CognitoUserPoolsAuthorizer( + this, + 'ContactsAppAuthorizer', + { + cognitoUserPools: [props.userPool], + } + ); + + // Adding CORS + const optionsWithCors: ResourceOptions = { + defaultCorsPreflightOptions: { + allowOrigins: Cors.ALL_ORIGINS, + allowMethods: Cors.ALL_METHODS, + }, + }; + + // API Routes + const contactsResource = api.root.addResource('contacts', optionsWithCors); + + //Create Contact + contactsResource.addMethod( + 'POST', + new apigateway.LambdaIntegration(props.createContactFunction), + { + authorizer, + authorizationType: apigateway.AuthorizationType.COGNITO, + } + ); + + //get all Contacts + contactsResource.addMethod( + 'GET', + new apigateway.LambdaIntegration(props.getContactFunction), + { + authorizer, + authorizationType: apigateway.AuthorizationType.COGNITO, + } + ); + + // Get one Contact + const singleContactResource = contactsResource.addResource( + '{contactId}', + optionsWithCors + ); + singleContactResource.addMethod( + 'GET', + new apigateway.LambdaIntegration(props.getContactFunction), + { + authorizer, + authorizationType: apigateway.AuthorizationType.COGNITO, + } + ); + // Update one Contact + singleContactResource.addMethod( + 'PUT', + new apigateway.LambdaIntegration(props.updateContactFunction), + { + authorizer, + authorizationType: apigateway.AuthorizationType.COGNITO, + } + ); + + // Delete Contact + singleContactResource.addMethod( + 'DELETE', + new apigateway.LambdaIntegration(props.deleteContactFunction), + { + authorizer, + authorizationType: apigateway.AuthorizationType.COGNITO, + } + ); + } +} diff --git a/backendAWS/lib/authentication_stack.ts b/backendAWS/lib/authentication_stack.ts new file mode 100644 index 0000000000000000000000000000000000000000..82d2fb7ba7e543b6bcae52bb2b272daef11a5446 --- /dev/null +++ b/backendAWS/lib/authentication_stack.ts @@ -0,0 +1,40 @@ +import * as cdk from 'aws-cdk-lib'; +import * as cognito from 'aws-cdk-lib/aws-cognito'; + +//CognitoService +export class AuthenticationStack extends cdk.Stack { + public readonly userPool: cognito.UserPool; + public readonly userPoolClient: cognito.UserPoolClient; + + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + this.userPool = new cognito.UserPool(this, 'ContactsAppUserPool', { + selfSignUpEnabled: true, + signInAliases: { email: true }, + autoVerify: { email: true }, + userPoolName: 'ContactsAppUserPool', + }); + //This make visible UserPoolId in Cloud Formation Output + new cdk.CfnOutput(this, 'ContactsAppUserPoolId', { + value: this.userPool.userPoolId, + }); + + this.userPoolClient = new cognito.UserPoolClient( + this, + 'ContactsAppUserPoolClient', + { + userPoolClientName: 'ContactAppUserPoolClient', + userPool: this.userPool, + authFlows: { userPassword: true }, + } + ); + + //This make visible UserPoolClientId in Cloud Formation Output + new cdk.CfnOutput(this, 'ContactsAppUserPoolClientId', { + value: this.userPoolClient.userPoolClientId, + }); + + cdk.Tags.of(this.userPool).add('Service', 'Auth'); + } +} diff --git a/backendAWS/lib/backend_aws-stack.ts b/backendAWS/lib/backend_aws-stack.ts deleted file mode 100644 index 791c5485866eab1da705d191270946da3b9b80a0..0000000000000000000000000000000000000000 --- a/backendAWS/lib/backend_aws-stack.ts +++ /dev/null @@ -1,331 +0,0 @@ -import * as cdk from 'aws-cdk-lib'; -import * as lambda from 'aws-cdk-lib/aws-lambda'; -import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs'; -import * as apigateway from 'aws-cdk-lib/aws-apigateway'; -import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; -import * as s3 from 'aws-cdk-lib/aws-s3'; -import * as path from 'path'; -import * as cognito from 'aws-cdk-lib/aws-cognito'; -import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam'; -import { Cors, ResourceOptions } from 'aws-cdk-lib/aws-apigateway'; - -export class BackendAwsStack extends cdk.Stack { - constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { - super(scope, id, props); - - //Adding Tags for better overview of costs - cdk.Tags.of(this).add('Environment', 'Production'); - cdk.Tags.of(this).add('Project', 'ContactsApp'); - - // Cognito Service - const userPool = new cognito.UserPool(this, 'ContactsAppUserPool', { - selfSignUpEnabled: true, - signInAliases: { email: true }, - autoVerify: { email: true }, - userPoolName: 'ContactsAppUserPool', - }); - - const userPoolClient = new cognito.UserPoolClient( - this, - 'ContactsAppUserPoolClient', - { - userPoolClientName: 'ContactAppUserPoolClient', - userPool, - authFlows: { - userPassword: true, - }, - } - ); - - cdk.Tags.of(userPool).add('Service', 'Auth'); - - // DynamoDB Table for Contacts - const contactsTable = new dynamodb.Table(this, 'Contacts-app_Table', { - partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, - tableName: 'Contacts', - removalPolicy: cdk.RemovalPolicy.DESTROY, // Remove table when stack is destroyed (for dev purposes) - }); - - cdk.Tags.of(contactsTable).add('Service', 'Database'); - - // S3 Bucket for Contact Images - const contactsBucket = new s3.Bucket(this, 'ContactsAppImagesBucket', { - bucketName: 'contacts-app.images', - removalPolicy: cdk.RemovalPolicy.DESTROY, // Only for development, not recommended for production - autoDeleteObjects: true, // Automatically delete objects when bucket is removed (for dev purposes) - blockPublicAccess: s3.BlockPublicAccess.BLOCK_ACLS, // Option to block public ACLs or Block entirely - }); - - cdk.Tags.of(contactsBucket).add('Service', 'Storage'); - - contactsBucket.addToResourcePolicy( - new PolicyStatement({ - actions: ['s3:GetObject'], - resources: [`${contactsBucket.bucketArn}/*`], // Allow access to all objects in the bucket - effect: cdk.aws_iam.Effect.ALLOW, - principals: [new cdk.aws_iam.ArnPrincipal('*')], // Allow public access - }) - ); - - // Define Lambda Functions - - //Create Contact - const createContactFunction = this.createLambdaFunction( - 'CreateContactFunction', - '../functions/create-contact/index.ts', - contactsTable, - contactsBucket - ); - - cdk.Tags.of(createContactFunction).add('Service', 'Lambda'); - - createContactFunction.addToRolePolicy( - new PolicyStatement({ - effect: Effect.ALLOW, - resources: [contactsTable.tableArn], - actions: [ - 'dynamodb:PutItem', - 'dynamodb:GetItem', - 'dynamodb:UpdateItem', - ], - }) - ); - - //Get Contacts - const getContactFunction = this.getLambdaFunction( - 'GetContactFunction', - '../functions/get-contact/index.ts', - contactsTable - ); - - cdk.Tags.of(getContactFunction).add('Service', 'Lambda'); - - getContactFunction.addToRolePolicy( - new PolicyStatement({ - effect: Effect.ALLOW, - resources: [contactsTable.tableArn], - actions: ['dynamodb:Scan', 'dynamodb:GetItem'], - }) - ); - - // Update Contact - const updateContactFunction = this.updateLambdaFunction( - 'UpdateContactFunction', - '../functions/update-contact/index.ts', - contactsTable, - contactsBucket - ); - - cdk.Tags.of(updateContactFunction).add('Service', 'Lambda'); - - updateContactFunction.addToRolePolicy( - new PolicyStatement({ - effect: Effect.ALLOW, - resources: [contactsTable.tableArn], - actions: [ - 'dynamodb:PutItem', - 'dynamodb:GetItem', - 'dynamodb:UpdateItem', - ], - }) - ); - - //Delete contact - const deleteContactFunction = this.deleteLambdaFunction( - 'DeleteContactFunction', - '../functions/delete-contact/index.ts', - contactsTable, - contactsBucket - ); - - cdk.Tags.of(deleteContactFunction).add('Service', 'Lambda'); - - deleteContactFunction.addToRolePolicy( - new PolicyStatement({ - effect: Effect.ALLOW, - resources: [contactsTable.tableArn], - actions: ['dynamodb:DeleteItem', 'dynamodb:GetItem'], - }) - ); - - deleteContactFunction.addToRolePolicy( - new PolicyStatement({ - effect: Effect.ALLOW, - resources: [`${contactsBucket.bucketArn}/*`], - actions: ['s3:DeleteObject'], - }) - ); - - // API Gateway - const api = new apigateway.RestApi(this, 'ContactsAppApiGateway', { - restApiName: 'ContactsAppApiGateway', - description: 'API for managing contacts.', - }); - - cdk.Tags.of(api).add('Service', 'ApiGateway'); - - // Cognito Authorizer to API - const authorizer = new apigateway.CognitoUserPoolsAuthorizer( - this, - 'ContactsAppAuthorizer', - { - cognitoUserPools: [userPool], - } - ); - - // Adding CORS - const optionsWithCors: ResourceOptions = { - defaultCorsPreflightOptions: { - allowOrigins: Cors.ALL_ORIGINS, - allowMethods: Cors.ALL_METHODS, - }, - }; - - // API Routes - const contactsResource = api.root.addResource('contacts', optionsWithCors); - - //Create Contact - contactsResource.addMethod( - 'POST', - new apigateway.LambdaIntegration(createContactFunction), - { - authorizer, - authorizationType: apigateway.AuthorizationType.COGNITO, - } - ); - - //get all Contacts - contactsResource.addMethod( - 'GET', - new apigateway.LambdaIntegration(getContactFunction), - { - authorizer, - authorizationType: apigateway.AuthorizationType.COGNITO, - } - ); - - // Get one Contact - const singleContactResource = contactsResource.addResource( - '{contactId}', - optionsWithCors - ); - singleContactResource.addMethod( - 'GET', - new apigateway.LambdaIntegration(getContactFunction), - { - authorizer, - authorizationType: apigateway.AuthorizationType.COGNITO, - } - ); - // Update one Contact - singleContactResource.addMethod( - 'PUT', - new apigateway.LambdaIntegration(updateContactFunction), - { - authorizer, - authorizationType: apigateway.AuthorizationType.COGNITO, - } - ); - - // Delete Contact - singleContactResource.addMethod( - 'DELETE', - new apigateway.LambdaIntegration(deleteContactFunction), - { - authorizer, - authorizationType: apigateway.AuthorizationType.COGNITO, - } - ); - } - - private createLambdaFunction( - id: string, - entryPath: string, - table: dynamodb.Table, - bucket: s3.Bucket - ): lambdaNodejs.NodejsFunction { - const createContactFunction = new lambdaNodejs.NodejsFunction(this, id, { - functionName: 'CreateContactFunction', - runtime: lambda.Runtime.NODEJS_20_X, - timeout: cdk.Duration.seconds(10), - entry: path.join(__dirname, entryPath), - handler: 'handler', - environment: { - TABLE_NAME: table.tableName, - BUCKET_NAME: bucket.bucketName, - }, - }); - - table.grantWriteData(createContactFunction); - bucket.grantPut(createContactFunction); - - return createContactFunction; - } - - private updateLambdaFunction( - id: string, - entryPath: string, - table: dynamodb.Table, - bucket: s3.Bucket - ): lambdaNodejs.NodejsFunction { - const updateContactFunction = new lambdaNodejs.NodejsFunction(this, id, { - functionName: 'UpdateContactFunction', - runtime: lambda.Runtime.NODEJS_20_X, - timeout: cdk.Duration.seconds(10), - entry: path.join(__dirname, entryPath), - handler: 'handler', - environment: { - TABLE_NAME: table.tableName, - BUCKET_NAME: bucket.bucketName, - }, - }); - - table.grantWriteData(updateContactFunction); - bucket.grantPut(updateContactFunction); - - return updateContactFunction; - } - - private getLambdaFunction( - id: string, - entryPath: string, - table: dynamodb.Table - ): lambdaNodejs.NodejsFunction { - const getContactFunction = new lambdaNodejs.NodejsFunction(this, id, { - functionName: 'GetContactFunction', - runtime: lambda.Runtime.NODEJS_20_X, - entry: path.join(__dirname, entryPath), - handler: 'handler', - environment: { - TABLE_NAME: table.tableName, - }, - }); - table.grantReadData(getContactFunction); - - return getContactFunction; - } - - private deleteLambdaFunction( - id: string, - entryPath: string, - table: dynamodb.Table, - bucket: s3.Bucket - ): lambdaNodejs.NodejsFunction { - const deleteContactFunction = new lambdaNodejs.NodejsFunction(this, id, { - functionName: 'DeleteContactFunction', - runtime: lambda.Runtime.NODEJS_20_X, - timeout: cdk.Duration.seconds(10), - entry: path.join(__dirname, entryPath), - handler: 'handler', - environment: { - TABLE_NAME: table.tableName, - BUCKET_NAME: bucket.bucketName, - }, - }); - - table.grantReadWriteData(deleteContactFunction); - bucket.grantDelete(deleteContactFunction); - - return deleteContactFunction; - } -} diff --git a/backendAWS/lib/cloudfront_stack.ts b/backendAWS/lib/cloudfront_stack.ts new file mode 100644 index 0000000000000000000000000000000000000000..3b64122e00d909fd894adc6e05ccc72d3b1a0e50 --- /dev/null +++ b/backendAWS/lib/cloudfront_stack.ts @@ -0,0 +1,80 @@ +import * as cdk from 'aws-cdk-lib'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import { PolicyStatement } from 'aws-cdk-lib/aws-iam'; + +interface CloudFrontStackProps extends cdk.StackProps { + contactsBucket: s3.Bucket; +} + +export class CloudFrontStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props: CloudFrontStackProps) { + super(scope, id, props); + + // Create an Origin Access Control (OAC) + const originAccessControl = new cloudfront.CfnOriginAccessControl( + this, + 'ContactsAppOAC', + { + originAccessControlConfig: { + name: 'ContactsAppOAC', + originAccessControlOriginType: 's3', + signingBehavior: 'always', // Always sign requests + signingProtocol: 'sigv4', + }, + } + ); + + //CloudFront Distribution for Images + /* + const cloudFrontDistribution = new cloudfront.CfnDistribution( + this, + 'ContactsAppDistribution', + { + distributionConfig: { + enabled: true, + defaultRootObject: '', // Optional: Redirect root paths to index.html + origins: [ + { + id: 'S3Origin', + domainName: props.contactsBucket.bucketRegionalDomainName, // Use the regional domain name of the S3 bucket + originAccessControlId: originAccessControl.attrId, // Attach the OAC + s3OriginConfig: {}, // Required when using S3 as the origin + }, + ], + defaultCacheBehavior: { + targetOriginId: 'S3Origin', + viewerProtocolPolicy: 'redirect-to-https', // Enforce HTTPS + allowedMethods: ['GET', 'HEAD'], + cachedMethods: ['GET', 'HEAD'], + forwardedValues: { + queryString: false, + cookies: { forward: 'none' }, + }, + }, + priceClass: 'PriceClass_100', // Adjust to your requirements + viewerCertificate: { + cloudFrontDefaultCertificate: true, + }, + }, + } + ); + + props.contactsBucket.addToResourcePolicy( + new PolicyStatement({ + actions: ['s3:GetObject'], + resources: [`${props.contactsBucket.bucketArn}/*`], + principals: [ + new cdk.aws_iam.ServicePrincipal('cloudfront.amazonaws.com'), + ], + conditions: { + StringEquals: { + 'AWS:SourceArn': `arn:aws:cloudfront::${this.account}:distribution/${cloudFrontDistribution.ref}`, + }, + }, + }) + ); + + cdk.Tags.of(cloudFrontDistribution).add('Service', 'CloudFront'); */ + } +} diff --git a/backendAWS/lib/frontend_stack.ts b/backendAWS/lib/frontend_stack.ts new file mode 100644 index 0000000000000000000000000000000000000000..2ffc1397a44e1f3445577af21a058c7afa2fe29f --- /dev/null +++ b/backendAWS/lib/frontend_stack.ts @@ -0,0 +1,60 @@ +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 s3deploy from 'aws-cdk-lib/aws-s3-deployment'; +import { S3StaticWebsiteOrigin } from 'aws-cdk-lib/aws-cloudfront-origins'; +import { join } from 'path'; + +export class FrontendStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // Create an S3 bucket for the frontend + const frontendBucket = new s3.Bucket(this, 'ContactsAppFrontendBucket', { + bucketName: 'contacts-app-frontend-bucket', + websiteIndexDocument: 'index.html', + /* websiteErrorDocument: 'error.html', */ + removalPolicy: cdk.RemovalPolicy.DESTROY, // Only for dev environments + publicReadAccess: false, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ACLS, + }); + + // Set a bucket policy to allow public access + frontendBucket.addToResourcePolicy( + new cdk.aws_iam.PolicyStatement({ + actions: ['s3:GetObject'], + resources: [`${frontendBucket.bucketArn}/*`], // Allow access to all objects in the bucket + effect: cdk.aws_iam.Effect.ALLOW, + principals: [new cdk.aws_iam.ArnPrincipal('*')], // Allow public access + }) + ); + + //find dir with frontend build + const uiDir = join(__dirname, '..', '..', 'frontend', 'build'); + + // Deploy the frontend files from the 'frontend' folder + new s3deploy.BucketDeployment(this, 'DeployFrontend', { + sources: [s3deploy.Source.asset(uiDir)], // Path to your frontend folder + destinationBucket: frontendBucket, + }); + + // Create CloudFront distribution + const cloudfrontDistribution = new cloudfront.Distribution( + this, + 'FrontendDistribution', + { + defaultBehavior: { + origin: new S3StaticWebsiteOrigin(frontendBucket), + viewerProtocolPolicy: + cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, // Redirect HTTP to HTTPS + }, + } + ); + + // Output the CloudFront URL + new cdk.CfnOutput(this, 'CloudFrontClientAppURL', { + value: `https://${cloudfrontDistribution.distributionDomainName}`, + description: 'CloudFront URL for frontend', + }); + } +} diff --git a/backendAWS/lib/lambda_stack.ts b/backendAWS/lib/lambda_stack.ts new file mode 100644 index 0000000000000000000000000000000000000000..f9d591707b69dc0edea266c29862078b560814c7 --- /dev/null +++ b/backendAWS/lib/lambda_stack.ts @@ -0,0 +1,152 @@ +import * as cdk from 'aws-cdk-lib'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as path from 'path'; +import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam'; + +interface LambdaStackProps extends cdk.StackProps { + contactsTable: dynamodb.Table; + contactsBucket: s3.Bucket; +} + +export class LambdaStack extends cdk.Stack { + public readonly createContactFunction: lambdaNodejs.NodejsFunction; + public readonly getContactFunction: lambdaNodejs.NodejsFunction; + public readonly updateContactFunction: lambdaNodejs.NodejsFunction; + public readonly deleteContactFunction: lambdaNodejs.NodejsFunction; + + constructor(scope: cdk.App, id: string, props: LambdaStackProps) { + super(scope, id, props); + + const { contactsTable, contactsBucket } = props; + + //Create Contact Function Lambda + this.createContactFunction = new lambdaNodejs.NodejsFunction( + this, + 'CreateContactFunction', + { + functionName: 'CreateContactFunction', + runtime: lambda.Runtime.NODEJS_20_X, + entry: path.join(__dirname, '../functions/create-contact/index.ts'), + handler: 'handler', + environment: { + TABLE_NAME: contactsTable.tableName, + BUCKET_NAME: contactsBucket.bucketName, + }, + } + ); + + contactsTable.grantWriteData(this.createContactFunction); + contactsBucket.grantPut(this.createContactFunction); + + this.createContactFunction.addToRolePolicy( + new PolicyStatement({ + effect: Effect.ALLOW, + resources: [contactsTable.tableArn], + actions: [ + 'dynamodb:PutItem', + 'dynamodb:GetItem', + 'dynamodb:UpdateItem', + ], + }) + ); + + //Get Contact Function Lambda + this.getContactFunction = new lambdaNodejs.NodejsFunction( + this, + 'GetContactFunction', + { + functionName: 'GetContactFunction', + runtime: lambda.Runtime.NODEJS_20_X, + entry: path.join(__dirname, '../functions/get-contact/index.ts'), + handler: 'handler', + environment: { + TABLE_NAME: contactsTable.tableName, + }, + } + ); + + contactsTable.grantReadData(this.getContactFunction); + + this.getContactFunction.addToRolePolicy( + new PolicyStatement({ + effect: Effect.ALLOW, + resources: [contactsTable.tableArn], + actions: ['dynamodb:Scan', 'dynamodb:GetItem'], + }) + ); + + //Update Contact Function Lambda + this.updateContactFunction = new lambdaNodejs.NodejsFunction( + this, + 'UpdateContactFunction', + { + functionName: 'UpdateContactFunction', + runtime: lambda.Runtime.NODEJS_20_X, + entry: path.join(__dirname, '../functions/update-contact/index.ts'), + handler: 'handler', + environment: { + TABLE_NAME: contactsTable.tableName, + BUCKET_NAME: contactsBucket.bucketName, + }, + } + ); + + contactsTable.grantWriteData(this.updateContactFunction); + contactsBucket.grantPut(this.updateContactFunction); + + this.updateContactFunction.addToRolePolicy( + new PolicyStatement({ + effect: Effect.ALLOW, + resources: [contactsTable.tableArn], + actions: [ + 'dynamodb:PutItem', + 'dynamodb:GetItem', + 'dynamodb:UpdateItem', + ], + }) + ); + + //Delete Contact Function Lambda + this.deleteContactFunction = new lambdaNodejs.NodejsFunction( + this, + 'DeleteContactFunction', + { + functionName: 'DeleteContactFunction', + runtime: lambda.Runtime.NODEJS_20_X, + entry: path.join(__dirname, '../functions/delete-contact/index.ts'), + handler: 'handler', + environment: { + TABLE_NAME: contactsTable.tableName, + BUCKET_NAME: contactsBucket.bucketName, + }, + } + ); + + contactsTable.grantReadWriteData(this.deleteContactFunction); + contactsBucket.grantDelete(this.deleteContactFunction); + + this.deleteContactFunction.addToRolePolicy( + new PolicyStatement({ + effect: Effect.ALLOW, + resources: [contactsTable.tableArn], + actions: ['dynamodb:DeleteItem', 'dynamodb:GetItem'], + }) + ); + + this.deleteContactFunction.addToRolePolicy( + new PolicyStatement({ + effect: Effect.ALLOW, + resources: [`${contactsBucket.bucketArn}/*`], + actions: ['s3:DeleteObject'], + }) + ); + + cdk.Tags.of(this.createContactFunction).add('Service', 'Lambda'); + cdk.Tags.of(this.getContactFunction).add('Service', 'Lambda'); + cdk.Tags.of(this.updateContactFunction).add('Service', 'Lambda'); + cdk.Tags.of(this.deleteContactFunction).add('Service', 'Lambda'); + } +} diff --git a/backendAWS/lib/storage_stack.ts b/backendAWS/lib/storage_stack.ts new file mode 100644 index 0000000000000000000000000000000000000000..18db14c7461fd2a918817092b7ad071b3c54db4d --- /dev/null +++ b/backendAWS/lib/storage_stack.ts @@ -0,0 +1,32 @@ +import * as cdk from 'aws-cdk-lib'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; + +export class StorageStack extends cdk.Stack { + public readonly contactsTable: dynamodb.Table; + public readonly contactsBucket: s3.Bucket; + + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + //DynamoDB Contacts + this.contactsTable = new dynamodb.Table(this, 'Contacts-app_Table', { + partitionKey: { name: 'id', type: dynamodb.AttributeType.STRING }, + tableName: 'Contacts', + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + cdk.Tags.of(this.contactsTable).add('Service', 'Database'); + + //Contacts Images S3 + + this.contactsBucket = new s3.Bucket(this, 'ContactsAppImagesBucket', { + bucketName: 'contacts-app.images', + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + }); + + cdk.Tags.of(this.contactsBucket).add('Service', 'Storage'); + } +} diff --git a/frontend/src/constants/index.ts b/frontend/src/constants/index.ts index 8ede8b59013ffb8aecc9b0d2ebfc754c85731a06..9fa51bfff5c9e8fb786141b4779d0f95cf1e1f3f 100644 --- a/frontend/src/constants/index.ts +++ b/frontend/src/constants/index.ts @@ -2,8 +2,8 @@ export const apiName = 'contactsApi'; export const resourcePath = 'contacts'; export const region = 'eu-north-1'; export const poolData = { - UserPoolId: 'eu-north-1_k6cYnOn8X', - ClientId: '5n4jouhsheqmo250ptoohih914', + UserPoolId: 'eu-north-1_VJB0zCMTH', + ClientId: '42nc47ndifaeqe861c16eaj30s', }; export const baseURL = - 'https://wdm9jovi8l.execute-api.eu-north-1.amazonaws.com/prod/'; + 'https://yiwft1rh0l.execute-api.eu-north-1.amazonaws.com/prod/';