Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rename nextjs-site to food ordering and refactor #2

Merged
merged 4 commits into from
Mar 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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