Skip to content

Commit

Permalink
rename nextjs-site to food ordering and refactor (#2)
Browse files Browse the repository at this point in the history
Co-authored-by: Sam Sussman <sussmansa@gmail.com>
  • Loading branch information
sam and thantos authored Mar 1, 2023
1 parent 5cd5b0b commit f1f8eda
Show file tree
Hide file tree
Showing 87 changed files with 2,396 additions and 1,484 deletions.
File renamed without changes.
File renamed without changes.
3 changes: 3 additions & 0 deletions examples/food-ordering/apps/frontend/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
NEXT_PUBLIC_EVENTUAL_API_DOMAIN=https://gecml2jl79.execute-api.us-east-1.amazonaws.com/
NEXT_PUBLIC_USER_POOL_ID=us-east-1_Pl1wNTMfV
NEXT_PUBLIC_USER_POLL_CLIENT_ID=a4f5eusm699it63gfa5r2g7lp
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@
"dependencies": {
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@eventual/client": "^0.21.0",
"@food-ordering/core": "workspace:^",
"@food-ordering/service": "workspace:^",
"@mui/icons-material": "^5.11.0",
"@mui/material": "^5.11.7",
"@mui/lab": "^5.0.0-alpha.118",
"@nextjs-site/core": "workspace:^",
"@mui/material": "^5.11.7",
"@next/font": "13.1.6",
"@types/node": "^16",
"@types/react": "18.0.27",
"@types/react-dom": "18.0.10",
"@typescript-eslint/eslint-plugin": "^5.52.0",
"amazon-cognito-identity-js": "^6.1.2",
"eslint": "8.33.0",
"eslint-config-next": "13.1.6",
Expand All @@ -28,4 +31,4 @@
"react-dom": "18.2.0",
"typescript": "^4.9.4"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Head from "next/head";
import { Inter } from "@next/font/google";
import styles from "@/styles/Home.module.css";
import { createTheme, ThemeProvider } from "@mui/material/styles";
import { Inter } from "@next/font/google";
import Head from "next/head";
import React from "react";
import { _Menu } from "./menu";
import { createTheme, ThemeProvider } from "@mui/material/styles";

const inter = Inter({ subsets: ["latin"] });

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import DirectionsCar from "@mui/icons-material/DirectionsCar";
import MenuIcon from "@mui/icons-material/Menu";
import {
AppBar,
Expand All @@ -12,10 +13,9 @@ import {
Typography,
} from "@mui/material";
import Menu from "@mui/material/Menu";
import { useContext, useState } from "react";
import DirectionsCar from "@mui/icons-material/DirectionsCar";
import Link from "next/link";
import { useRouter } from "next/router";
import { useState } from "react";
import { signOut } from "./auth";
import useUser from "./use-user";

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { overrideApiDomain } from "@/api";
import { overrideUserPoolIds } from "@/auth";
import "@/styles/globals.css";
import { UserProvider } from "@/use-user";
Expand All @@ -22,9 +21,11 @@ _App.getInitialProps = async (context: AppContext) => {
await fetch("/env_override.json")
).json();

if (overrides.eventualApi) {
overrideApiDomain(overrides.eventualApi);
}
// why do we need this???

// if (overrides.eventualApi) {
// overrideApiDomain(overrides.eventualApi);
// }
if (overrides.userPoolId || overrides.userPoolClientId) {
overrideUserPoolIds(overrides.userPoolId, overrides.userPoolClientId);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
import { startOrder } from "@/api";
import Layout from "@/layout";
import { useService } from "@/use-service";
import useUser from "@/use-user";
import { Box, Button } from "@mui/material";
import { useRouter } from "next/router";
import { useCallback, useContext } from "react";
import { useCallback } from "react";

export default function Home() {
const router = useRouter();
const { session } = useUser({ redirectTo: "/login" });
const start = useCallback(() => {
const service = useService(session);

const start = useCallback(async () => {
if (session) {
startOrder(
{
address: "somewhere",
items: [],
store: "there",
},
session.getAccessToken().getJwtToken()
).then(({ orderId }) => {
router.push("/track?orderId=" + orderId);
const order = await service.createOrder({
address: "somewhere",
items: [],
store: "there",
});
router.push("/track?orderId=" + order.id);
}
}, [session, router]);
}, [session, service, router]);

return (
<Layout>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { useContext, useEffect, useState } from "react";
import { Order } from "@nextjs-site/core";
import { useEffect, useState } from "react";
import { Order } from "@food-ordering/core";
import Layout from "@/layout";
import { getOrders } from "@/api";
import Stack from "@mui/material/Stack";
import { styled } from "@mui/material/styles";
import Paper from "@mui/material/Paper";
import { Grid } from "@mui/material";
import Link from "next/link";
import useUser from "@/use-user";
import { useService } from "@/use-service";

const Item = styled(Paper)(({ theme }) => ({
backgroundColor: "#1A2027",
Expand All @@ -19,11 +19,13 @@ const Item = styled(Paper)(({ theme }) => ({
export default function Home() {
const [orders, setOrders] = useState<Order[]>([]);
const { session } = useUser({ redirectTo: "/login" });
const service = useService(session);

useEffect(() => {
if (session) {
getOrders(session.getAccessToken().getJwtToken()).then((orders) =>
service.listOrders(undefined).then((orders) =>
setOrders(orders)
);
)
}
}, [session]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Layout from "@/layout";
import useUser from "@/use-user";
import { Box, TextField, Button } from "@mui/material";
import { useRouter } from "next/router";
import { ChangeEvent, useCallback, useContext, useState } from "react";
import { ChangeEvent, useCallback, useState } from "react";

export default function Home() {
const router = useRouter();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { getOrder } from "@/api";
import Layout from "@/layout";
import { useService } from "@/use-service";
import useUser from "@/use-user";
import { useInterval } from "@/utils";
import { Order, OrderStatus } from "@food-ordering/core";
import Check from "@mui/icons-material/Check";
import DirectionsCar from "@mui/icons-material/DirectionsCar";
import DoneAll from "@mui/icons-material/DoneAll";
Expand All @@ -18,7 +19,6 @@ import {
TimelineSeparator,
} from "@mui/lab";
import { Grid, Stack } from "@mui/material";
import { Order, OrderStatus } from "@nextjs-site/core";
import { useRouter } from "next/router";
import React, { useCallback, useEffect, useState } from "react";

Expand All @@ -29,14 +29,12 @@ export default function Track() {
const [pollDelay, setPollDelay] = useState<null | number>(1000);
const [order, setOrder] = useState<Order | undefined>(undefined);
const { session } = useUser({ redirectTo: "/login" });

const get = useCallback(() => {
const service = useService(session);
const get = useCallback(async () => {
if (session && orderId && typeof orderId === "string") {
getOrder(orderId, session.getAccessToken().getJwtToken()).then((order) =>
setOrder(order)
);
setOrder(await service.getOrder(orderId));
}
}, [orderId, session]);
}, [orderId, session, service]);

useInterval(get, pollDelay);

Expand Down
20 changes: 20 additions & 0 deletions examples/food-ordering/apps/frontend/src/use-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ServiceClient } from "@eventual/client";
import * as FoodOrderingService from "@food-ordering/service";
import type { CognitoUserSession } from "amazon-cognito-identity-js";

let apiDomain: string | undefined = process.env.NEXT_PUBLIC_EVENTUAL_API_DOMAIN;

export function useService(session: CognitoUserSession | null | undefined) {
return new ServiceClient<typeof FoodOrderingService>({
serviceUrl: apiDomain!,
beforeRequest: async (request) => {
if (session) {
request.headers ??= {};
request.headers.Authorization = `Basic ${session
.getAccessToken()
.getJwtToken()}`;
}
return request;
},
});
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CognitoUser, CognitoUserSession } from "amazon-cognito-identity-js";
import { useRouter } from "next/router";
import React, { useState, useEffect, createContext, useContext } from "react";
import React, { createContext, useContext, useEffect, useState } from "react";
import { currentUser, getSession, refreshSession } from "./auth";

export const UserContext = createContext<{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,6 @@
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
"exclude": ["node_modules"],
"references": [{ "path": "../service" }]
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@nextjs-site/service",
"name": "@food-ordering/service",
"type": "module",
"main": "lib/index.js",
"module": "lib/index.js",
Expand All @@ -9,16 +9,16 @@
"test": "jest --passWithNoTests"
},
"dependencies": {
"@eventual/core": "^0.15.2",
"@nextjs-site/core": "workspace:^"
"@eventual/core": "^0.21.0",
"@food-ordering/core": "workspace:^"
},
"devDependencies": {
"@aws-sdk/client-cognito-identity-provider": "^3.262.0",
"@aws-sdk/client-dynamodb": "^3.262.0",
"@aws-sdk/lib-dynamodb": "^3.262.0",
"@aws-sdk/smithy-client": "^3.261.0",
"@aws-sdk/types": "^3.257.0",
"@eventual/testing": "^0.15.2",
"@eventual/testing": "^0.21.0",
"esbuild": "^0.16.14",
"jest": "^29",
"ts-jest": "^29",
Expand Down
44 changes: 44 additions & 0 deletions examples/food-ordering/apps/service/src/api/create-order.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { PutCommand } from "@aws-sdk/lib-dynamodb";
import { CreateOrderRequest, Order } from "@food-ordering/core";
import { ulid } from "ulidx";
import { dynamo } from "../util/clients.js";
import { OrderRecord } from "../util/order-record.js";
import { TABLE_NAME } from "../util/variables.js";
import { processOrder } from "../workflows/process-order.js";
import { privateAccess } from "./middleware/default.js";

export const createOrder = privateAccess.command(
"createOrder",
async (orderRequest: CreateOrderRequest, { user }) => {
const order: Order = {
...orderRequest,
userId: user.username,
id: ulid(),
status: "CREATED",
timestamp: new Date().toISOString(),
};

const orderRecord: OrderRecord = {
...order,
pk: OrderRecord.partitionKey,
sk: OrderRecord.sortKey(order.id),
// the sort key we will use to order user records by timestamp
user_time: OrderRecord.userTime(order.userId, order.timestamp),
};

await dynamo.send(
new PutCommand({
Item: orderRecord,
TableName: TABLE_NAME,
ConditionExpression: "attribute_not_exists(sk)",
})
);

await processOrder.startExecution({
input: { orderId: orderRecord.id },
executionName: orderRecord.id,
});

return order;
}
);
47 changes: 47 additions & 0 deletions examples/food-ordering/apps/service/src/api/get-order.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { GetCommand } from "@aws-sdk/lib-dynamodb";
import { HttpError } from "@eventual/core";
import type { Order } from "@food-ordering/core";
import { dynamo } from "../util/clients.js";
import { orderFromRecord, OrderRecord } from "../util/order-record.js";
import { TABLE_NAME } from "../util/variables.js";
import { privateAccess } from "./middleware/default.js";
/**
* Gets the record for an {@link Order} by its ID.
*
* Users must be logged in and the owner of the {@link Order} record.
*/
export const getOrder = privateAccess.command(
"getOrder",
async (orderId: string, { user }) => {
const order = await getOrderRecord(orderId);
if (order === undefined) {
return undefined;
} else if (order?.userId !== user.username) {
throw new HttpError({
code: 401,
message: `User does not have permissions to view order ${orderId}`,
});
} else {
return order;
}
}
);

// this is annoyingly redundant - we want to use it in a workflow
// and therefore have to pull it out separate;y
// would be better to just call the command from the workflow
// will impact how we design middleware
export async function getOrderRecord(orderId: string) {
const item = await dynamo.send(
new GetCommand({
Key: {
pk: OrderRecord.partitionKey,
sk: OrderRecord.sortKey(orderId),
},
TableName: TABLE_NAME,
ConsistentRead: true,
})
);

return item.Item ? orderFromRecord(item.Item as OrderRecord) : undefined;
}
5 changes: 5 additions & 0 deletions examples/food-ordering/apps/service/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from "./create-order.js";
export * from "./get-order.js";
export * from "./list-orders.js";
export * from "./middleware/auth.js";
export * from "./update-order-status.js";
27 changes: 27 additions & 0 deletions examples/food-ordering/apps/service/src/api/list-orders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { QueryCommand } from "@aws-sdk/lib-dynamodb";
import { dynamo } from "../util/clients.js";
import { orderFromRecord, OrderRecord } from "../util/order-record.js";
import { TABLE_NAME } from "../util/variables.js";
import { privateAccess } from "./middleware/default.js";

export const listOrders = privateAccess.command(
"listOrders",
async (_: any, { user }) => {
const { Items } = await dynamo.send(
new QueryCommand({
TableName: TABLE_NAME,
IndexName: OrderRecord.userTimeIndex,
KeyConditionExpression: "pk=:pk and begins_with(user_time, :userId)",
// newest first
ScanIndexForward: false,
ExpressionAttributeValues: {
":userId": user.username,
":pk": OrderRecord.partitionKey,
},
ConsistentRead: true,
})
);

return (Items as OrderRecord[]).map(orderFromRecord) ?? {};
}
);
Loading

0 comments on commit f1f8eda

Please sign in to comment.