From 2cee186edc7adf644916e187bafb782266d6fb98 Mon Sep 17 00:00:00 2001 From: Stefanie Plieschnegger Date: Thu, 27 Jul 2023 12:38:09 +0200 Subject: [PATCH] prepare app to work with community version of LocalStack --- .github/workflows/main.yml | 3 +- README.md | 4 +- packages/backend/src/createNote.ts | 14 ++++- packages/backend/src/deleteNote.ts | 13 ++++- packages/backend/src/getNote.ts | 17 +++++- packages/backend/src/libs/response.ts | 4 ++ packages/backend/src/listNotes.ts | 13 ++++- packages/backend/src/updateNote.ts | 13 ++++- packages/frontend/README.md | 3 - packages/frontend/src/config.json | 2 - packages/frontend/src/content/CreateNote.tsx | 4 +- .../frontend/src/content/DeleteNoteButton.tsx | 4 -- packages/frontend/src/content/ShowNote.tsx | 5 -- packages/frontend/src/libs/deleteObject.ts | 13 ----- packages/frontend/src/libs/getObjectUrl.ts | 26 --------- packages/frontend/src/libs/index.ts | 4 -- packages/frontend/src/libs/putObject.ts | 17 ------ packages/frontend/src/libs/s3Client.ts | 14 ----- .../infra/cdk/aws-sdk-js-notes-app-stack.ts | 57 ------------------- packages/scripts/populate-frontend-config.js | 15 ++--- 20 files changed, 76 insertions(+), 169 deletions(-) delete mode 100644 packages/frontend/src/libs/deleteObject.ts delete mode 100644 packages/frontend/src/libs/getObjectUrl.ts delete mode 100644 packages/frontend/src/libs/index.ts delete mode 100644 packages/frontend/src/libs/putObject.ts delete mode 100644 packages/frontend/src/libs/s3Client.ts diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 578e988..d8cee40 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,12 +29,11 @@ jobs: - name: Start LocalStack env: - LOCALSTACK_API_KEY: ${{ secrets.LOCALSTACK_API_KEY }} DNS_ADDRESS: 0 run: | pip install localstack awscli-local[ver1] pip install terraform-local - docker pull localstack/localstack-pro:latest + docker pull localstack/localstack:latest # Start LocalStack in the background DEBUG=1 localstack start -d # Wait 30 seconds for the LocalStack container to become ready before timing out diff --git a/README.md b/README.md index 14fc78b..f77e5c3 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,6 @@ We are using the following AWS services and their features to build our infrastr Start LocalStack Pro with the `LOCALSTACK_API_KEY` pre-configured: ```shell -export LOCALSTACK_API_KEY= EXTRA_CORS_ALLOWED_ORIGINS=* localstack start ``` @@ -162,5 +161,4 @@ The sample application is based on a public [AWS sample app](https://github.com/ ## Contributing We appreciate your interest in contributing to our project and are always looking for new ways to improve the developer experience. We welcome feedback, bug reports, and even feature ideas from the community. -Please refer to the [contributing file](CONTRIBUTING.md) for more details on how to get started. - +Please refer to the [contributing file](CONTRIBUTING.md) for more details on how to get started. diff --git a/packages/backend/src/createNote.ts b/packages/backend/src/createNote.ts index 7ae36df..17a33fa 100644 --- a/packages/backend/src/createNote.ts +++ b/packages/backend/src/createNote.ts @@ -19,7 +19,19 @@ export const handler = async (event: APIGatewayEvent) => { }; try { - const client = new DynamoDBClient({}); + let client: DynamoDBClient; + + if (process.env.LOCALSTACK_HOSTNAME) { + const localStackConfig = { + endpoint: `http://${process.env.LOCALSTACK_HOSTNAME}:${process.env.EDGE_PORT}`, + region: "us-east-1", // Change the region as per your setup + }; + client = new DynamoDBClient(localStackConfig); + } else { + // Use the default AWS configuration + client = new DynamoDBClient({}); + } + await client.send(new PutItemCommand(params)); return success(params.Item); } catch (e) { diff --git a/packages/backend/src/deleteNote.ts b/packages/backend/src/deleteNote.ts index e45ab87..112f074 100644 --- a/packages/backend/src/deleteNote.ts +++ b/packages/backend/src/deleteNote.ts @@ -14,7 +14,18 @@ export const handler = async (event: APIGatewayEvent) => { }; try { - const client = new DynamoDBClient({}); + let client: DynamoDBClient; + + if (process.env.LOCALSTACK_HOSTNAME) { + const localStackConfig = { + endpoint: `http://${process.env.LOCALSTACK_HOSTNAME}:${process.env.EDGE_PORT}`, + region: "us-east-1", // Change the region as per your setup + }; + client = new DynamoDBClient(localStackConfig); + } else { + // Use the default AWS configuration + client = new DynamoDBClient({}); + } await client.send(new DeleteItemCommand(params)); return success({ status: true }); } catch (e) { diff --git a/packages/backend/src/getNote.ts b/packages/backend/src/getNote.ts index eaa4016..09c1999 100644 --- a/packages/backend/src/getNote.ts +++ b/packages/backend/src/getNote.ts @@ -1,6 +1,6 @@ import { DynamoDBClient, GetItemCommand } from "@aws-sdk/client-dynamodb"; import { marshall, unmarshall } from "@aws-sdk/util-dynamodb"; -import { success, failure } from "./libs/response"; +import { success, failure, not_found } from "./libs/response"; // eslint-disable-next-line no-unused-vars import { APIGatewayEvent } from "aws-lambda"; @@ -14,13 +14,24 @@ export const handler = async (event: APIGatewayEvent) => { }; try { - const client = new DynamoDBClient({}); + let client: DynamoDBClient; + + if (process.env.LOCALSTACK_HOSTNAME) { + const localStackConfig = { + endpoint: `http://${process.env.LOCALSTACK_HOSTNAME}:${process.env.EDGE_PORT}`, + region: "us-east-1", // Change the region as per your setup + }; + client = new DynamoDBClient(localStackConfig); + } else { + // Use the default AWS configuration + client = new DynamoDBClient({}); + } const result = await client.send(new GetItemCommand(params)); if (result.Item) { // Return the retrieved item return success(unmarshall(result.Item)); } else { - return failure({ status: false, error: "Item not found." }); + return not_found({ status: false, error: "Item not found." }); } } catch (e) { console.log(e); diff --git a/packages/backend/src/libs/response.ts b/packages/backend/src/libs/response.ts index 63dbc12..740e337 100644 --- a/packages/backend/src/libs/response.ts +++ b/packages/backend/src/libs/response.ts @@ -6,6 +6,10 @@ export const failure = (body: any) => { return buildResponse(500, body); }; +export const not_found = (body: any) => { + return buildResponse(404, body); +}; + const buildResponse = (statusCode: number, body: any) => ({ statusCode: statusCode, headers: { diff --git a/packages/backend/src/listNotes.ts b/packages/backend/src/listNotes.ts index f42bfd9..2dc4b02 100644 --- a/packages/backend/src/listNotes.ts +++ b/packages/backend/src/listNotes.ts @@ -8,7 +8,18 @@ export const handler = async () => { }; try { - const client = new DynamoDBClient({}); + let client: DynamoDBClient; + + if (process.env.LOCALSTACK_HOSTNAME) { + const localStackConfig = { + endpoint: `http://${process.env.LOCALSTACK_HOSTNAME}:${process.env.EDGE_PORT}`, + region: "us-east-1", // Change the region as per your setup + }; + client = new DynamoDBClient(localStackConfig); + } else { + // Use the default AWS configuration + client = new DynamoDBClient({}); + } const result = await client.send(new ScanCommand(params)); // Return the matching list of items in response body return success(result.Items.map((Item) => unmarshall(Item))); diff --git a/packages/backend/src/updateNote.ts b/packages/backend/src/updateNote.ts index 0a36b19..e36ac6c 100644 --- a/packages/backend/src/updateNote.ts +++ b/packages/backend/src/updateNote.ts @@ -23,7 +23,18 @@ export const handler = async (event: APIGatewayEvent) => { }; try { - const client = new DynamoDBClient({}); + let client: DynamoDBClient; + + if (process.env.LOCALSTACK_HOSTNAME) { + const localStackConfig = { + endpoint: `http://${process.env.LOCALSTACK_HOSTNAME}:${process.env.EDGE_PORT}`, + region: "us-east-1", // Change the region as per your setup + }; + client = new DynamoDBClient(localStackConfig); + } else { + // Use the default AWS configuration + client = new DynamoDBClient({}); + } await client.send(new UpdateItemCommand(params)); return success({ status: true }); } catch (e) { diff --git a/packages/frontend/README.md b/packages/frontend/README.md index 8f67ef6..9f4b807 100644 --- a/packages/frontend/README.md +++ b/packages/frontend/README.md @@ -32,11 +32,8 @@ Ensure that you've followed pre-requisites from main [README](../../README.md), - `yarn prepare:frontend` to populate Cloudformation resources in frontend config. - The resources can also be manually added in [`src/config.json`](./src/config.json). - - Add `aws-js-sdk-notes-app.FilesBucket` from CDK output for `FILES_BUCKET`. - Add `aws-js-sdk-notes-app.GatewayUrl` from CDK output for `GATEWAY_URL`. - Example GatewayURL: `https://randomstring.execute-api.region.amazonaws.com/prod/` - - Add `aws-js-sdk-notes-app.IdentityPoolId` from CDK output for `IDENTITY_POOL_ID`. - - Example IdentityPoolId: `region:random-strc-4ce1-84ee-9a429f9b557e` - Add `aws-js-sdk-notes-app.Region` from CDK output for `REGION`. - `yarn start:frontend` to run the server. - This will open the website in the browser, and enable HMR. diff --git a/packages/frontend/src/config.json b/packages/frontend/src/config.json index 25ecc59..aa942e4 100644 --- a/packages/frontend/src/config.json +++ b/packages/frontend/src/config.json @@ -1,7 +1,5 @@ { - "FILES_BUCKET": "", "GATEWAY_URL": "", - "IDENTITY_POOL_ID": "", "REGION": "", "MAX_FILE_SIZE": 2000000 } diff --git a/packages/frontend/src/content/CreateNote.tsx b/packages/frontend/src/content/CreateNote.tsx index f004bdb..49c7f6e 100644 --- a/packages/frontend/src/content/CreateNote.tsx +++ b/packages/frontend/src/content/CreateNote.tsx @@ -2,7 +2,6 @@ import React, { useState, FormEvent } from "react"; import { Form, Button, Alert } from "react-bootstrap"; import { navigate, RouteComponentProps } from "@reach/router"; import { GATEWAY_URL, MAX_FILE_SIZE } from "../config.json"; -import { putObject } from "../libs"; import { HomeButton, ButtonSpinner, PageContainer } from "../components"; const CreateNote = (props: RouteComponentProps) => { @@ -26,10 +25,9 @@ const CreateNote = (props: RouteComponentProps) => { try { // @ts-ignore Argument of type 'undefined' is not assignable to parameter of type 'File' - const attachment = file ? await putObject(file) : undefined; await fetch(createNoteURL, { method: "POST", - body: JSON.stringify({ attachment, content: noteContent }), + body: JSON.stringify({ content: noteContent }), }); navigate("/"); } catch (error) { diff --git a/packages/frontend/src/content/DeleteNoteButton.tsx b/packages/frontend/src/content/DeleteNoteButton.tsx index 6ee725c..220cde6 100644 --- a/packages/frontend/src/content/DeleteNoteButton.tsx +++ b/packages/frontend/src/content/DeleteNoteButton.tsx @@ -2,7 +2,6 @@ import React, { useState } from "react"; import { Button, Alert } from "react-bootstrap"; import { GATEWAY_URL } from "../config.json"; import { navigate } from "@reach/router"; -import { deleteObject } from "../libs"; import { ButtonSpinner } from "../components"; const DeleteNoteButton = (props: { noteId: string; attachment?: string }) => { @@ -17,9 +16,6 @@ const DeleteNoteButton = (props: { noteId: string; attachment?: string }) => { const deleteNoteURL = `${GATEWAY_URL}notes/${noteId}`; try { - if (attachment) { - await deleteObject(attachment); - } await fetch(deleteNoteURL, { method: "DELETE", }); diff --git a/packages/frontend/src/content/ShowNote.tsx b/packages/frontend/src/content/ShowNote.tsx index f2bc3f3..cd2d5c7 100644 --- a/packages/frontend/src/content/ShowNote.tsx +++ b/packages/frontend/src/content/ShowNote.tsx @@ -3,7 +3,6 @@ import { RouteComponentProps, navigate } from "@reach/router"; import { Form, Card } from "react-bootstrap"; import { GATEWAY_URL } from "../config.json"; import { DeleteNoteButton, SaveNoteButton } from "./"; -import { getObjectUrl } from "../libs"; import { HomeButton, Loading, PageContainer } from "../components"; const ShowNote = (props: RouteComponentProps<{ noteId: string }>) => { @@ -22,10 +21,6 @@ const ShowNote = (props: RouteComponentProps<{ noteId: string }>) => { const response = await fetch(fetchURL); const data = await response.json(); setNoteContent(data.content); - if (data.attachment) { - setAttachment(data.attachment); - setAttachmentURL(await getObjectUrl(data.attachment)); - } } catch (error) { // Navigate to 404 page, as noteId probably not present navigate("/404"); diff --git a/packages/frontend/src/libs/deleteObject.ts b/packages/frontend/src/libs/deleteObject.ts deleted file mode 100644 index ac2012a..0000000 --- a/packages/frontend/src/libs/deleteObject.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { s3Client } from "./s3Client"; -import { FILES_BUCKET } from "../config.json"; -import { DeleteObjectCommand } from "@aws-sdk/client-s3"; - -const deleteObject = async (fileName: string) => - s3Client.send( - new DeleteObjectCommand({ - Key: fileName, - Bucket: FILES_BUCKET, - }) - ); - -export { deleteObject }; diff --git a/packages/frontend/src/libs/getObjectUrl.ts b/packages/frontend/src/libs/getObjectUrl.ts deleted file mode 100644 index 095f699..0000000 --- a/packages/frontend/src/libs/getObjectUrl.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { s3Client } from "./s3Client"; -import { FILES_BUCKET } from "../config.json"; - -import { createRequest } from "@aws-sdk/util-create-request"; -import { GetObjectCommand } from "@aws-sdk/client-s3"; -import { S3RequestPresigner } from "@aws-sdk/s3-request-presigner"; -import { formatUrl } from "@aws-sdk/util-format-url"; - -const getObjectUrl = async (fileName: string) => { - const request = await createRequest( - s3Client, - new GetObjectCommand({ - Key: fileName, - Bucket: FILES_BUCKET, - }) - ); - - const signer = new S3RequestPresigner({ - ...s3Client.config, - }); - - const url = await signer.presign(request); - return formatUrl(url); -}; - -export { getObjectUrl }; diff --git a/packages/frontend/src/libs/index.ts b/packages/frontend/src/libs/index.ts deleted file mode 100644 index b8d9635..0000000 --- a/packages/frontend/src/libs/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./s3Client"; -export * from "./getObjectUrl"; -export * from "./putObject"; -export * from "./deleteObject"; diff --git a/packages/frontend/src/libs/putObject.ts b/packages/frontend/src/libs/putObject.ts deleted file mode 100644 index 64fb809..0000000 --- a/packages/frontend/src/libs/putObject.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { s3Client } from "./s3Client"; -import { FILES_BUCKET } from "../config.json"; -import { PutObjectCommand } from "@aws-sdk/client-s3"; - -const putObject = async (file: File) => { - const Key = `${Date.now()}-${file.name}`; - await s3Client.send( - new PutObjectCommand({ - Key, - Body: file, - Bucket: FILES_BUCKET, - }) - ); - return Key; -}; - -export { putObject }; diff --git a/packages/frontend/src/libs/s3Client.ts b/packages/frontend/src/libs/s3Client.ts deleted file mode 100644 index 46d8879..0000000 --- a/packages/frontend/src/libs/s3Client.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { S3Client } from "@aws-sdk/client-s3"; -import { fromCognitoIdentityPool } from "@aws-sdk/credential-provider-cognito-identity"; -import { CognitoIdentityClient } from "@aws-sdk/client-cognito-identity"; -import { IDENTITY_POOL_ID, REGION } from "../config.json"; - -const s3Client = new S3Client({ - region: REGION, - credentials: fromCognitoIdentityPool({ - client: new CognitoIdentityClient({ region: REGION }), - identityPoolId: IDENTITY_POOL_ID, - }), -}); - -export { s3Client }; diff --git a/packages/infra/cdk/aws-sdk-js-notes-app-stack.ts b/packages/infra/cdk/aws-sdk-js-notes-app-stack.ts index cf74969..40ae9c9 100644 --- a/packages/infra/cdk/aws-sdk-js-notes-app-stack.ts +++ b/packages/infra/cdk/aws-sdk-js-notes-app-stack.ts @@ -73,64 +73,7 @@ export class AwsSdkJsNotesAppStack extends Stack { ) ); - const filesBucket = new s3.Bucket(this, "files-bucket"); - filesBucket.addCorsRule({ - allowedOrigins: apigw.Cors.ALL_ORIGINS, // NOT recommended for production code - allowedMethods: [ - s3.HttpMethods.PUT, - s3.HttpMethods.GET, - s3.HttpMethods.DELETE, - ], - allowedHeaders: ["*"], - }); - - const identityPool = new cognito.CfnIdentityPool(this, "identity-pool", { - allowUnauthenticatedIdentities: true, - }); - - const unauthenticated = new iam.Role(this, "unauthenticated-role", { - assumedBy: new iam.FederatedPrincipal( - "cognito-identity.amazonaws.com", - { - StringEquals: { - "cognito-identity.amazonaws.com:aud": identityPool.ref, - }, - "ForAnyValue:StringLike": { - "cognito-identity.amazonaws.com:amr": "unauthenticated", - }, - }, - "sts:AssumeRoleWithWebIdentity" - ), - }); - - // NOT recommended for production code - only give read permissions for unauthenticated resources - filesBucket.grantRead(unauthenticated); - filesBucket.grantPut(unauthenticated); - filesBucket.grantDelete(unauthenticated); - - // Add policy to start Transcribe stream transcription - unauthenticated.addToPolicy( - new iam.PolicyStatement({ - resources: ["*"], - actions: ["transcribe:StartStreamTranscriptionWebSocket"], - }) - ); - - // Add policy to enable Amazon Polly text-to-speech - unauthenticated.addManagedPolicy( - iam.ManagedPolicy.fromAwsManagedPolicyName("AmazonPollyFullAccess") - ); - - new cognito.CfnIdentityPoolRoleAttachment(this, "role-attachment", { - identityPoolId: identityPool.ref, - roles: { - unauthenticated: unauthenticated.roleArn, - }, - }); - - new CfnOutput(this, "FilesBucket", { value: filesBucket.bucketName }); new CfnOutput(this, "GatewayUrl", { value: api.url }); - new CfnOutput(this, "IdentityPoolId", { value: identityPool.ref }); new CfnOutput(this, "Region", { value: this.region }); } } diff --git a/packages/scripts/populate-frontend-config.js b/packages/scripts/populate-frontend-config.js index 8d42c2b..61e101d 100644 --- a/packages/scripts/populate-frontend-config.js +++ b/packages/scripts/populate-frontend-config.js @@ -12,13 +12,12 @@ const isLocal = process.argv.includes("--local"); const configFile = join(__dirname, "..", "frontend", "src", "config.json"); try { - const deployCommand = isLocal ? "yarn cdklocal deploy --outputs-file" : "yarn cdk deploy --outputs-file"; - const execProcess = exec( - `${deployCommand} ${cdkOutputsFile}`, - { - cwd: join(__dirname, "..", "infra"), - } - ); + const deployCommand = isLocal + ? "yarn cdklocal deploy --outputs-file" + : "yarn cdk deploy --outputs-file"; + const execProcess = exec(`${deployCommand} ${cdkOutputsFile}`, { + cwd: join(__dirname, "..", "infra"), + }); execProcess.stdout.pipe(process.stdout); execProcess.stderr.pipe(process.stderr); await new Promise((resolve) => { @@ -34,9 +33,7 @@ const isLocal = process.argv.includes("--local"); const cdkOutput = JSON.parse(readFileSync(cdkOutputsFile))[ "aws-sdk-js-notes-app" ]; - configContents.FILES_BUCKET = cdkOutput.FilesBucket; configContents.GATEWAY_URL = cdkOutput.GatewayUrl; - configContents.IDENTITY_POOL_ID = cdkOutput.IdentityPoolId; configContents.REGION = cdkOutput.Region; writeFileSync(configFile, JSON.stringify(configContents, null, 2)); } catch (error) {