Skip to content

Commit 5e5ced5

Browse files
committed
Finished CRUD examples
1 parent 5f5edb6 commit 5e5ced5

File tree

4 files changed

+178
-2
lines changed

4 files changed

+178
-2
lines changed

samples/from_crud_to_eventsourcing/src/core/validation.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const enum ValidationErrors {
77
NOT_A_STRING_OR_UNDEFINED = 'NOT_A_STRING_OR_UNDEFINED',
88
NOT_A_POSITIVE_NUMBER = 'NOT_A_POSITIVE_NUMBER',
99
NOT_AN_UNSIGNED_BIGINT = 'NOT_AN_UNSIGNED_BIGINT',
10+
NOT_AN_ARRAY = 'NOT_AN_ARRAY',
1011
}
1112

1213
export const assertNotEmptyString = (value: any): string => {
@@ -37,3 +38,10 @@ export const assertUnsignedBigInt = (value: string): bigint => {
3738
}
3839
return number;
3940
};
41+
42+
export const assertArray = (value: any): [] => {
43+
if (!Array.isArray(value)) {
44+
throw ValidationErrors.NOT_AN_ARRAY;
45+
}
46+
return value as [];
47+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { startAPI } from '#core/api';
2+
import { getPostgres } from '#core/postgres';
3+
import { router } from './shoppingCarts/routes';
4+
5+
//////////////////////////////////////////////////////////
6+
/// Make sure that we dispose Postgres connection pool
7+
//////////////////////////////////////////////////////////
8+
9+
process.once('SIGTERM', () => {
10+
const db = getPostgres();
11+
12+
db.dispose().catch((ex) => {
13+
console.error(ex);
14+
});
15+
});
16+
17+
//////////////////////////////////////////////////////////
18+
/// API
19+
//////////////////////////////////////////////////////////
20+
21+
startAPI(router);
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import { getPostgres } from '#core/postgres';
2+
import {
3+
assertArray,
4+
assertNotEmptyString,
5+
assertPositiveNumber,
6+
assertStringOrUndefined,
7+
} from '#core/validation';
8+
import { NextFunction, Request, Response, Router } from 'express';
9+
import { cartItems, carts } from '../db';
10+
11+
//////////////////////////////////////
12+
/// Routes
13+
//////////////////////////////////////
14+
15+
export const router = Router();
16+
17+
// Open Shopping cart
18+
router.post(
19+
'/v1/shopping-carts/:sessionId',
20+
async (request: Request, response: Response, next: NextFunction) => {
21+
try {
22+
const sessionId = assertNotEmptyString(request.params.sessionId);
23+
24+
const shoppingCarts = carts(getPostgres());
25+
const shoppingCartItems = cartItems(getPostgres());
26+
27+
const { items, ...cart } = getShoppingCart(request);
28+
29+
let resultCarts = await shoppingCarts.insertOrIgnore({
30+
...cart,
31+
sessionId,
32+
createdAt: new Date(),
33+
});
34+
35+
if (resultCarts.length === 0) {
36+
await shoppingCarts.update(
37+
{ sessionId },
38+
{
39+
...cart,
40+
updatedAt: new Date(),
41+
}
42+
);
43+
}
44+
45+
const cartId = resultCarts[0].id;
46+
47+
// delete and recreate all cart items
48+
shoppingCartItems.delete({ cartId: resultCarts[0].id });
49+
50+
await shoppingCartItems.bulkInsert({
51+
columnsToInsert: [
52+
'cartId',
53+
'content',
54+
'createdAt',
55+
'discount',
56+
'price',
57+
'productId',
58+
'quantity',
59+
'sku',
60+
'updatedAt',
61+
],
62+
records: items.map((item) => {
63+
return {
64+
...item,
65+
cartId,
66+
createdAt: new Date(),
67+
updatedAt: new Date(),
68+
};
69+
}),
70+
});
71+
72+
response.status(200);
73+
} catch (error) {
74+
next(error);
75+
}
76+
}
77+
);
78+
79+
const getShoppingCart = (request: Request) => {
80+
return {
81+
city: assertStringOrUndefined(request.body.city),
82+
country: assertStringOrUndefined(request.body.country) ?? null,
83+
content: assertStringOrUndefined(request.body.content) ?? null,
84+
email: assertStringOrUndefined(request.body.email) ?? null,
85+
firstName: assertStringOrUndefined(request.body.lastName) ?? null,
86+
lastName: assertStringOrUndefined(request.body.firstName) ?? null,
87+
middleName: assertStringOrUndefined(request.body.middleName) ?? null,
88+
line1: assertStringOrUndefined(request.body.line1) ?? null,
89+
line2: assertStringOrUndefined(request.body.line2) ?? null,
90+
mobile: assertStringOrUndefined(request.body.mobile) ?? null,
91+
province: assertStringOrUndefined(request.body.province) ?? null,
92+
status: assertPositiveNumber(request.body.status),
93+
userId: assertPositiveNumber(request.body.userId),
94+
items: assertArray(request.body.items).map((item: any) => {
95+
return {
96+
content: assertStringOrUndefined(item.content) ?? null,
97+
discount: assertPositiveNumber(item.discount),
98+
productId: assertPositiveNumber(item.productId),
99+
price: assertPositiveNumber(item.price),
100+
quantity: assertPositiveNumber(item.quantity),
101+
sku: assertNotEmptyString(item.sku),
102+
};
103+
}),
104+
};
105+
};
106+
107+
router.get(
108+
'/v1/shopping-carts/:shoppingCartId',
109+
async (request: Request, response: Response, next: NextFunction) => {
110+
try {
111+
const shoppingCarts = carts(getPostgres());
112+
const shoppingCartItems = cartItems(getPostgres());
113+
114+
const result = await shoppingCarts.findOne({
115+
sessionId: assertNotEmptyString(request.params.shoppingCartId),
116+
});
117+
118+
if (result === null) {
119+
response.sendStatus(404);
120+
return;
121+
}
122+
123+
const items = await shoppingCartItems
124+
.find({
125+
cartId: result.id,
126+
})
127+
.all();
128+
129+
response.send({
130+
...result,
131+
items,
132+
});
133+
} catch (error) {
134+
next(error);
135+
}
136+
}
137+
);

samples/from_crud_to_eventsourcing/src/eventsourced/shoppingCarts/routes.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { create, update } from '#eventsourced/core/commandHandling';
1313
import { getEventStore } from '#eventsourced/core/streams';
1414
import { NextFunction, Request, Response, Router } from 'express';
1515
import { v4 as uuid } from 'uuid';
16-
import { carts } from '../db';
16+
import { cartItems, carts } from '../db';
1717
import { getPricedProductItem } from './productItem';
1818
import {
1919
AddProductItemToShoppingCart,
@@ -168,6 +168,7 @@ router.get(
168168
async (request: Request, response: Response, next: NextFunction) => {
169169
try {
170170
const shoppingCarts = carts(getPostgres());
171+
const shoppingCartItems = cartItems(getPostgres());
171172

172173
const result = await shoppingCarts.findOne({
173174
sessionId: assertNotEmptyString(request.params.shoppingCartId),
@@ -178,8 +179,17 @@ router.get(
178179
return;
179180
}
180181

182+
const items = await shoppingCartItems
183+
.find({
184+
cartId: result.id,
185+
})
186+
.all();
187+
181188
response.set('ETag', toWeakETag(result.revision));
182-
response.send(result);
189+
response.send({
190+
...result,
191+
items,
192+
});
183193
} catch (error) {
184194
next(error);
185195
}

0 commit comments

Comments
 (0)