Skip to content

Commit

Permalink
Add back equipment read models
Browse files Browse the repository at this point in the history
  • Loading branch information
Lan2u committed Sep 20, 2024
1 parent 9cf1b11 commit 5075de6
Show file tree
Hide file tree
Showing 7 changed files with 240 additions and 0 deletions.
38 changes: 38 additions & 0 deletions src/read-models/equipment/get-all.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {pipe} from 'fp-ts/lib/function';
import * as O from 'fp-ts/Option';
import {DomainEvent, isEventOfType} from '../../types';
import * as RA from 'fp-ts/ReadonlyArray';
import {readModels} from '..';
import {UUID} from 'io-ts-types';

type Equipment = {
name: string;
id: UUID;
areaId: UUID;
areaName: string;
trainingSheetId: O.Option<string>;
};

export const getAll = (
events: ReadonlyArray<DomainEvent>
): ReadonlyArray<Equipment> =>
pipe(
events,
RA.filter(isEventOfType('EquipmentAdded')),
RA.map(equipment =>
pipe(
readModels.areas.getArea(events)(equipment.areaId),
O.map(area => ({
...equipment,
areaName: area.name,
})),
O.map(equipmentData => ({
...equipmentData,
trainingSheetId: readModels.equipment.getTrainingSheetId(events)(
equipmentData.id
),
}))
)
),
RA.compact
);
19 changes: 19 additions & 0 deletions src/read-models/equipment/get-for-area.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {pipe} from 'fp-ts/lib/function';
import {DomainEvent, isEventOfType} from '../../types';
import * as RA from 'fp-ts/ReadonlyArray';
import {UUID} from 'io-ts-types';

type Equipment = {
name: string;
id: UUID;
areaId: UUID;
};

export const getForArea =
(events: ReadonlyArray<DomainEvent>) =>
(areaId: string): ReadonlyArray<Equipment> =>
pipe(
events,
RA.filter(isEventOfType('EquipmentAdded')),
RA.filter(event => event.areaId === areaId)
);
45 changes: 45 additions & 0 deletions src/read-models/equipment/get-trained-on.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {DomainEvent} from '../../types';

type TrainedInfo = {
when: Date;
by: number | null;
prev: ReadonlyArray<{
when: Date;
by: number | null;
}>;
};

export const getMembersTrainedOn =
(equipmentId: string) =>
(events: ReadonlyArray<DomainEvent>): ReadonlyMap<number, TrainedInfo> => {
// Note that currently you cannot revoke training.

const trained: Map<number, TrainedInfo> = new Map();
for (const event of events) {
if (
event.type !== 'MemberTrainedOnEquipment' ||
event.equipmentId !== equipmentId
) {
continue;
}
const current = trained.get(event.memberNumber);
if (current) {
trained.set(event.memberNumber, {
when: event.recordedAt,
by: event.trainedByMemberNumber,
prev: current.prev.concat([
{
when: current.when,
by: current.by,
},
]),
});
}
trained.set(event.memberNumber, {
when: event.recordedAt,
by: event.trainedByMemberNumber,
prev: [],
});
}
return trained;
};
17 changes: 17 additions & 0 deletions src/read-models/equipment/get-training-quiz-results.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {pipe} from 'fp-ts/lib/function';
import * as RA from 'fp-ts/ReadonlyArray';
import {DomainEvent, isEventOfType} from '../../types';
import {EventOfType} from '../../types/domain-event';

export const getTrainingQuizResults =
(events: ReadonlyArray<DomainEvent>) =>
(
equipmentId: string
): ReadonlyArray<EventOfType<'EquipmentTrainingQuizResult'>> =>
pipe(
events,
RA.filter(isEventOfType('EquipmentTrainingQuizResult')),
RA.filter(event => {
return event.equipmentId === equipmentId;
})
);
18 changes: 18 additions & 0 deletions src/read-models/equipment/get-training-sheet-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {pipe} from 'fp-ts/lib/function';
import * as RA from 'fp-ts/ReadonlyArray';
import * as O from 'fp-ts/Option';
import {DomainEvent, isEventOfType} from '../../types';

export const getTrainingSheetId =
(events: ReadonlyArray<DomainEvent>) =>
(equipmentId: string): O.Option<string> =>
pipe(
events,
RA.filter(isEventOfType('EquipmentTrainingSheetRegistered')),
RA.filter(event => event.equipmentId === equipmentId),
RA.last,
O.match(
() => O.none,
mostRecentSheet => O.some(mostRecentSheet.trainingSheetId)
)
);
90 changes: 90 additions & 0 deletions src/read-models/equipment/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {pipe} from 'fp-ts/lib/function';
import * as RM from 'fp-ts/ReadonlyMap';
import * as O from 'fp-ts/Option';
import {DomainEvent, SubsetOfDomainEvent, filterByName} from '../../types';
import * as RA from 'fp-ts/ReadonlyArray';
import {EventName, isEventOfType} from '../../types/domain-event';
import {Eq as stringEq} from 'fp-ts/string';
import {UUID} from 'io-ts-types';

export type Equipment = {
name: string;
id: UUID;
trainers: ReadonlyArray<number>;
areaId: UUID;
trainedMembers: ReadonlyArray<number>;
trainingSheetId: O.Option<string>;
};

type EquipmentState = {
name: string;
id: UUID;
areaId: UUID;
trainers: Set<number>;
trainedMembers: Set<number>;
trainingSheetId?: string;
};

const pertinentEvents: Array<EventName> = [
'EquipmentAdded',
'TrainerAdded',
'MemberTrainedOnEquipment',
'EquipmentTrainingSheetRegistered',
];

const updateState = (
state: Map<string, EquipmentState>,
event: SubsetOfDomainEvent<typeof pertinentEvents>
) => {
if (isEventOfType('EquipmentAdded')(event)) {
state.set(event.id, {
...event,
trainers: new Set(),
trainedMembers: new Set(),
});
}
if (isEventOfType('TrainerAdded')(event)) {
const equipment = state.get(event.equipmentId);
if (equipment) {
state.set(event.equipmentId, {
...equipment,
trainers: equipment.trainers.add(event.memberNumber),
});
}
}
if (isEventOfType('MemberTrainedOnEquipment')(event)) {
const equipment = state.get(event.equipmentId);
if (equipment) {
state.set(event.equipmentId, {
...equipment,
trainedMembers: equipment.trainedMembers.add(event.memberNumber),
});
}
}
if (isEventOfType('EquipmentTrainingSheetRegistered')(event)) {
const equipment = state.get(event.equipmentId);
if (equipment) {
state.set(event.equipmentId, {
...equipment,
trainingSheetId: event.trainingSheetId,
});
}
}
return state;
};

export const get =
(events: ReadonlyArray<DomainEvent>) =>
(equipmentId: string): O.Option<Equipment> =>
pipe(
events,
filterByName(pertinentEvents),
RA.reduce(new Map(), updateState),
RM.lookup(stringEq)(equipmentId), // TODO - Do updateState lazily based on what is looked up.
O.map(state => ({
...state,
trainingSheetId: O.fromNullable(state.trainingSheetId),
trainers: Array.from(state.trainers.values()),
trainedMembers: Array.from(state.trainedMembers.values()),
}))
);
13 changes: 13 additions & 0 deletions src/read-models/equipment/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {get} from './get';
import {getAll} from './get-all';
import {getForArea} from './get-for-area';
import {getTrainingQuizResults} from './get-training-quiz-results';
import {getTrainingSheetId} from './get-training-sheet-id';

export const equipment = {
get,
getAll,
getForArea,
getTrainingSheetId,
getTrainingQuizResults,
};

0 comments on commit 5075de6

Please sign in to comment.