Skip to content

Commit

Permalink
feat: store messages
Browse files Browse the repository at this point in the history
  • Loading branch information
jbrunton committed Dec 29, 2022
1 parent 272c303 commit 7b1bc31
Show file tree
Hide file tree
Showing 21 changed files with 2,442 additions and 57 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/node_modules/
/docker/
18 changes: 6 additions & 12 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import reactLogo from './assets/react.svg'
import './App.css'
import { AuthWidget } from './components/auth/AuthWidget'
import { useGreeting } from './data/greetings'
import { MessagesWidget } from './components/messages/MessagesWidget'
import { useMessages } from './data/messages'


function App() {
const [count, setCount] = useState(0)
const { isLoading, data: apiResponse } = useGreeting();

const [roomId, setRoomId] = useState("101");

return (
<div className="App">
<div>
Expand All @@ -21,15 +22,8 @@ function App() {
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<div className="card">
<p>Api response: <span>{isLoading ? "loading" : apiResponse}</span></p>
<input placeholder='room ID' value={roomId} onChange={(e) => setRoomId(e.target.value)} />
<MessagesWidget roomId={roomId} />
</div>
<div>
<AuthWidget />
Expand Down
20 changes: 20 additions & 0 deletions client/src/components/auth/useAccessToken.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useAuth0 } from "@auth0/auth0-react";
import { useEffect, useState } from "react";

export const useAccessToken = (): string | undefined => {
const { isAuthenticated, getAccessTokenSilently } = useAuth0();
const [accessToken, setAccessToken] = useState<string>();
useEffect(() => {
if (isAuthenticated) {
const getAccessToken = async () => {
const accessToken = await getAccessTokenSilently({
audience: `https://auth0-test-api.jbrunton-aws.com`,
scope: "openid profile email",
});
setAccessToken(accessToken);
}
getAccessToken();
}
}, [isAuthenticated]);
return accessToken;
};
33 changes: 33 additions & 0 deletions client/src/components/messages/MessagesWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React, { useState, KeyboardEventHandler } from "react";
import { useMessages, usePostMessage } from "../../data/messages";
import { useAccessToken } from "../auth/useAccessToken";

export type MessagesWidgetProps = {
roomId: string;
}

export const MessagesWidget: React.FC<MessagesWidgetProps> = ({ roomId }) => {
const { data: messages } = useMessages(roomId);
const [content, setContent] = useState<string>("");
const accessToken = useAccessToken();
const { mutate } = usePostMessage(roomId, content, accessToken)
const onKeyDown: KeyboardEventHandler = (e) => {
if (e.key === 'Enter') {
mutate();
setContent("");
}
};
return (
<div>
{messages && (
messages.map(msg => (
<div key={msg.id}>
<p>{msg.content}</p>
<span>{msg.time}</span>
</div>
))
)}
<input value={content} onChange={(e) => setContent(e.target.value)} onKeyDown={onKeyDown} />
</div>
)
};
32 changes: 32 additions & 0 deletions client/src/data/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";

const apiUrl = import.meta.env.VITE_API_URL || "";

export type Message = {
id: string;
content: string;
time: number;
}

export const useMessages = (roomId: string) =>
useQuery({
queryKey: [`messages/${roomId}`],
queryFn: () => fetch(`${apiUrl}/messages/${roomId}`).then((res) => res.json().then(msg => msg as Message[])),
});

export const usePostMessage = (roomId: string, content?: string, accessToken?: string) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => fetch(`${apiUrl}/messages`, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ content, roomId }),
}).then((res) => res.json()),
onSuccess: (message) => {
queryClient.invalidateQueries([`messages/${roomId}`]);
}
});
}
11 changes: 11 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
version: '3.8'
services:
dynamodb-local:
command: "-jar DynamoDBLocal.jar -sharedDb -dbPath ./data"
image: "amazon/dynamodb-local:latest"
container_name: dynamodb-local
ports:
- "8000:8000"
volumes:
- "./docker/dynamodb:/home/dynamodblocal/data"
working_dir: /home/dynamodblocal
104 changes: 72 additions & 32 deletions pulumi/app/aws/apply-stack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,14 @@ function createResources(config: StackConfig, shared: SharedResources) {
],
});

const role = new aws.iam.Role(config.appName, {
const taskExecutionRole = new aws.iam.Role(`${config.appName}-exec-role`, {
assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({
Service: "ecs-tasks.amazonaws.com",
}),
});

new aws.iam.RolePolicy(`${config.appName}-get-params`, {
role: role.name,
role: taskExecutionRole.name,
policy: {
Version: "2012-10-17",
Statement: [
Expand All @@ -80,12 +80,37 @@ function createResources(config: StackConfig, shared: SharedResources) {
},
});

new aws.iam.RolePolicyAttachment(config.appName, {
role: role.name,
new aws.iam.RolePolicyAttachment(`${config.appName}-exec-role-attachment`, {
role: taskExecutionRole.name,
policyArn:
"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
});

const taskRole = new aws.iam.Role(`${config.appName}-role`, {
assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({
Service: "ecs-tasks.amazonaws.com",
}),
});

new aws.iam.RolePolicy(`${config.appName}-dynamo-db`, {
role: taskRole.name,
policy: {
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Action: ["dynamodb:*"],
Resource: ["*"],
},
],
},
});

new aws.iam.RolePolicyAttachment(`${config.appName}-role-attachment`, {
role: taskRole.name,
policyArn: aws.iam.ManagedPolicy.AmazonECSFullAccess,
});

const webLogGroup = new aws.cloudwatch.LogGroup(
`/ecs/${config.appName}`,
{
Expand Down Expand Up @@ -119,36 +144,51 @@ function createResources(config: StackConfig, shared: SharedResources) {
],
});

pulumi.all([webLogGroup.name, role.arn]).apply(([logGroupName, roleArn]) => {
const taskDefinitionSpec = getTaskDefinitionSpec(
config,
roleArn,
logGroupName
);
const taskDefinition = new aws.ecs.TaskDefinition(
config.appName,
taskDefinitionSpec
);

new aws.ecs.Service(config.appName, {
cluster: cluster.arn,
desiredCount: 1,
launchType: "FARGATE",
taskDefinition: taskDefinition.arn,
networkConfiguration: {
assignPublicIp: true,
subnets: subnets.ids,
securityGroups: [securityGroup.id],
},
loadBalancers: [
{
targetGroupArn: targetGroup.arn,
containerName: "auth0-test-api",
containerPort: 8080,
const table = new aws.dynamodb.Table(config.appName, {
attributes: [
{ name: "Id", type: "S" },
{ name: "Sort", type: "S" },
],
hashKey: "Id",
rangeKey: "Sort",
readCapacity: 1,
writeCapacity: 1,
});

pulumi
.all([webLogGroup.name, taskExecutionRole.arn, taskRole.arn, table.name])
.apply(([logGroupName, executionRoleArn, taskRoleArn, tableName]) => {
const taskDefinitionSpec = getTaskDefinitionSpec({
config,
executionRoleArn,
taskRoleArn,
logGroupName,
tableName,
});
const taskDefinition = new aws.ecs.TaskDefinition(
config.appName,
taskDefinitionSpec
);

new aws.ecs.Service(config.appName, {
cluster: cluster.arn,
desiredCount: 1,
launchType: "FARGATE",
taskDefinition: taskDefinition.arn,
networkConfiguration: {
assignPublicIp: true,
subnets: subnets.ids,
securityGroups: [securityGroup.id],
},
],
loadBalancers: [
{
targetGroupArn: targetGroup.arn,
containerName: "auth0-test-api",
containerPort: 8080,
},
],
});
});
});

const lb = aws.lb.getLoadBalancerOutput({ arn: shared.loadBalancer.arn });

Expand Down
15 changes: 11 additions & 4 deletions pulumi/app/aws/get-app-spec.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,21 @@ describe("getTaskDefinitionSpec", () => {
publicUrl: "https://auth0-test-test.dev.jbrunton-aws.com",
rootDomain: "jbrunton-aws.com",
};
const spec = getTaskDefinitionSpec(
const spec = getTaskDefinitionSpec({
config,
"executionRoleArn",
"/ecs/auth0-test-logs"
);
executionRoleArn: "executionRoleArn",
taskRoleArn: "taskRoleArn",
logGroupName: "/ecs/auth0-test-logs",
tableName: "MyTable",
});
expect(spec).toEqual({
family: "auth0-test-test",
cpu: "256",
memory: "512",
networkMode: "awsvpc",
requiresCompatibilities: ["FARGATE"],
executionRoleArn: "executionRoleArn",
taskRoleArn: "taskRoleArn",
containerDefinitions: JSON.stringify([
{
name: "auth0-test-api",
Expand Down Expand Up @@ -53,6 +56,10 @@ describe("getTaskDefinitionSpec", () => {
name: "TAG",
value: "latest",
},
{
name: "DB_TABLE_NAME",
value: "MyTable",
},
],
secrets: [],
logConfiguration: {
Expand Down
25 changes: 20 additions & 5 deletions pulumi/app/aws/get-app-spec.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
import * as aws from "@pulumi/aws";
import { StackConfig } from "./usecases/stack/get-stack-config";

export const getTaskDefinitionSpec = (
config: StackConfig,
executionRoleArn: string,
logGroupName: string
): aws.ecs.TaskDefinitionArgs => {
export type GetTaskDefinitionSpecParams = {
config: StackConfig;
executionRoleArn: string;
taskRoleArn: string;
logGroupName: string;
tableName: string;
};

export const getTaskDefinitionSpec = ({
config,
executionRoleArn,
taskRoleArn,
logGroupName,
tableName,
}: GetTaskDefinitionSpecParams): aws.ecs.TaskDefinitionArgs => {
return {
family: config.appName,
cpu: "256",
memory: "512",
networkMode: "awsvpc",
requiresCompatibilities: ["FARGATE"],
taskRoleArn,
executionRoleArn,
containerDefinitions: JSON.stringify([
{
Expand Down Expand Up @@ -41,6 +52,10 @@ export const getTaskDefinitionSpec = (
name: "TAG",
value: config.tag,
},
{
name: "DB_TABLE_NAME",
value: tableName,
},
],
secrets: [],
logConfiguration: {
Expand Down
Loading

0 comments on commit 7b1bc31

Please sign in to comment.