Skip to content

Commit 3a79810

Browse files
committed
Updating Commands to produce TaskEither
1 parent 4e07ba3 commit 3a79810

10 files changed

+60
-38
lines changed

src/commands/member-numbers/link-number-to-email.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {EmailAddressCodec, constructEvent, isEventOfType} from '../../types';
33
import * as t from 'io-ts';
44
import * as tt from 'io-ts-types';
55
import * as O from 'fp-ts/Option';
6+
import * as TE from 'fp-ts/TaskEither';
67
import {Command} from '../command';
78
import {isAdminOrSuperUser} from '../is-admin-or-super-user';
89
import {pipe} from 'fp-ts/lib/function';
@@ -39,19 +40,19 @@ const process: Command<LinkNumberToEmail>['process'] = input =>
3940
event.memberNumber === input.command.memberNumber
4041
),
4142
RA.matchW(
42-
() => O.some(constructEvent('MemberNumberLinkedToEmail')(input.command)),
43+
() => TE.right(O.some(constructEvent('MemberNumberLinkedToEmail')(input.command))),
4344
events => {
4445
if (isDuplicateOfPreviousCommand(events[0], input.command)) {
45-
return O.none;
46+
return TE.right(O.none);
4647
}
4748
if (isUsingAlreadyUsedEmail(events[0], input.command)) {
48-
return O.some(
49+
return TE.right(O.some(
4950
constructEvent('LinkingMemberNumberToAnAlreadyUsedEmailAttempted')(
5051
input.command
5152
)
52-
);
53+
));
5354
}
54-
return O.none;
55+
return TE.right(O.none);
5556
}
5657
)
5758
);

src/commands/member-numbers/mark-member-rejoined-with-existing-number.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {constructEvent, isEventOfType} from '../../types';
33
import * as t from 'io-ts';
44
import * as tt from 'io-ts-types';
55
import * as O from 'fp-ts/Option';
6+
import * as TE from 'fp-ts/TaskEither';
67
import {Command} from '../command';
78
import {isAdminOrSuperUser} from '../is-admin-or-super-user';
89
import {pipe} from 'fp-ts/lib/function';
@@ -29,7 +30,8 @@ const process: Command<MarkMemberRejoinedWithExistingNumber>['process'] =
2930
? O.none
3031
: O.some(
3132
constructEvent('MemberRejoinedWithExistingNumber')(input.command)
32-
)
33+
),
34+
TE.right
3335
);
3436

3537
const resource: Command<MarkMemberRejoinedWithExistingNumber>['resource'] =

src/commands/member-numbers/mark-member-rejoined-with-new-number.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {constructEvent, isEventOfType} from '../../types';
33
import * as t from 'io-ts';
44
import * as tt from 'io-ts-types';
55
import * as O from 'fp-ts/Option';
6+
import * as TE from 'fp-ts/TaskEither';
67
import {Command} from '../command';
78
import {isAdminOrSuperUser} from '../is-admin-or-super-user';
89
import {pipe} from 'fp-ts/lib/function';
@@ -28,7 +29,8 @@ const process: Command<MarkMemberRejoinedWithNewNumber>['process'] = input =>
2829
events =>
2930
RA.some(isDuplicateOfPreviousCommand(input.command))(events)
3031
? O.none
31-
: O.some(constructEvent('MemberRejoinedWithNewNumber')(input.command))
32+
: O.some(constructEvent('MemberRejoinedWithNewNumber')(input.command)),
33+
TE.right
3234
);
3335

3436
const resource: Command<MarkMemberRejoinedWithNewNumber>['resource'] =

src/commands/members/add-email.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as O from 'fp-ts/Option';
2+
import * as TE from 'fp-ts/TaskEither';
23
import * as t from 'io-ts';
34
import * as tt from 'io-ts-types';
45
import {Command} from '../command';
@@ -8,6 +9,8 @@ import {
89
findMemberNumberByEmail,
910
projectMemberEmailStates,
1011
} from './email-state';
12+
import { failureWithStatus } from '../../types/failure-with-status';
13+
import { StatusCodes } from 'http-status-codes';
1114

1215
const codec = t.strict({
1316
memberNumber: tt.NumberFromString,
@@ -20,30 +23,30 @@ const process: Command<AddMemberEmail>['process'] = input => {
2023
const states = projectMemberEmailStates(input.events);
2124
const currentMember = states.get(input.command.memberNumber);
2225
if (currentMember === undefined) {
23-
return O.none;
26+
return TE.left(failureWithStatus('Bad Request', StatusCodes.BAD_REQUEST)());
2427
}
2528

2629
const ownerOfEmail = findMemberNumberByEmail(states, input.command.email);
2730
if (ownerOfEmail === input.command.memberNumber) {
28-
return O.none;
31+
return TE.left(failureWithStatus('Bad Request', StatusCodes.BAD_REQUEST)());
2932
}
3033
if (ownerOfEmail !== undefined) {
31-
return O.some(
34+
return TE.right(O.some(
3235
constructEvent('LinkingMemberNumberToAnAlreadyUsedEmailAttempted')({
3336
actor: input.command.actor,
3437
memberNumber: input.command.memberNumber,
3538
email: input.command.email,
3639
})
37-
);
40+
));
3841
}
3942

40-
return O.some(
43+
return TE.right(O.some(
4144
constructEvent('MemberEmailAdded')({
4245
actor: input.command.actor,
4346
memberNumber: input.command.memberNumber,
4447
email: input.command.email,
4548
})
46-
);
49+
));
4750
};
4851

4952
const resource = () => ({

src/commands/members/change-primary-email.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import * as O from 'fp-ts/Option';
2+
import * as TE from 'fp-ts/TaskEither';
23
import * as t from 'io-ts';
34
import * as tt from 'io-ts-types';
45
import {Command} from '../command';
56
import {EmailAddressCodec, constructEvent} from '../../types';
67
import {isSelfOrPrivileged} from '../is-self-or-privileged';
78
import {projectMemberEmailStates} from './email-state';
89
import {normaliseEmailAddress} from '../../read-models/shared-state/normalise-email-address';
10+
import { failureWithStatus } from '../../types/failure-with-status';
11+
import { StatusCodes } from 'http-status-codes';
912

1013
const codec = t.strict({
1114
memberNumber: tt.NumberFromString,
@@ -19,7 +22,7 @@ const process: Command<ChangeMemberPrimaryEmail>['process'] = input => {
1922
input.command.memberNumber
2023
);
2124
if (state === undefined) {
22-
return O.none;
25+
return TE.left(failureWithStatus('Bad Request', StatusCodes.BAD_REQUEST)());
2326
}
2427

2528
const emailAddress = normaliseEmailAddress(input.command.email);
@@ -29,16 +32,16 @@ const process: Command<ChangeMemberPrimaryEmail>['process'] = input => {
2932
!email.verified ||
3033
state.primaryEmailAddress === emailAddress
3134
) {
32-
return O.none;
35+
return TE.left(failureWithStatus('Bad Request', StatusCodes.BAD_REQUEST)());
3336
}
3437

35-
return O.some(
38+
return TE.right(O.some(
3639
constructEvent('MemberPrimaryEmailChanged')({
3740
actor: input.command.actor,
3841
memberNumber: input.command.memberNumber,
3942
email: input.command.email,
4043
})
41-
);
44+
));
4245
};
4346

4447
const resource = () => ({

src/commands/members/edit-form-of-address.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {constructEvent} from '../../types';
22
import * as t from 'io-ts';
33
import * as tt from 'io-ts-types';
44
import * as O from 'fp-ts/Option';
5+
import * as TE from 'fp-ts/TaskEither';
56
import {Command} from '../command';
67
import {isSelfOrPrivileged} from '../is-self-or-privileged';
78

@@ -13,14 +14,14 @@ const codec = t.strict({
1314
type EditFormOfAddress = t.TypeOf<typeof codec>;
1415

1516
const process: Command<EditFormOfAddress>['process'] = input =>
16-
O.some(
17+
TE.right(O.some(
1718
constructEvent('MemberDetailsUpdated')({
1819
memberNumber: input.command.memberNumber,
1920
name: undefined,
2021
formOfAddress: input.command.formOfAddress,
2122
actor: input.command.actor,
2223
})
23-
);
24+
));
2425

2526
const resource: Command<EditFormOfAddress>['resource'] = input => ({
2627
type: 'MemberDetails',

src/commands/members/edit-name.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {constructEvent} from '../../types';
22
import * as t from 'io-ts';
33
import * as tt from 'io-ts-types';
44
import * as O from 'fp-ts/Option';
5+
import * as TE from 'fp-ts/TaskEither';
56
import {Command} from '../command';
67
import {isAdminOrSuperUser} from '../is-admin-or-super-user';
78

@@ -13,14 +14,14 @@ const codec = t.strict({
1314
type EditName = t.TypeOf<typeof codec>;
1415

1516
const process: Command<EditName>['process'] = input =>
16-
O.some(
17+
TE.right(O.some(
1718
constructEvent('MemberDetailsUpdated')({
1819
memberNumber: input.command.memberNumber,
1920
name: input.command.name,
2021
formOfAddress: undefined,
2122
actor: input.command.actor,
2223
})
23-
);
24+
));
2425

2526
const resource: Command<EditName>['resource'] = input => ({
2627
type: 'MemberDetails',

src/commands/members/send-email-verification.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as O from 'fp-ts/Option';
2+
import * as TE from 'fp-ts/TaskEither';
23
import * as t from 'io-ts';
34
import * as tt from 'io-ts-types';
45
import {Command} from '../command';
@@ -7,6 +8,8 @@ import {isSelfOrPrivileged} from '../is-self-or-privileged';
78
import {projectMemberEmailStates, SEND_EMAIL_VERIFICATION_COOLDOWN_MS} from './email-state';
89
import {normaliseEmailAddress} from '../../read-models/shared-state/normalise-email-address';
910
import { publish } from 'pubsub-js';
11+
import { failureWithStatus } from '../../types/failure-with-status';
12+
import { StatusCodes } from 'http-status-codes';
1013

1114
const codec = t.strict({
1215
memberNumber: tt.NumberFromString,
@@ -20,19 +23,19 @@ const process: Command<SendMemberEmailVerification>['process'] = input => {
2023
input.command.memberNumber
2124
);
2225
if (state === undefined) {
23-
return O.none;
26+
return TE.left(failureWithStatus('Bad Request', StatusCodes.BAD_REQUEST)());
2427
}
2528

2629
const emailAddress = normaliseEmailAddress(input.command.email);
2730
const email = state.emails[emailAddress];
2831
if (!email || email.verified) {
29-
return O.none;
32+
return TE.left(failureWithStatus('Bad Request', StatusCodes.BAD_REQUEST)());
3033
}
3134

3235
if (email.verificationLastSent) {
3336
const sinceLastMs = Date.now() - email.verificationLastSent.getTime();
3437
if (sinceLastMs < SEND_EMAIL_VERIFICATION_COOLDOWN_MS) {
35-
return O.none;
38+
return TE.left(failureWithStatus('Bad Request', StatusCodes.BAD_REQUEST)());
3639
}
3740
}
3841

@@ -44,13 +47,13 @@ const process: Command<SendMemberEmailVerification>['process'] = input => {
4447
emailAddress,
4548
});
4649

47-
return O.some(
50+
return TE.right(O.some(
4851
constructEvent('MemberEmailVerificationRequested')({
4952
actor: input.command.actor,
5053
memberNumber: input.command.memberNumber,
5154
email: emailAddress,
5255
})
53-
);
56+
));
5457
};
5558

5659
const resource = () => ({

src/commands/members/sign-owner-agreement.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as t from 'io-ts';
22
import * as tt from 'io-ts-types';
33
import * as O from 'fp-ts/Option';
4+
import * as TE from 'fp-ts/TaskEither';
45
import {Command} from '../command';
56
import {isSelfOrPrivileged} from '../is-self-or-privileged';
67
import {constructEvent} from '../../types';
@@ -14,7 +15,7 @@ const codec = t.strict({
1415
type SignOwnerAgreement = t.TypeOf<typeof codec>;
1516

1617
const process: Command<SignOwnerAgreement>['process'] = input =>
17-
pipe(input.command, constructEvent('OwnerAgreementSigned'), O.some);
18+
pipe(input.command, constructEvent('OwnerAgreementSigned'), O.some, TE.right);
1819

1920
const resource: Command<SignOwnerAgreement>['resource'] = input => ({
2021
type: 'OwnerAgreementSigning',

src/commands/trainers/mark-member-trained-by.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ import {DomainEvent, constructEvent} from '../../types';
22
import * as t from 'io-ts';
33
import * as tt from 'io-ts-types';
44
import * as O from 'fp-ts/Option';
5+
import * as TE from 'fp-ts/TaskEither';
56
import {Command, WithActor} from '../command';
67
import {isAdminOrSuperUser} from '../is-admin-or-super-user';
78
import {isEquipmentTrainer} from '../is-equipment-trainer';
89
import {Actor} from '../../types/actor';
910
import {DateTime} from 'luxon';
11+
import { FailureWithStatus, failureWithStatus } from '../../types/failure-with-status';
12+
import { StatusCodes } from 'http-status-codes';
1013

1114
const codec = t.strict({
1215
equipmentId: tt.UUID,
@@ -20,18 +23,20 @@ export type MarkMemberTrainedBy = t.TypeOf<typeof codec>;
2023
const process = (input: {
2124
command: WithActor<MarkMemberTrainedBy>;
2225
events: ReadonlyArray<DomainEvent>;
23-
}): O.Option<DomainEvent> =>
26+
}) =>
2427
input.command.actor.tag !== 'user'
25-
? O.none
26-
: O.some(
27-
constructEvent('MemberTrainedOnEquipmentBy')({
28-
equipmentId: input.command.equipmentId,
29-
trainedByMemberNumber: input.command.trainedByMemberNumber,
30-
trainedAt: input.command.trainedAt,
31-
memberNumber: input.command.memberNumber,
32-
markedTrainedBy: input.command.actor.user.memberNumber,
33-
actor: input.command.actor,
34-
})
28+
? TE.left(failureWithStatus('Invalid Request', StatusCodes.BAD_REQUEST)())
29+
: TE.right(
30+
O.some(
31+
constructEvent('MemberTrainedOnEquipmentBy')({
32+
equipmentId: input.command.equipmentId,
33+
trainedByMemberNumber: input.command.trainedByMemberNumber,
34+
trainedAt: input.command.trainedAt,
35+
memberNumber: input.command.memberNumber,
36+
markedTrainedBy: input.command.actor.user.memberNumber,
37+
actor: input.command.actor,
38+
})
39+
)
3540
);
3641

3742
const resource = (command: MarkMemberTrainedBy) => ({

0 commit comments

Comments
 (0)