Skip to content

Commit 753f262

Browse files
committed
Added subscriptions and projection for shopping cart details
1 parent 807aa06 commit 753f262

File tree

6 files changed

+193
-24
lines changed

6 files changed

+193
-24
lines changed
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { getEventStore } from '#core/eventStore';
22
import { getSubscriptionToAllWithESDBCheckpointing } from '#core/eventStore/subscribing';
3+
import { projectToCurrentShoppingCartDetails } from './shoppingCarts/gettingById/projection';
34

45
export const getSubscription = () =>
5-
getSubscriptionToAllWithESDBCheckpointing(getEventStore(), []);
6+
getSubscriptionToAllWithESDBCheckpointing(getEventStore(), [
7+
projectToCurrentShoppingCartDetails,
8+
]);
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,19 @@
11
export * from './queryHandler';
22
export * from './route';
3+
4+
export type ProductItem = Readonly<{
5+
productId: string;
6+
quantity: number;
7+
}>;
8+
9+
export type CurrentShoppingCartDetails = Readonly<{
10+
shoppingCartId: string;
11+
clientId: string;
12+
status: string;
13+
productItems: ProductItem[];
14+
openedAt: Date;
15+
confirmedAt?: Date;
16+
revision: string;
17+
}>;
18+
19+
export const CURRENT_SHOPPING_CART_DETAILS = 'currentShoppingCartDetails';
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import { executeOnMongoDB } from '#core/mongoDB';
2+
import { assertUnreachable, Result, success } from '#core/primitives';
3+
import { Event, StreamEvent } from '#core/events';
4+
import {
5+
ProductItemAddedToShoppingCart,
6+
ProductItemRemovedFromShoppingCart,
7+
ShoppingCartConfirmed,
8+
ShoppingCartOpened,
9+
ShoppingCartStatus,
10+
} from '..';
11+
import { CurrentShoppingCartDetails, CURRENT_SHOPPING_CART_DETAILS } from '.';
12+
import { addProductItem, removeProductItem } from '../productItems';
13+
14+
export async function projectShoppingCartOpened(
15+
event: ShoppingCartOpened,
16+
streamRevision: bigint
17+
): Promise<Result<true>> {
18+
await executeOnMongoDB<CurrentShoppingCartDetails>(
19+
{ collectionName: CURRENT_SHOPPING_CART_DETAILS },
20+
async (collection) => {
21+
await collection.insertOne({
22+
shoppingCartId: event.data.shoppingCartId,
23+
clientId: event.data.clientId,
24+
status: ShoppingCartStatus.Opened.toString(),
25+
productItems: [],
26+
openedAt: event.data.openedAt,
27+
revision: streamRevision.toString(),
28+
});
29+
}
30+
);
31+
32+
return success(true);
33+
}
34+
35+
export async function projectProductItemAddedToShoppingCart(
36+
event: ProductItemAddedToShoppingCart,
37+
streamRevision: bigint
38+
): Promise<Result<true>> {
39+
await executeOnMongoDB<CurrentShoppingCartDetails>(
40+
{ collectionName: CURRENT_SHOPPING_CART_DETAILS },
41+
async (collection) => {
42+
const { productItems } = (await collection.findOne(
43+
{
44+
shoppingCartId: event.data.shoppingCartId,
45+
},
46+
{ projection: { productItems: 1 } }
47+
))!;
48+
49+
await collection.findOneAndUpdate(
50+
{
51+
shoppingCartId: event.data.shoppingCartId,
52+
},
53+
{
54+
$set: {
55+
productItems: addProductItem(productItems, event.data.productItem),
56+
revision: streamRevision.toString(),
57+
},
58+
}
59+
);
60+
}
61+
);
62+
63+
return success(true);
64+
}
65+
66+
export async function projectProductItemRemovedFromShoppingCart(
67+
event: ProductItemRemovedFromShoppingCart,
68+
streamRevision: bigint
69+
): Promise<Result<true>> {
70+
await executeOnMongoDB<CurrentShoppingCartDetails>(
71+
{ collectionName: CURRENT_SHOPPING_CART_DETAILS },
72+
async (collection) => {
73+
const { productItems } = (await collection.findOne(
74+
{
75+
shoppingCartId: event.data.shoppingCartId,
76+
},
77+
{ projection: { productItems: 1 } }
78+
))!;
79+
80+
await collection.findOneAndUpdate(
81+
{
82+
shoppingCartId: event.data.shoppingCartId,
83+
},
84+
{
85+
$set: {
86+
productItems: removeProductItem(
87+
productItems,
88+
event.data.productItem
89+
),
90+
revision: streamRevision.toString(),
91+
},
92+
}
93+
);
94+
}
95+
);
96+
97+
return success(true);
98+
}
99+
100+
export async function projectShoppingCartConfirmed(
101+
event: ShoppingCartConfirmed,
102+
streamRevision: bigint
103+
): Promise<Result<true>> {
104+
await executeOnMongoDB<CurrentShoppingCartDetails>(
105+
{ collectionName: CURRENT_SHOPPING_CART_DETAILS },
106+
async (collection) => {
107+
await collection.updateOne(
108+
{
109+
shoppingCartId: event.data.shoppingCartId,
110+
},
111+
{
112+
$set: {
113+
confirmedAt: event.data.confirmedAt,
114+
status: ShoppingCartStatus.Confirmed.toString(),
115+
revision: streamRevision.toString(),
116+
},
117+
}
118+
);
119+
}
120+
);
121+
122+
return success(true);
123+
}
124+
125+
type CurrentShoppingCartDetailsEvent =
126+
| ShoppingCartOpened
127+
| ProductItemAddedToShoppingCart
128+
| ProductItemRemovedFromShoppingCart
129+
| ShoppingCartConfirmed;
130+
131+
function isCashierShoppingCartDetailsEvent(
132+
event: Event
133+
): event is CurrentShoppingCartDetailsEvent {
134+
const eventType = (event as CurrentShoppingCartDetailsEvent).type;
135+
136+
return (
137+
eventType === 'shopping-cart-opened' ||
138+
eventType === 'product-item-added-to-shopping-cart' ||
139+
eventType === 'product-item-removed-from-shopping-cart' ||
140+
eventType === 'shopping-cart-confirmed'
141+
);
142+
}
143+
144+
export async function projectToCurrentShoppingCartDetails(
145+
streamEvent: StreamEvent
146+
): Promise<Result<boolean>> {
147+
const { event, streamRevision } = streamEvent;
148+
149+
if (!isCashierShoppingCartDetailsEvent(event)) {
150+
return success(false);
151+
}
152+
153+
switch (event.type) {
154+
case 'shopping-cart-opened':
155+
return projectShoppingCartOpened(event, streamRevision);
156+
case 'product-item-added-to-shopping-cart':
157+
return projectProductItemAddedToShoppingCart(event, streamRevision);
158+
case 'product-item-removed-from-shopping-cart':
159+
return projectProductItemRemovedFromShoppingCart(event, streamRevision);
160+
case 'shopping-cart-confirmed':
161+
return projectShoppingCartConfirmed(event, streamRevision);
162+
default:
163+
assertUnreachable(event);
164+
}
165+
}

samples/optimisticConcurrency/src/shoppingCarts/gettingById/queryHandler.ts

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,39 +2,23 @@ import { Query } from '#core/queries';
22
import { failure, Result, success } from '#core/primitives';
33
import { getSingleFromMongoDB } from '#core/mongoDB';
44
import { SHIFT_DOES_NOT_EXIST } from '../shoppingCart';
5+
import { CurrentShoppingCartDetails, CURRENT_SHOPPING_CART_DETAILS } from '.';
56

67
export type GetCurrentShoppingCartDetails = Query<
78
'get-current-shopping-cart-details',
89
{
9-
cashRegisterId: string;
10+
shoppingCartId: string;
1011
}
1112
>;
1213

13-
export type ProductItem = Readonly<{
14-
productId: string;
15-
quantity: number;
16-
}>;
17-
18-
export type CurrentShoppingCartDetails = Readonly<{
19-
id: string;
20-
clientId: string;
21-
status: string;
22-
productItems: ProductItem[];
23-
openedAt: Date;
24-
confirmedAt?: Date;
25-
revision: string;
26-
}>;
27-
28-
const CurrentShoppingCartDetails = 'currentShoppingCartDetails';
29-
3014
export async function handleGetCurrentShoppingCartDetails(
3115
query: GetCurrentShoppingCartDetails
3216
): Promise<Result<CurrentShoppingCartDetails, SHIFT_DOES_NOT_EXIST>> {
3317
const result = await getSingleFromMongoDB<CurrentShoppingCartDetails>(
34-
CurrentShoppingCartDetails,
18+
CURRENT_SHOPPING_CART_DETAILS,
3519
(collection) =>
3620
collection.findOne({
37-
cashRegisterId: query.data.cashRegisterId,
21+
shoppingCartId: query.data.shoppingCartId,
3822
})
3923
);
4024

samples/optimisticConcurrency/src/shoppingCarts/gettingById/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ function mapRequestToQuery(
4949
return {
5050
type: 'get-current-shopping-cart-details',
5151
data: {
52-
cashRegisterId: request.params.cashRegisterId,
52+
shoppingCartId: request.params.shoppingCartId,
5353
},
5454
};
5555
}

samples/optimisticConcurrency/src/shoppingCarts/shoppingCart.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export type ShoppingCartEvent =
4141
| ShoppingCartConfirmed;
4242

4343
export enum ShoppingCartStatus {
44-
Pending = 1 << 0,
44+
Opened = 1 << 0,
4545
Confirmed = 1 << 1,
4646
Cancelled = 1 << 2,
4747

@@ -77,7 +77,7 @@ export function when(
7777
clientId: event.data.clientId,
7878
openedAt: event.data.openedAt,
7979
productItems: [],
80-
status: ShoppingCartStatus.Pending,
80+
status: ShoppingCartStatus.Opened,
8181
};
8282
case 'product-item-added-to-shopping-cart':
8383
return {

0 commit comments

Comments
 (0)