Skip to content

Commit 3f608fe

Browse files
committed
Add Preview deployments + Cloudfront distro (#6)
1 parent 022a747 commit 3f608fe

31 files changed

+1137
-951
lines changed

.github/workflows/preview.yml

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Create PR Preview
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened]
6+
7+
jobs:
8+
preview:
9+
permissions: write-all
10+
runs-on: ubuntu-latest
11+
timeout-minutes: 15
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v4
15+
16+
- name: Deploy Preview
17+
uses: LocalStack/setup-localstack/preview@main
18+
with:
19+
github-token: ${{ secrets.GITHUB_TOKEN }}
20+
localstack-api-key: ${{ secrets.LOCALSTACK_API_KEY }}
21+
preview-cmd: |
22+
npm install -g aws-cdk-local aws-cdk
23+
pip install awscli-local[ver1]
24+
make build
25+
make bootstrap
26+
make deploy
27+
make prepare-frontend-local
28+
make build-frontend
29+
make bootstrap-frontend
30+
make deploy-frontend
31+
distributionId=$(awslocal cloudfront list-distributions | jq -r '.DistributionList.Items[0].Id')
32+
echo "Open URL: $AWS_ENDPOINT_URL/cloudfront/$AWS_DEFAULT_REGION/$distributionId/"
33+
34+
- name: Finalize PR comment
35+
uses: LocalStack/setup-localstack/finish@main
36+
with:
37+
github-token: ${{ secrets.GITHUB_TOKEN }}
38+
include-preview: true

README.md

+35-15
Large diffs are not rendered by default.

aws-sdk-js-notes.png

-314 KB
Binary file not shown.

images/aws-sdk-js-notes.png

380 KB
Loading

packages/backend/src/createNote.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { success, failure } from "./libs/response";
55

66
// eslint-disable-next-line no-unused-vars
77
import { APIGatewayEvent } from "aws-lambda";
8+
import { endpoint } from "./libs/endpoint";
89

910
export const handler = async (event: APIGatewayEvent) => {
1011
const data = JSON.parse(event.body || "{}");
@@ -19,7 +20,7 @@ export const handler = async (event: APIGatewayEvent) => {
1920
};
2021

2122
try {
22-
const client = new DynamoDBClient({});
23+
const client = new DynamoDBClient({endpoint});
2324
await client.send(new PutItemCommand(params));
2425
return success(params.Item);
2526
} catch (e) {

packages/backend/src/deleteNote.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { success, failure } from "./libs/response";
44

55
// eslint-disable-next-line no-unused-vars
66
import { APIGatewayEvent } from "aws-lambda";
7+
import { endpoint } from "./libs/endpoint";
78

89
export const handler = async (event: APIGatewayEvent) => {
910
const params = {
@@ -14,7 +15,7 @@ export const handler = async (event: APIGatewayEvent) => {
1415
};
1516

1617
try {
17-
const client = new DynamoDBClient({});
18+
const client = new DynamoDBClient({endpoint});
1819
await client.send(new DeleteItemCommand(params));
1920
return success({ status: true });
2021
} catch (e) {

packages/backend/src/getNote.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { success, failure } from "./libs/response";
44

55
// eslint-disable-next-line no-unused-vars
66
import { APIGatewayEvent } from "aws-lambda";
7+
import { endpoint } from "./libs/endpoint";
78

89
export const handler = async (event: APIGatewayEvent) => {
910
const params = {
@@ -14,7 +15,7 @@ export const handler = async (event: APIGatewayEvent) => {
1415
};
1516

1617
try {
17-
const client = new DynamoDBClient({});
18+
const client = new DynamoDBClient({endpoint});
1819
const result = await client.send(new GetItemCommand(params));
1920
if (result.Item) {
2021
// Return the retrieved item

packages/backend/src/libs/endpoint.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const endpoint = process.env.AWS_ENDPOINT_URL || '';

packages/backend/src/listNotes.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import { DynamoDBClient, ScanCommand } from "@aws-sdk/client-dynamodb";
22
import { unmarshall } from "@aws-sdk/util-dynamodb";
33
import { success, failure } from "./libs/response";
4+
import { endpoint } from "./libs/endpoint";
45

56
export const handler = async () => {
67
const params = {
78
TableName: process.env.NOTES_TABLE_NAME || "",
89
};
910

11+
1012
try {
11-
const client = new DynamoDBClient({});
13+
const client = new DynamoDBClient({endpoint});
1214
const result = await client.send(new ScanCommand(params));
1315
// Return the matching list of items in response body
1416
return success(result.Items.map((Item) => unmarshall(Item)));

packages/backend/src/updateNote.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { success, failure } from "./libs/response";
44

55
// eslint-disable-next-line no-unused-vars
66
import { APIGatewayEvent } from "aws-lambda";
7+
import { endpoint } from "./libs/endpoint";
78

89
export const handler = async (event: APIGatewayEvent) => {
910
const data = JSON.parse(event.body || "{}");
@@ -23,7 +24,7 @@ export const handler = async (event: APIGatewayEvent) => {
2324
};
2425

2526
try {
26-
const client = new DynamoDBClient({});
27+
const client = new DynamoDBClient({endpoint});
2728
await client.send(new UpdateItemCommand(params));
2829
return success({ status: true });
2930
} catch (e) {

packages/frontend/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,14 @@
1313
"@aws-sdk/s3-request-presigner": "3.245.0",
1414
"@aws-sdk/util-create-request": "3.234.0",
1515
"@aws-sdk/util-format-url": "3.226.0",
16-
"@reach/router": "1.3.4",
1716
"buffer": "6.0.3",
1817
"events": "^3.3.0",
1918
"microphone-stream": "6.0.1",
2019
"process": "0.11.10",
2120
"react": "18.2.0",
2221
"react-bootstrap": "1.4.0",
2322
"react-dom": "18.2.0",
23+
"react-router-dom": "^6.20.0",
2424
"util": "^0.12.5"
2525
},
2626
"scripts": {

packages/frontend/src/Routes.tsx

+17-13
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
1-
import React, { lazy, Suspense } from "react";
2-
import { Router } from "@reach/router";
1+
import React from "react";
2+
import { Suspense } from "react";
3+
import { BrowserRouter, Route, Routes as RouterRoutes } from "react-router-dom";
4+
import ListNotes from "./content/ListNotes";
5+
import CreateNote from "./content/CreateNote";
6+
import ShowNote from "./content/ShowNote";
7+
import NotFound from "./content/NotFound";
38

4-
const ListNotes = lazy(() => import("./content/ListNotes"));
5-
const CreateNote = lazy(() => import("./content/CreateNote"));
6-
const ShowNote = lazy(() => import("./content/ShowNote"));
7-
const NotFound = lazy(() => import("./content/NotFound"));
9+
import { BASE_URL } from "./config";
810

9-
const Routes = () => (
11+
const Routes = () => (
1012
<div className="mt-md-4 d-flex flex-column justify-content-center">
1113
<Suspense fallback={<div>Loading...</div>}>
12-
<Router>
13-
<ListNotes path="/" />
14-
<CreateNote path="/note/new" />
15-
<ShowNote path="/notes/:noteId" />
16-
<NotFound default />
17-
</Router>
14+
<BrowserRouter basename={BASE_URL}>
15+
<RouterRoutes>
16+
<Route path="/" element={<ListNotes/>} />
17+
<Route path="/note/new" element={<CreateNote/>} />
18+
<Route path="/notes/:noteId" element={<ShowNote/>} />
19+
<Route element={<NotFound/>} />
20+
</RouterRoutes>
21+
</BrowserRouter>
1822
</Suspense>
1923
</div>
2024
);

packages/frontend/src/components/HomeButton.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import React from "react";
22
import { Button } from "react-bootstrap";
3+
import { BASE_URL } from "../config";
4+
35

46
const HomeButton = () => (
5-
<Button href="/" variant="light" className="border border-secondary">
7+
<Button href={BASE_URL} variant="light" className="border border-secondary">
68
&lt; Home
79
</Button>
810
);

packages/frontend/src/config.json

-7
This file was deleted.

packages/frontend/src/config.tsx

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const getOrigin = () => {
2+
const origin = window.location.origin;
3+
if (origin.indexOf('localhost') !== -1) {
4+
return 'http://localhost:4566';
5+
}
6+
return origin;
7+
}
8+
9+
10+
export const GATEWAY_URL = `${getOrigin()}/restapis/${import.meta.env.VITE_GATEWAY_ID}/prod/_user_request_/`;
11+
export const MAX_FILE_SIZE = 500000;
12+
export const FILES_BUCKET = import.meta.env.VITE_FILES_BUCKET;
13+
export const REGION = import.meta.env.VITE_REGION;
14+
export const IDENTITY_POOL_ID = import.meta.env.VITE_IDENTITY_POOL_ID;
15+
export const BASE_URL = import.meta.env.BASE_URL;

packages/frontend/src/content/CreateNote.tsx

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import React, { useState, FormEvent } from "react";
22
import { Form, Button, Alert } from "react-bootstrap";
3-
import { navigate, RouteComponentProps } from "@reach/router";
4-
import { GATEWAY_URL, MAX_FILE_SIZE } from "../config.json";
3+
import { GATEWAY_URL, MAX_FILE_SIZE } from "../config";
54
import { putObject } from "../libs";
65
import { HomeButton, ButtonSpinner, PageContainer } from "../components";
6+
import { useNavigate } from "react-router-dom";
77

8-
const CreateNote = (props: RouteComponentProps) => {
8+
const CreateNote = (): JSX.Element => {
99
const [isLoading, setIsLoading] = useState(false);
1010
const [errorMsg, setErrorMsg] = useState("");
1111
const [noteContent, setNoteContent] = useState("");
1212
const [file, setFile] = useState();
1313

14+
const navigate = useNavigate();
15+
1416
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
1517
event.preventDefault();
1618

packages/frontend/src/content/DeleteNoteButton.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import React, { useState } from "react";
22
import { Button, Alert } from "react-bootstrap";
3-
import { GATEWAY_URL } from "../config.json";
4-
import { navigate } from "@reach/router";
3+
import { GATEWAY_URL } from "../config";
54
import { deleteObject } from "../libs";
65
import { ButtonSpinner } from "../components";
6+
import { useNavigate } from "react-router-dom";
77

88
const DeleteNoteButton = (props: { noteId: string; attachment?: string }) => {
99
const { noteId, attachment } = props;
1010
const [isDeleting, setIsDeleting] = useState(false);
1111
const [errorMsg, setErrorMsg] = useState("");
1212

13+
const navigate = useNavigate();
14+
1315
const handleDelete = async (event: any) => {
1416
event.preventDefault();
1517
setIsDeleting(true);

packages/frontend/src/content/ListNotes.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
import React, { useState, useEffect } from "react";
2-
import { Link, RouteComponentProps } from "@reach/router";
3-
import { GATEWAY_URL } from "../config.json";
2+
import { GATEWAY_URL } from "../config";
43
import { Card, Alert, CardColumns, Button } from "react-bootstrap";
54
import { Loading, PageContainer } from "../components";
5+
import { Link } from "react-router-dom";
66
interface Note {
77
noteId: string;
88
createdAt: string;
99
content: string;
1010
attachment: boolean;
1111
}
1212

13-
const ListNotes = (props: RouteComponentProps) => {
13+
const ListNotes = (): JSX.Element => {
1414
const [isLoading, setIsLoading] = useState(true);
1515
const [errorMsg, setErrorMsg] = useState("");
1616
const [notes, setNotes] = useState([]);

packages/frontend/src/content/NotFound.tsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
import React from "react";
2-
import { RouteComponentProps } from "@reach/router";
31
import { HomeButton, PageContainer } from "../components";
42

5-
const NotFound = (props: RouteComponentProps) => (
3+
const NotFound = (): JSX.Element => (
64
<PageContainer header={<HomeButton />}>404 Page Not Found</PageContainer>
75
);
86

packages/frontend/src/content/SaveNoteButton.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import React, { useState } from "react";
22
import { Button, Alert } from "react-bootstrap";
3-
import { GATEWAY_URL } from "../config.json";
4-
import { navigate } from "@reach/router";
3+
import { GATEWAY_URL } from "../config";
54
import { ButtonSpinner } from "../components";
5+
import { useNavigate } from "react-router-dom";
66

77
const SaveNoteButton = (props: { noteId: string; noteContent: string }) => {
88
const [isSaving, setIsSaving] = useState(false);
99
const [errorMsg, setErrorMsg] = useState("");
1010

11+
const navigate = useNavigate();
12+
1113
const handleSave = async (event: any) => {
1214
event.preventDefault();
1315
setIsSaving(true);

packages/frontend/src/content/ShowNote.tsx

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
import React, { useState, useEffect } from "react";
2-
import { RouteComponentProps, navigate } from "@reach/router";
32
import { Form, Card } from "react-bootstrap";
4-
import { GATEWAY_URL } from "../config.json";
3+
import { GATEWAY_URL } from "../config";
54
import { DeleteNoteButton, SaveNoteButton } from "./";
65
import { getObjectUrl } from "../libs";
76
import { HomeButton, Loading, PageContainer } from "../components";
7+
import { useNavigate, useParams } from "react-router-dom";
88

9-
const ShowNote = (props: RouteComponentProps<{ noteId: string }>) => {
10-
const { noteId } = props;
9+
const ShowNote = () => {
10+
const { noteId } = useParams<'noteId'>();
1111
const [isLoading, setIsLoading] = useState(true);
1212
const [noteContent, setNoteContent] = useState("");
1313
const [attachment, setAttachment] = useState("");
1414
const [attachmentURL, setAttachmentURL] = useState("");
15+
const navigate = useNavigate();
1516

1617
useEffect(() => {
1718
const fetchNote = async (noteId: string) => {

packages/frontend/src/libs/deleteObject.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { s3Client } from "./s3Client";
2-
import { FILES_BUCKET } from "../config.json";
2+
import { FILES_BUCKET } from "../config";
33
import { DeleteObjectCommand } from "@aws-sdk/client-s3";
44

55
const deleteObject = async (fileName: string) =>

packages/frontend/src/libs/getObjectUrl.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { s3Client } from "./s3Client";
2-
import { FILES_BUCKET } from "../config.json";
2+
import { FILES_BUCKET } from "../config";
33

44
import { createRequest } from "@aws-sdk/util-create-request";
55
import { GetObjectCommand } from "@aws-sdk/client-s3";

packages/frontend/src/libs/putObject.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { s3Client } from "./s3Client";
2-
import { FILES_BUCKET } from "../config.json";
2+
import { FILES_BUCKET } from "../config";
33
import { PutObjectCommand } from "@aws-sdk/client-s3";
44

55
const putObject = async (file: File) => {

packages/frontend/src/libs/s3Client.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { S3Client } from "@aws-sdk/client-s3";
22
import { fromCognitoIdentityPool } from "@aws-sdk/credential-provider-cognito-identity";
33
import { CognitoIdentityClient } from "@aws-sdk/client-cognito-identity";
4-
import { IDENTITY_POOL_ID, REGION } from "../config.json";
4+
import { IDENTITY_POOL_ID, REGION } from "../config";
55

66
const s3Client = new S3Client({
77
region: REGION,

packages/frontend/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"compilerOptions": {
33
"target": "ESNext",
4+
"types": ["vite/client"],
45
"lib": ["dom", "dom.iterable", "esnext"],
56
"allowJs": true,
67
"skipLibCheck": true,

packages/frontend/vite.config.ts

+13-8
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
import { defineConfig } from "vite";
1+
import { defineConfig, loadEnv } from "vite";
22
import reactRefresh from "@vitejs/plugin-react-refresh";
33

44
// https://vitejs.dev/config/
5-
export default defineConfig({
6-
plugins: [reactRefresh()],
7-
resolve: {
8-
alias: {
9-
"./runtimeConfig": "./runtimeConfig.browser",
5+
export default ({mode}) => {
6+
console.log(process.env.VITE_BASE_URL)
7+
process.env = {...process.env, ...loadEnv(mode, process.cwd())};
8+
return defineConfig({
9+
plugins: [reactRefresh()],
10+
base: process.env.VITE_BASE_URL,
11+
resolve: {
12+
alias: {
13+
"./runtimeConfig": "./runtimeConfig.browser",
14+
},
1015
},
11-
},
12-
});
16+
});
17+
}

0 commit comments

Comments
 (0)