Skip to content

Commit

Permalink
split products & orders endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
Facundo De Lorenzo committed May 20, 2024
1 parent d522ae0 commit 19d0e0f
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 72 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,12 @@ Tests for the backend were not implemented.
If they were to be implemented, I'd use jest.
I'd write a test for every lambda handler, so as to check that they can handle recieving wrong parameters, and also to ensure that they get their job done as it's meant to be, which is important to keep the business rules working rightfully.

[Outdated]
For the db i was on my way to implement dynamo but run out of time
(Friday 6pm)

I'll try to make it work for monday :)

[new]
Dynamo db added to store orders,
Api allows to list and finish orders but UI wasn't developed.
3 changes: 2 additions & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
},
"dependencies": {
"@aws-sdk/client-dynamodb": "^3.577.0",
"@aws-sdk/lib-dynamodb": "^3.577.0"
"@aws-sdk/lib-dynamodb": "^3.577.0",
"uuid": "^9.0.1"
}
}
194 changes: 194 additions & 0 deletions packages/api/src/orders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
import { APIGatewayEvent, Callback, Context } from 'aws-lambda';
import { CreateOrderRequest, FinishOrderRequest, Order, OrderDTO } from './types';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import {
DynamoDBDocumentClient,
PutCommand,
PutCommandOutput,
QueryCommand,
QueryCommandOutput,
UpdateCommand,
UpdateCommandOutput,
} from '@aws-sdk/lib-dynamodb';
import { v4 as uuid } from 'uuid';

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);

const responseHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Credentials': true,
'Access-Control-Allow-Methods': 'OPTIONS, GET, POST',
};

exports.get = async (
event: APIGatewayEvent,
context: Context,
callback: Callback,
) => {
try {
const req = event.queryStringParameters;
const restaurantId = parseInt(req.restaurantId);
if (!restaurantId)
return {
isBase64Encoded: false,
statusCode: 400,
headers: responseHeaders,
body: 'restaurantId must be provided',
};

const command = new QueryCommand({
TableName: 'wakeup-challenge-orders',
ExpressionAttributeValues: {
':id': restaurantId,
':status': 'active',
},
KeyConditionExpression: 'restaurantId = :id',
FilterExpression: 'orderStatus = :status',
});
const response = await docClient.send(command) as Omit<QueryCommandOutput, "Items"> & { Items: Order[] };

const orders: OrderDTO[] = response.Items.map((x) => ({
id: x.orderId,
totalPrice: x.totalPrice,
products: x.products,
}));

return {
isBase64Encoded: false,
statusCode: 200,
headers: responseHeaders,
body: JSON.stringify({ orders }),
};
} catch (error) {
return {
isBase64Encoded: false,
statusCode: 500,
headers: responseHeaders,
body: error.message,
};
}
};

exports.create = async (
event: APIGatewayEvent,
context: Context,
callback: Callback,
) => {
try {
const req: CreateOrderRequest = JSON.parse(event.body);

if (!req?.products)
return {
isBase64Encoded: false,
statusCode: 400,
headers: responseHeaders,
body: 'products must be provided',
};
if (!req?.restaurantId)
return {
isBase64Encoded: false,
statusCode: 400,
headers: responseHeaders,
body: 'restaurantId must be provided',
};

const orderId = uuid();
const totalPrice = req.products.reduce(
(acc, val) => acc + val.product.price,
0,
);
const command = new PutCommand({
TableName: 'wakeup-challenge-orders',
Item: {
restaurantId: req.restaurantId,
orderId,
products: req.products,
totalPrice,
orderStatus: 'active',
},
});
await docClient.send(command) as Omit<PutCommandOutput, "Item"> & { Item: Order };

const order: OrderDTO = {
id: orderId,
products: req.products,
totalPrice,
};
return {
isBase64Encoded: false,
statusCode: 200,
headers: responseHeaders,
body: JSON.stringify(order),
};
} catch (error) {
return {
isBase64Encoded: false,
statusCode: 500,
headers: responseHeaders,
body: error.message,
};
}
};

exports.finish = async (
event: APIGatewayEvent,
context: Context,
callback: Callback,
) => {
try {
const req: FinishOrderRequest = JSON.parse(event.body);

if (!req.orderId)
return {
isBase64Encoded: false,
statusCode: 400,
headers: responseHeaders,
body: 'orderId must be provided',
};
if (!req.restaurantId)
return {
isBase64Encoded: false,
statusCode: 400,
headers: responseHeaders,
body: 'restaurantId must be provided',
};

const command = new UpdateCommand({
TableName: 'wakeup-challenge-orders',
Key: {
restaurantId: req.restaurantId,
},
UpdateExpression: 'set orderStatus = :orderStatus',
ConditionExpression: 'orderId = :orderId',
ExpressionAttributeValues: {
':orderStatus': 'finished',
':orderId': req.orderId,
},
ReturnValues: 'ALL_NEW',
});
const response = await docClient.send(command) as Omit<UpdateCommandOutput, "Item"> & { Item: Order };
console.log('response: ', response);

const updatedItem = response.Attributes;
const updatedOrder: OrderDTO = {
id: updatedItem.orderId,
products: updatedItem.products,
totalPrice: updatedItem.totalPrice,
};

return {
isBase64Encoded: false,
statusCode: 200,
headers: responseHeaders,
body: JSON.stringify(updatedOrder),
};
} catch (error) {
return {
isBase64Encoded: false,
statusCode: 500,
headers: responseHeaders,
body: error.message,
};
}
};
66 changes: 2 additions & 64 deletions packages/api/src/products.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { APIGatewayEvent, Callback, Context } from 'aws-lambda';
import { CreateOrderRequest, Order, Product } from './types';
import { ProductDTO } from './types';
import {
BatchWriteItemCommand,
DynamoDBClient,
WriteRequest,
} from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, QueryCommand } from '@aws-sdk/lib-dynamodb';

Expand Down Expand Up @@ -33,9 +31,7 @@ exports.get = async (
};

const lastKey = req.lastKey ? JSON.parse(req.lastKey) : undefined;

const limit = req.limit ? Number(req.limit) : 5;
const offset = req.offset ? Number(req.offset) : 0;

const command = new QueryCommand({
ExpressionAttributeValues: {
Expand All @@ -49,35 +45,13 @@ exports.get = async (
const response = await docClient.send(command);
console.log('response: ', response);

const productsDB: Product[] = response.Items.map((x) => ({
const products: ProductDTO[] = response.Items.map((x) => ({
id: x.productId,
name: x.name,
price: x.price,
}));
const newLastKey = response.LastEvaluatedKey;

// const productsDB: Product[] = [
// { id: 1, name: 'Kuva Roast Rib Eye', price: 418 },
// { id: 2, name: 'Guadalupe Half Rack', price: 298 },
// { id: 3, name: 'Tohono Chicken', price: 308 },
// { id: 4, name: 'Milanesa napolitana', price: 500 },
// { id: 5, name: 'Asado', price: 800 },
// { id: 6, name: 'Choripan', price: 230 },
// { id: 7, name: 'Asado x4', price: 2700 },
// { id: 8, name: 'Kuva Roast Rib Eye', price: 418 },
// { id: 9, name: 'Guadalupe Half Rack', price: 298 },
// { id: 10, name: 'Tohono Chicken', price: 308 },
// { id: 11, name: 'Milanesa napolitana', price: 500 },
// { id: 12, name: 'Asado', price: 800 },
// { id: 13, name: 'Choripan', price: 230 },
// { id: 14, name: 'Asado x45', price: 2700 },
// { id: 15, name: 'Asado', price: 800 },
// { id: 16, name: 'Choripan', price: 230 },
// { id: 17, name: 'Asado x455', price: 2700 },
// ];

const products = productsDB.slice(offset, offset + limit);

return {
isBase64Encoded: false,
statusCode: 200,
Expand All @@ -93,39 +67,3 @@ exports.get = async (
};
}
};

exports.createOrder = async (
event: APIGatewayEvent,
context: Context,
callback: Callback,
) => {
try {
const req: CreateOrderRequest = JSON.parse(event.body);

if (!req?.products)
return {
isBase64Encoded: false,
statusCode: 400,
headers: responseHeaders,
body: 'Products must be provided',
};
const order: Order = {
products: req.products,
totalPrice: req.products.reduce((acc, val) => acc + val.product.price, 0),
};

return {
isBase64Encoded: false,
statusCode: 200,
headers: responseHeaders,
body: JSON.stringify(order),
};
} catch (error) {
return {
isBase64Encoded: false,
statusCode: 500,
headers: responseHeaders,
body: error.message,
};
}
};
29 changes: 26 additions & 3 deletions packages/api/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
export interface CreateOrderRequest {
restaurantId: number;
products: OrderProduct[];
}

export interface FinishOrderRequest {
orderId: string;
restaurantId: number;
}
export interface OrderProduct {
product: Product;
product: ProductDTO;
amount: number;
}

export interface Product {
export interface ProductDTO {
id: number;
name: string;
price: number;
}

export interface Order {
export interface OrderDTO {
id: string;
products: OrderProduct[];
totalPrice: number;
}
Expand All @@ -21,3 +28,19 @@ export interface Restaurant {
id: number;
name: string;
}

//DB Types
export interface Order {
totalPrice: number;
orderStatus: string;
products: OrderProduct[];
orderId: string;
restaurantId: number;
}

export interface Product {
restaurantId: number;
productId: number;
name: string;
price: number;
}
Loading

0 comments on commit 19d0e0f

Please sign in to comment.