Skip to content

Commit 2bf7f96

Browse files
committed
Added Pongo projection for Guest Stay Details
1 parent 3dceeef commit 2bf7f96

File tree

5 files changed

+126
-16
lines changed

5 files changed

+126
-16
lines changed

src/guestStayAccounts/api/api.e2e.spec.ts

Lines changed: 83 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
formatDateToUtcYYYYMMDD,
3+
projections,
34
type EventStore,
45
} from '@event-driven-io/emmett';
56
import {
@@ -13,12 +14,18 @@ import {
1314
getPostgreSQLEventStore,
1415
type PostgresEventStore,
1516
} from '@event-driven-io/emmett-postgresql';
17+
import { pongoClient, type PongoClient } from '@event-driven-io/pongo';
1618
import {
1719
PostgreSqlContainer,
1820
type StartedPostgreSqlContainer,
1921
} from '@testcontainers/postgresql';
2022
import { randomUUID } from 'node:crypto';
2123
import { after, before, beforeEach, describe, it } from 'node:test';
24+
import { toGuestStayAccountId } from '../guestStayAccount';
25+
import {
26+
guestStayDetailsProjection,
27+
type GuestStayDetails,
28+
} from '../guestStayDetails';
2229
import { guestStayAccountsApi } from './api';
2330

2431
const doesGuestStayExist = (_guestId: string, _roomId: string, _day: Date) =>
@@ -35,11 +42,18 @@ void describe('guestStayAccount E2E', () => {
3542

3643
let postgres: StartedPostgreSqlContainer;
3744
let eventStore: PostgresEventStore;
45+
let readStore: PongoClient;
3846
let given: ApiE2ESpecification;
3947

4048
before(async () => {
4149
postgres = await new PostgreSqlContainer().start();
42-
eventStore = getPostgreSQLEventStore(postgres.getConnectionUri());
50+
51+
const connectionString = postgres.getConnectionUri();
52+
53+
eventStore = getPostgreSQLEventStore(connectionString, {
54+
projections: projections.inline([guestStayDetailsProjection]),
55+
});
56+
readStore = pongoClient(connectionString);
4357

4458
given = ApiE2ESpecification.for(
4559
(): EventStore => eventStore,
@@ -48,6 +62,7 @@ void describe('guestStayAccount E2E', () => {
4862
apis: [
4963
guestStayAccountsApi(
5064
eventStore,
65+
readStore.db(),
5166
doesGuestStayExist,
5267
(prefix) => `${prefix}-${transactionId}`,
5368
() => now,
@@ -59,6 +74,7 @@ void describe('guestStayAccount E2E', () => {
5974

6075
after(async () => {
6176
await eventStore.close();
77+
await readStore.close();
6278
await postgres.stop();
6379
});
6480

@@ -88,6 +104,8 @@ void describe('guestStayAccount E2E', () => {
88104
request.delete(
89105
`/guests/${guestId}/stays/${roomId}/periods/${formattedNow}`,
90106
);
107+
const getDetails: TestRequest = (request) =>
108+
request.get(`/guests/${guestId}/stays/${roomId}/periods/${formattedNow}`);
91109

92110
void describe('When not existing', () => {
93111
const notExistingAccount: TestRequest[] = [];
@@ -119,6 +137,11 @@ void describe('guestStayAccount E2E', () => {
119137
given(...notExistingAccount)
120138
.when(checkOut)
121139
.then([expectError(403)]));
140+
141+
void it(`details return 404`, () =>
142+
given(...notExistingAccount)
143+
.when(getDetails)
144+
.then([expectError(404)]));
122145
});
123146

124147
void describe('When checked in', () => {
@@ -144,6 +167,24 @@ void describe('guestStayAccount E2E', () => {
144167
.when(checkOut)
145168
.then([expectResponse(204)]));
146169

170+
void it(`details return checked in stay`, () =>
171+
given(checkedInAccount)
172+
.when(getDetails)
173+
.then([
174+
expectResponse<GuestStayDetails>(200, {
175+
body: {
176+
_id: toGuestStayAccountId(guestId, roomId, now),
177+
status: 'CheckedIn',
178+
balance: 0,
179+
roomId,
180+
guestId,
181+
transactions: [],
182+
transactionsCount: 0,
183+
checkedInAt: now,
184+
},
185+
}),
186+
]));
187+
147188
void describe('with unsettled balance', () => {
148189
const unsettledAccount: TestRequest[] = [checkIn, recordCharge];
149190

@@ -167,6 +208,24 @@ void describe('guestStayAccount E2E', () => {
167208
given(...unsettledAccount)
168209
.when(checkOut)
169210
.then([expectError(403)]));
211+
212+
void it(`details return checked in stay with charge`, () =>
213+
given(...unsettledAccount)
214+
.when(getDetails)
215+
.then([
216+
expectResponse(200, {
217+
body: {
218+
_id: toGuestStayAccountId(guestId, roomId, now),
219+
status: 'CheckedIn',
220+
balance: -amount,
221+
roomId,
222+
guestId,
223+
transactions: [{ amount }],
224+
transactionsCount: 1,
225+
checkedInAt: now,
226+
},
227+
}),
228+
]));
170229
});
171230

172231
void describe('with settled balance', () => {
@@ -190,6 +249,24 @@ void describe('guestStayAccount E2E', () => {
190249
given(...settledAccount)
191250
.when(checkOut)
192251
.then([expectResponse(204)]));
252+
253+
void it(`details return checked in stay with charge`, () =>
254+
given(...settledAccount)
255+
.when(getDetails)
256+
.then([
257+
expectResponse(200, {
258+
body: {
259+
_id: toGuestStayAccountId(guestId, roomId, now),
260+
status: 'CheckedIn',
261+
balance: 0,
262+
roomId,
263+
guestId,
264+
transactions: [{ amount }, { amount }],
265+
transactionsCount: 2,
266+
checkedInAt: now,
267+
},
268+
}),
269+
]));
193270
});
194271
});
195272

@@ -226,5 +303,10 @@ void describe('guestStayAccount E2E', () => {
226303
given(...checkedOutAccount)
227304
.when(checkOut)
228305
.then([expectError(403, { detail: `NotCheckedIn` })]));
306+
307+
void it(`details return 404`, () =>
308+
given(...checkedOutAccount)
309+
.when(getDetails)
310+
.then([expectResponse(404)]));
229311
});
230312
});

src/guestStayAccounts/api/api.int.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ void describe('Guest stay account', () => {
448448
apis: [
449449
guestStayAccountsApi(
450450
eventStore,
451+
null!,
451452
doesGuestStayExist,
452453
(prefix) => `${prefix}-${transactionId}`,
453454
() => now,

src/guestStayAccounts/api/api.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ import {
1313
NotFound,
1414
OK,
1515
on,
16+
toWeakETag,
1617
type WebApiSetup,
1718
} from '@event-driven-io/emmett-expressjs';
19+
import { type PongoDb } from '@event-driven-io/pongo';
1820
import { type Request, type Router } from 'express';
1921
import {
2022
checkIn,
@@ -64,6 +66,7 @@ type GetGuestStayAccountDetailsRequest = Request<
6466
export const guestStayAccountsApi =
6567
(
6668
eventStore: EventStore,
69+
readStore: PongoDb,
6770
doesGuestStayExist: (
6871
guestId: string,
6972
roomId: string,
@@ -185,17 +188,17 @@ export const guestStayAccountsApi =
185188
on(async (request: GetGuestStayAccountDetailsRequest) => {
186189
const guestStayAccountId = parseGuestStayAccountId(request.params);
187190

188-
const result = await getGuestStayDetails(
189-
eventStore,
190-
guestStayAccountId,
191-
);
191+
const result = await getGuestStayDetails(readStore, guestStayAccountId);
192192

193193
if (result === null) return NotFound();
194194

195-
if (result.state.status !== 'CheckedIn') return NotFound();
195+
if (result.status !== 'CheckedIn') return NotFound();
196+
197+
const { _version, ...document } = result;
196198

197199
return OK({
198-
body: result.state,
200+
body: document,
201+
eTag: toWeakETag(_version),
199202
});
200203
}),
201204
);

src/guestStayAccounts/guestStayDetails.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { type EventStore } from '@event-driven-io/emmett';
1+
import { pongoSingleStreamProjection } from '@event-driven-io/emmett-postgresql';
2+
import { type PongoDb } from '@event-driven-io/pongo';
23
import type { GuestStayAccountEvent } from './guestStayAccount';
34

45
export type NotExisting = { status: 'NotExisting' };
56

67
export type CheckedIn = {
7-
id: string;
8+
_id: string;
89
guestId: string;
910
roomId: string;
1011
status: 'CheckedIn' | 'CheckedOut';
@@ -13,6 +14,7 @@ export type CheckedIn = {
1314
transactions: { id: string; amount: number }[];
1415
checkedInAt: Date;
1516
checkedOutAt?: Date;
17+
_version?: bigint;
1618
};
1719

1820
export type GuestStayDetails = NotExisting | CheckedIn;
@@ -29,7 +31,7 @@ export const evolve = (
2931
case 'GuestCheckedIn': {
3032
return state.status === 'NotExisting'
3133
? {
32-
id: event.guestStayAccountId,
34+
_id: event.guestStayAccountId,
3335
guestId: event.guestId,
3436
roomId: event.roomId,
3537
status: 'CheckedIn',
@@ -85,11 +87,25 @@ export const evolve = (
8587
}
8688
};
8789

90+
const guestStayDetailsCollectionName = 'GuestStayDetails';
91+
92+
export const guestStayDetailsProjection = pongoSingleStreamProjection({
93+
collectionName: guestStayDetailsCollectionName,
94+
evolve,
95+
canHandle: [
96+
'GuestCheckedIn',
97+
'ChargeRecorded',
98+
'PaymentRecorded',
99+
'GuestCheckedOut',
100+
'GuestCheckoutFailed',
101+
],
102+
initialState,
103+
});
104+
88105
export const getGuestStayDetails = (
89-
eventStore: EventStore,
106+
pongo: PongoDb,
90107
guestStayAccountId: string,
91108
) =>
92-
eventStore.aggregateStream(guestStayAccountId, {
93-
evolve,
94-
initialState,
95-
});
109+
pongo
110+
.collection<GuestStayDetails>(guestStayDetailsCollectionName)
111+
.findOne({ _id: guestStayAccountId });

src/index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
1+
import { projections } from '@event-driven-io/emmett';
12
import { getApplication, startAPI } from '@event-driven-io/emmett-expressjs';
23
import { getPostgreSQLEventStore } from '@event-driven-io/emmett-postgresql';
4+
import { pongoClient } from '@event-driven-io/pongo';
35
import { randomUUID } from 'crypto';
46
import type { Application } from 'express';
57
import { guestStayAccountsApi } from './guestStayAccounts/api/api';
8+
import { guestStayDetailsProjection } from './guestStayAccounts/guestStayDetails';
69

710
const connectionString =
811
process.env.POSTGRESQL_CONNECTION_STRING ??
912
'postgresql://postgres@localhost:5432/postgres';
1013

11-
const eventStore = getPostgreSQLEventStore(connectionString);
14+
const eventStore = getPostgreSQLEventStore(connectionString, {
15+
projections: projections.inline([guestStayDetailsProjection]),
16+
});
17+
18+
const readStore = pongoClient(connectionString);
1219

1320
const doesGuestStayExist = (_guestId: string, _roomId: string, _day: Date) =>
1421
Promise.resolve(true);
1522

1623
const guestStayAccounts = guestStayAccountsApi(
1724
eventStore,
25+
readStore.db(),
1826
doesGuestStayExist,
1927
(prefix) => `${prefix}-${randomUUID()}`,
2028
() => new Date(),

0 commit comments

Comments
 (0)