Skip to content

Commit 868eb9c

Browse files
committed
Add functionality to remove equipment
1 parent ffa2caa commit 868eb9c

File tree

8 files changed

+125
-0
lines changed

8 files changed

+125
-0
lines changed

src/commands/equipment/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import {registerTrainingSheet} from './register-training-sheet';
44
import {registerTrainingSheetForm} from './register-training-sheet-form';
55
import {removeTrainingSheet} from './remove-training-sheet';
66
import {removeTrainingSheetForm} from './remove-training-sheet-form';
7+
import {removeEquipment} from './remove';
8+
import {removeEquipmentForm} from './remove-form';
79

810
export const equipment = {
911
add: {
@@ -18,4 +20,8 @@ export const equipment = {
1820
...removeTrainingSheet,
1921
...removeTrainingSheetForm,
2022
},
23+
remove: {
24+
...removeEquipment,
25+
...removeEquipmentForm,
26+
},
2127
};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import * as E from 'fp-ts/Either';
2+
import {Form} from '../../types/form';
3+
import {pipe} from 'fp-ts/lib/function';
4+
import {html, safe, sanitizeString, toLoggedInContent} from '../../types/html';
5+
import {getEquipmentIdFromForm} from './get-equipment-id-from-form';
6+
import {getEquipmentName} from './get-equipment-name';
7+
8+
type ViewModel = {
9+
equipmentId: string;
10+
equipmentName: string;
11+
};
12+
13+
const renderForm = (viewModel: ViewModel) =>
14+
pipe(
15+
html`
16+
<div class="stack-large">
17+
<h1>Remove '${sanitizeString(viewModel.equipmentName)}'?</h1>
18+
<form action="#" method="post">
19+
<input
20+
type="hidden"
21+
name="id"
22+
value="${safe(viewModel.equipmentId)}"
23+
/>
24+
<button type="submit">Confirm and send</button>
25+
</form>
26+
</div>
27+
`,
28+
toLoggedInContent(safe('Remove Equipment'))
29+
);
30+
31+
export const removeEquipmentForm: Form<ViewModel> = {
32+
renderForm,
33+
constructForm:
34+
input =>
35+
({readModel}) =>
36+
pipe(
37+
E.Do,
38+
E.bind('equipmentId', () => getEquipmentIdFromForm(input)),
39+
E.bind('equipmentName', ({equipmentId}) =>
40+
getEquipmentName(readModel, equipmentId)
41+
)
42+
),
43+
};

src/commands/equipment/remove.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {constructEvent, isEventOfType} from '../../types';
2+
import * as RA from 'fp-ts/ReadonlyArray';
3+
import * as t from 'io-ts';
4+
import * as tt from 'io-ts-types';
5+
import * as O from 'fp-ts/Option';
6+
import {pipe} from 'fp-ts/lib/function';
7+
import {Command} from '../command';
8+
import {isAdminOrSuperUser} from '../is-admin-or-super-user';
9+
10+
const codec = t.strict({
11+
id: tt.UUID,
12+
});
13+
14+
type RemoveEquipment = t.TypeOf<typeof codec>;
15+
16+
const process: Command<RemoveEquipment>['process'] = input => {
17+
if (input.events.length === 0) {
18+
return O.none;
19+
}
20+
return pipe(
21+
input.events,
22+
RA.filter(isEventOfType('EquipmentRemoved')),
23+
RA.match(
24+
() => O.some(constructEvent('EquipmentRemoved')(input.command)),
25+
() => O.none
26+
)
27+
);
28+
};
29+
30+
const resource: Command<RemoveEquipment>['resource'] = command => ({
31+
type: 'Equipment',
32+
id: command.id,
33+
});
34+
35+
export const removeEquipment: Command<RemoveEquipment> = {
36+
process,
37+
resource,
38+
decode: codec.decode,
39+
isAuthorized: isAdminOrSuperUser,
40+
};

src/queries/equipment/render.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,24 @@ const adminMarkTrainedBy = (viewModel: ViewModel) =>
7373
O.getOrElse(() => html``)
7474
);
7575

76+
const adminRemoveEquipment = (viewModel: ViewModel) =>
77+
pipe(
78+
viewModel,
79+
O.of,
80+
O.filter(viewModel => viewModel.isSuperUser),
81+
O.map(viewModel => viewModel.equipment.id),
82+
O.map(
83+
id =>
84+
html` <li>
85+
<a href="/equipment/remove?equipmentId=${id}"
86+
>[Admin] Remove equipment</a
87+
>
88+
${tooltip(safe('Only admins can remove equipment'))}
89+
</li>`
90+
),
91+
O.getOrElse(() => html``)
92+
);
93+
7694
const addTrainer = (viewModel: ViewModel) =>
7795
pipe(
7896
viewModel,
@@ -141,6 +159,7 @@ const equipmentActions = (viewModel: ViewModel) => html`
141159
${trainMember(viewModel)} ${adminMarkTrainedBy(viewModel)}
142160
${addTrainer(viewModel)} ${registerSheet(viewModel)}
143161
${currentSheet(viewModel)} ${removeTrainingSheet(viewModel)}
162+
${adminRemoveEquipment(viewModel)}
144163
</ul>
145164
`;
146165

src/read-models/shared-state/update-state.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,15 @@ export const updateState =
9292
.values({id: event.id, name: event.name, areaId: event.areaId})
9393
.run();
9494
break;
95+
case 'EquipmentRemoved':
96+
db.delete(trainersTable)
97+
.where(eq(trainersTable.equipmentId, event.id))
98+
.run();
99+
db.delete(trainedMemberstable)
100+
.where(eq(trainedMemberstable.equipmentId, event.id))
101+
.run();
102+
db.delete(equipmentTable).where(eq(equipmentTable.id, event.id)).run();
103+
break;
95104
case 'TrainerAdded': {
96105
if (
97106
isOwnerOfAreaContainingEquipment(db, linking)(

src/routes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export const initRoutes = (
4242
'remove-training-sheet',
4343
commands.equipment.removeTrainingSheet
4444
),
45+
...command('equipment', 'remove', commands.equipment.remove),
4546
...command(
4647
'equipment',
4748
'mark-member-trained',

src/types/domain-event.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ const EquipmentAdded = defineEvent('EquipmentAdded', {
5454
areaId: tt.UUID,
5555
});
5656

57+
const EquipmentRemoved = defineEvent('EquipmentRemoved', {
58+
id: tt.UUID,
59+
});
60+
5761
const OwnerAdded = defineEvent('OwnerAdded', {
5862
areaId: tt.UUID,
5963
memberNumber: t.number,
@@ -202,6 +206,7 @@ export const events = [
202206
AreaRemoved,
203207
AreaEmailUpdated,
204208
EquipmentAdded,
209+
EquipmentRemoved,
205210
OwnerAdded,
206211
OwnerRemoved,
207212
SuperUserDeclared,
@@ -233,6 +238,7 @@ export const DomainEvent = t.union([
233238
AreaRemoved.codec,
234239
AreaEmailUpdated.codec,
235240
EquipmentAdded.codec,
241+
EquipmentRemoved.codec,
236242
OwnerAdded.codec,
237243
OwnerRemoved.codec,
238244
SuperUserDeclared.codec,

tests/read-models/test-framework.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ export const initTestFramework = async (): Promise<TestFramework> => {
136136
removeTrainingSheet: frameworkify(
137137
commands.equipment.removeTrainingSheet
138138
),
139+
remove: frameworkify(commands.equipment.remove),
139140
},
140141
trainers: {
141142
add: frameworkify(commands.trainers.add),

0 commit comments

Comments
 (0)