diff --git a/src/commands/equipment/index.ts b/src/commands/equipment/index.ts
index 24f9bc7d..8cc6d77e 100644
--- a/src/commands/equipment/index.ts
+++ b/src/commands/equipment/index.ts
@@ -4,6 +4,8 @@ import {registerTrainingSheet} from './register-training-sheet';
import {registerTrainingSheetForm} from './register-training-sheet-form';
import {removeTrainingSheet} from './remove-training-sheet';
import {removeTrainingSheetForm} from './remove-training-sheet-form';
+import {removeEquipment} from './remove';
+import {removeEquipmentForm} from './remove-form';
export const equipment = {
add: {
@@ -18,4 +20,8 @@ export const equipment = {
...removeTrainingSheet,
...removeTrainingSheetForm,
},
+ remove: {
+ ...removeEquipment,
+ ...removeEquipmentForm,
+ },
};
diff --git a/src/commands/equipment/remove-form.ts b/src/commands/equipment/remove-form.ts
new file mode 100644
index 00000000..bd66b6b1
--- /dev/null
+++ b/src/commands/equipment/remove-form.ts
@@ -0,0 +1,43 @@
+import * as E from 'fp-ts/Either';
+import {Form} from '../../types/form';
+import {pipe} from 'fp-ts/lib/function';
+import {html, safe, sanitizeString, toLoggedInContent} from '../../types/html';
+import {getEquipmentIdFromForm} from './get-equipment-id-from-form';
+import {getEquipmentName} from './get-equipment-name';
+
+type ViewModel = {
+ equipmentId: string;
+ equipmentName: string;
+};
+
+const renderForm = (viewModel: ViewModel) =>
+ pipe(
+ html`
+
+
Remove '${sanitizeString(viewModel.equipmentName)}'?
+
+
+ `,
+ toLoggedInContent(safe('Remove Equipment'))
+ );
+
+export const removeEquipmentForm: Form = {
+ renderForm,
+ constructForm:
+ input =>
+ ({readModel}) =>
+ pipe(
+ E.Do,
+ E.bind('equipmentId', () => getEquipmentIdFromForm(input)),
+ E.bind('equipmentName', ({equipmentId}) =>
+ getEquipmentName(readModel, equipmentId)
+ )
+ ),
+};
diff --git a/src/commands/equipment/remove.ts b/src/commands/equipment/remove.ts
new file mode 100644
index 00000000..4cb98347
--- /dev/null
+++ b/src/commands/equipment/remove.ts
@@ -0,0 +1,40 @@
+import {constructEvent, isEventOfType} from '../../types';
+import * as RA from 'fp-ts/ReadonlyArray';
+import * as t from 'io-ts';
+import * as tt from 'io-ts-types';
+import * as O from 'fp-ts/Option';
+import {pipe} from 'fp-ts/lib/function';
+import {Command} from '../command';
+import {isAdminOrSuperUser} from '../is-admin-or-super-user';
+
+const codec = t.strict({
+ id: tt.UUID,
+});
+
+type RemoveEquipment = t.TypeOf;
+
+const process: Command['process'] = input => {
+ if (input.events.length === 0) {
+ return O.none;
+ }
+ return pipe(
+ input.events,
+ RA.filter(isEventOfType('EquipmentRemoved')),
+ RA.match(
+ () => O.some(constructEvent('EquipmentRemoved')(input.command)),
+ () => O.none
+ )
+ );
+};
+
+const resource: Command['resource'] = command => ({
+ type: 'Equipment',
+ id: command.id,
+});
+
+export const removeEquipment: Command = {
+ process,
+ resource,
+ decode: codec.decode,
+ isAuthorized: isAdminOrSuperUser,
+};
diff --git a/src/queries/equipment/render.ts b/src/queries/equipment/render.ts
index 7fea0065..366d091e 100644
--- a/src/queries/equipment/render.ts
+++ b/src/queries/equipment/render.ts
@@ -73,6 +73,24 @@ const adminMarkTrainedBy = (viewModel: ViewModel) =>
O.getOrElse(() => html``)
);
+const adminRemoveEquipment = (viewModel: ViewModel) =>
+ pipe(
+ viewModel,
+ O.of,
+ O.filter(viewModel => viewModel.isSuperUser),
+ O.map(viewModel => viewModel.equipment.id),
+ O.map(
+ id =>
+ html`
+ [Admin] Remove equipment
+ ${tooltip(safe('Only admins can remove equipment'))}
+ `
+ ),
+ O.getOrElse(() => html``)
+ );
+
const addTrainer = (viewModel: ViewModel) =>
pipe(
viewModel,
@@ -141,6 +159,7 @@ const equipmentActions = (viewModel: ViewModel) => html`
${trainMember(viewModel)} ${adminMarkTrainedBy(viewModel)}
${addTrainer(viewModel)} ${registerSheet(viewModel)}
${currentSheet(viewModel)} ${removeTrainingSheet(viewModel)}
+ ${adminRemoveEquipment(viewModel)}
`;
diff --git a/src/read-models/shared-state/update-state.ts b/src/read-models/shared-state/update-state.ts
index fe4e937c..8d62d079 100644
--- a/src/read-models/shared-state/update-state.ts
+++ b/src/read-models/shared-state/update-state.ts
@@ -92,6 +92,15 @@ export const updateState =
.values({id: event.id, name: event.name, areaId: event.areaId})
.run();
break;
+ case 'EquipmentRemoved':
+ db.delete(trainersTable)
+ .where(eq(trainersTable.equipmentId, event.id))
+ .run();
+ db.delete(trainedMemberstable)
+ .where(eq(trainedMemberstable.equipmentId, event.id))
+ .run();
+ db.delete(equipmentTable).where(eq(equipmentTable.id, event.id)).run();
+ break;
case 'TrainerAdded': {
if (
isOwnerOfAreaContainingEquipment(db, linking)(
diff --git a/src/routes.ts b/src/routes.ts
index 2917b545..dfe1f4c1 100644
--- a/src/routes.ts
+++ b/src/routes.ts
@@ -42,6 +42,7 @@ export const initRoutes = (
'remove-training-sheet',
commands.equipment.removeTrainingSheet
),
+ ...command('equipment', 'remove', commands.equipment.remove),
...command(
'equipment',
'mark-member-trained',
diff --git a/src/types/domain-event.ts b/src/types/domain-event.ts
index 3ab9f0f4..6f426cda 100644
--- a/src/types/domain-event.ts
+++ b/src/types/domain-event.ts
@@ -54,6 +54,10 @@ const EquipmentAdded = defineEvent('EquipmentAdded', {
areaId: tt.UUID,
});
+const EquipmentRemoved = defineEvent('EquipmentRemoved', {
+ id: tt.UUID,
+});
+
const OwnerAdded = defineEvent('OwnerAdded', {
areaId: tt.UUID,
memberNumber: t.number,
@@ -202,6 +206,7 @@ export const events = [
AreaRemoved,
AreaEmailUpdated,
EquipmentAdded,
+ EquipmentRemoved,
OwnerAdded,
OwnerRemoved,
SuperUserDeclared,
@@ -233,6 +238,7 @@ export const DomainEvent = t.union([
AreaRemoved.codec,
AreaEmailUpdated.codec,
EquipmentAdded.codec,
+ EquipmentRemoved.codec,
OwnerAdded.codec,
OwnerRemoved.codec,
SuperUserDeclared.codec,
diff --git a/tests/read-models/test-framework.ts b/tests/read-models/test-framework.ts
index e5dce21b..3161f08d 100644
--- a/tests/read-models/test-framework.ts
+++ b/tests/read-models/test-framework.ts
@@ -136,6 +136,7 @@ export const initTestFramework = async (): Promise => {
removeTrainingSheet: frameworkify(
commands.equipment.removeTrainingSheet
),
+ remove: frameworkify(commands.equipment.remove),
},
trainers: {
add: frameworkify(commands.trainers.add),