From 83cc58fe96e010d40f54cef9f13eed5aa71db948 Mon Sep 17 00:00:00 2001 From: Timon Date: Tue, 25 Jun 2024 23:09:44 +0200 Subject: [PATCH] feat(api): add rescue station manager (#860) --- apps/api/src/app/app.module.ts | 2 + libs/api/protocol/src/index.ts | 6 + ...eate-communication-message.command.spec.ts | 3 +- .../rescue-station-message.factory.spec.ts | 125 +++++++ .../helper/rescue-station-message.factory.ts | 75 ++++ ...e-station-sign-off-message.command.spec.ts | 95 ++++++ ...rescue-station-sign-off-message.command.ts | 87 +++++ ...ue-station-sign-on-message.command.spec.ts | 129 +++++++ ...-rescue-station-sign-on-message.command.ts | 68 ++++ ...cue-station-update-message.command.spec.ts | 128 +++++++ ...e-rescue-station-update-message.command.ts | 68 ++++ ...ge-command-rescue-station-details.model.ts | 24 ++ .../entity/partials/unit-partial.entity.ts | 2 + .../rescue-station-message-payload.entity.ts | 74 ++++ .../rescue-station-sign-off-message.entity.ts | 26 ++ .../rescue-station-sign-on-message.entity.ts | 12 + .../rescue-station-update-message.entity.ts | 12 + .../communication-message.schema.ts | 2 +- .../schema/protocol-entry-base.schema.ts | 8 +- .../rescue-station-message-payload.schema.ts | 74 ++++ .../rescue-station-sign-off-message.schema.ts | 35 ++ .../rescue-station-sign-on-message.schema.ts | 21 ++ .../rescue-station-updated-message.schema.ts | 21 ++ .../protocol-document.mapper-profile.ts | 2 +- .../protocol-entity.mapper-profile.ts | 2 +- .../protocol-entry.mapper.spec.ts | 319 +++++++++++++++++- .../mapper-profile/protocol-entry.mapper.ts | 44 ++- ...message-payload-document.mapper-profile.ts | 48 +++ ...n-message-payload-entity.mapper-profile.ts | 48 +++ ...ign-off-message-document.mapper-profile.ts | 47 +++ ...-sign-off-message-entity.mapper-profile.ts | 47 +++ ...sign-on-message-document.mapper-profile.ts | 24 ++ ...n-sign-on-message-entity.mapper-profile.ts | 24 ++ ...-update-message-document.mapper-profile.ts | 24 ++ ...on-update-message-entity.mapper-profile.ts | 24 ++ libs/api/protocol/src/lib/protocol.module.ts | 55 ++- .../api/rescue-station-manager/.eslintrc.json | 18 + libs/api/rescue-station-manager/README.md | 11 + .../api/rescue-station-manager/jest.config.ts | 11 + libs/api/rescue-station-manager/project.json | 20 ++ libs/api/rescue-station-manager/src/index.ts | 1 + .../command-rescue-station-data.model.ts | 14 + .../launch-sign-off-process.command.spec.ts | 122 +++++++ .../launch-sign-off-process.command.ts | 86 +++++ .../launch-sign-on-process.command.spec.ts | 103 ++++++ .../command/launch-sign-on-process.command.ts | 81 +++++ ...-in-rescue-station-process.command.spec.ts | 109 ++++++ ...igned-in-rescue-station-process.command.ts | 80 +++++ ...and-rescue-station-details.factory.spec.ts | 139 ++++++++ ...-command-rescue-station-details.factory.ts | 93 +++++ .../event/rescue-station-signed-off.event.ts | 6 + .../event/rescue-station-signed-on.event.ts | 6 + .../signed-in-rescue-station-updated.event.ts | 6 + .../infra/argument/rescue-station.argument.ts | 46 +++ .../update-rescue-station.argument.ts | 45 +++ ...scue-station-subscription.resolver.spec.ts | 84 +++++ .../rescue-station-subscription.resolver.ts | 75 ++++ .../rescue-station.resolver.spec.ts | 230 +++++++++++++ .../controller/rescue-station.resolver.ts | 111 ++++++ .../infra/rescue-station-manager.module.ts | 20 ++ libs/api/rescue-station-manager/tsconfig.json | 23 ++ .../rescue-station-manager/tsconfig.lib.json | 16 + .../rescue-station-manager/tsconfig.spec.json | 14 + tsconfig.base.json | 3 + 64 files changed, 3353 insertions(+), 25 deletions(-) create mode 100644 libs/api/protocol/src/lib/core/command/helper/rescue-station-message.factory.spec.ts create mode 100644 libs/api/protocol/src/lib/core/command/helper/rescue-station-message.factory.ts create mode 100644 libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-sign-off-message.command.spec.ts create mode 100644 libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-sign-off-message.command.ts create mode 100644 libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-sign-on-message.command.spec.ts create mode 100644 libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-sign-on-message.command.ts create mode 100644 libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-update-message.command.spec.ts create mode 100644 libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-update-message.command.ts create mode 100644 libs/api/protocol/src/lib/core/command/rescue-station/message-command-rescue-station-details.model.ts create mode 100644 libs/api/protocol/src/lib/core/entity/protocol-entries/rescue-station/rescue-station-message-payload.entity.ts create mode 100644 libs/api/protocol/src/lib/core/entity/protocol-entries/rescue-station/rescue-station-sign-off-message.entity.ts create mode 100644 libs/api/protocol/src/lib/core/entity/protocol-entries/rescue-station/rescue-station-sign-on-message.entity.ts create mode 100644 libs/api/protocol/src/lib/core/entity/protocol-entries/rescue-station/rescue-station-update-message.entity.ts rename libs/api/protocol/src/lib/infra/schema/{ => communication}/communication-message.schema.ts (94%) create mode 100644 libs/api/protocol/src/lib/infra/schema/rescue-station/rescue-station-message-payload.schema.ts create mode 100644 libs/api/protocol/src/lib/infra/schema/rescue-station/rescue-station-sign-off-message.schema.ts create mode 100644 libs/api/protocol/src/lib/infra/schema/rescue-station/rescue-station-sign-on-message.schema.ts create mode 100644 libs/api/protocol/src/lib/infra/schema/rescue-station/rescue-station-updated-message.schema.ts create mode 100644 libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-message-payload-document.mapper-profile.ts create mode 100644 libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-message-payload-entity.mapper-profile.ts create mode 100644 libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-sign-off-message-document.mapper-profile.ts create mode 100644 libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-sign-off-message-entity.mapper-profile.ts create mode 100644 libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-sign-on-message-document.mapper-profile.ts create mode 100644 libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-sign-on-message-entity.mapper-profile.ts create mode 100644 libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-update-message-document.mapper-profile.ts create mode 100644 libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-update-message-entity.mapper-profile.ts create mode 100644 libs/api/rescue-station-manager/.eslintrc.json create mode 100644 libs/api/rescue-station-manager/README.md create mode 100644 libs/api/rescue-station-manager/jest.config.ts create mode 100644 libs/api/rescue-station-manager/project.json create mode 100644 libs/api/rescue-station-manager/src/index.ts create mode 100644 libs/api/rescue-station-manager/src/lib/core/command/command-rescue-station-data.model.ts create mode 100644 libs/api/rescue-station-manager/src/lib/core/command/launch-sign-off-process.command.spec.ts create mode 100644 libs/api/rescue-station-manager/src/lib/core/command/launch-sign-off-process.command.ts create mode 100644 libs/api/rescue-station-manager/src/lib/core/command/launch-sign-on-process.command.spec.ts create mode 100644 libs/api/rescue-station-manager/src/lib/core/command/launch-sign-on-process.command.ts create mode 100644 libs/api/rescue-station-manager/src/lib/core/command/launch-update-signed-in-rescue-station-process.command.spec.ts create mode 100644 libs/api/rescue-station-manager/src/lib/core/command/launch-update-signed-in-rescue-station-process.command.ts create mode 100644 libs/api/rescue-station-manager/src/lib/core/command/message-command-rescue-station-details.factory.spec.ts create mode 100644 libs/api/rescue-station-manager/src/lib/core/command/message-command-rescue-station-details.factory.ts create mode 100644 libs/api/rescue-station-manager/src/lib/core/event/rescue-station-signed-off.event.ts create mode 100644 libs/api/rescue-station-manager/src/lib/core/event/rescue-station-signed-on.event.ts create mode 100644 libs/api/rescue-station-manager/src/lib/core/event/signed-in-rescue-station-updated.event.ts create mode 100644 libs/api/rescue-station-manager/src/lib/infra/argument/rescue-station.argument.ts create mode 100644 libs/api/rescue-station-manager/src/lib/infra/argument/update-rescue-station.argument.ts create mode 100644 libs/api/rescue-station-manager/src/lib/infra/controller/rescue-station-subscription.resolver.spec.ts create mode 100644 libs/api/rescue-station-manager/src/lib/infra/controller/rescue-station-subscription.resolver.ts create mode 100644 libs/api/rescue-station-manager/src/lib/infra/controller/rescue-station.resolver.spec.ts create mode 100644 libs/api/rescue-station-manager/src/lib/infra/controller/rescue-station.resolver.ts create mode 100644 libs/api/rescue-station-manager/src/lib/infra/rescue-station-manager.module.ts create mode 100644 libs/api/rescue-station-manager/tsconfig.json create mode 100644 libs/api/rescue-station-manager/tsconfig.lib.json create mode 100644 libs/api/rescue-station-manager/tsconfig.spec.json diff --git a/apps/api/src/app/app.module.ts b/apps/api/src/app/app.module.ts index 2c2e008a..9e210956 100644 --- a/apps/api/src/app/app.module.ts +++ b/apps/api/src/app/app.module.ts @@ -12,6 +12,7 @@ import { DeploymentModule } from '@kordis/api/deployment'; import { ObservabilityModule } from '@kordis/api/observability'; import { OrganizationModule } from '@kordis/api/organization'; import { ProtocolModule } from '@kordis/api/protocol'; +import { RescueStationManagerModule } from '@kordis/api/rescue-station-manager'; import { DataLoaderContainer, DataLoaderContextProvider, @@ -40,6 +41,7 @@ const FEATURE_MODULES = [ UnitModule, TetraModule, DeploymentModule, + RescueStationManagerModule, ]; const SAGA_MODULES = [UnitsSagaModule]; const UTILITY_MODULES = [ diff --git a/libs/api/protocol/src/index.ts b/libs/api/protocol/src/index.ts index c2928b66..984dcb5a 100644 --- a/libs/api/protocol/src/index.ts +++ b/libs/api/protocol/src/index.ts @@ -1,2 +1,8 @@ export * from './lib/protocol.module'; +export { CreateRescueStationSignOnMessageCommand } from './lib/core/command/rescue-station/create-rescue-station-sign-on-message.command'; +export { CreateRescueStationSignOffMessageCommand } from './lib/core/command/rescue-station/create-rescue-station-sign-off-message.command'; +export { CreateRescueStationUpdateMessageCommand } from './lib/core/command/rescue-station/create-rescue-station-update-message.command'; export { BaseCreateMessageArgs } from './lib/infra/controller/base-create-message.args'; +export { MessageUnit } from './lib/core/entity/partials/unit-partial.entity'; +export { MessageCommandRescueStationDetails } from './lib/core/command/rescue-station/message-command-rescue-station-details.model'; +export { UnitInput } from './lib/infra/view-model/unit-input.view-model'; diff --git a/libs/api/protocol/src/lib/core/command/create-communication-message.command.spec.ts b/libs/api/protocol/src/lib/core/command/create-communication-message.command.spec.ts index dad3058b..1bf35b59 100644 --- a/libs/api/protocol/src/lib/core/command/create-communication-message.command.spec.ts +++ b/libs/api/protocol/src/lib/core/command/create-communication-message.command.spec.ts @@ -1,7 +1,6 @@ import { createMock } from '@golevelup/ts-jest'; import { EventBus } from '@nestjs/cqrs'; import { plainToInstance } from 'class-transformer'; -import { before } from 'node:test'; import { AuthUser } from '@kordis/shared/model'; @@ -23,7 +22,7 @@ describe('CreateCommunicationMessageCommand', () => { const repositoryMock = createMock(); const eventBusMock = createMock(); - before(() => { + beforeAll(() => { jest.useFakeTimers({ now: new Date(0) }); }); diff --git a/libs/api/protocol/src/lib/core/command/helper/rescue-station-message.factory.spec.ts b/libs/api/protocol/src/lib/core/command/helper/rescue-station-message.factory.spec.ts new file mode 100644 index 00000000..8d052e07 --- /dev/null +++ b/libs/api/protocol/src/lib/core/command/helper/rescue-station-message.factory.spec.ts @@ -0,0 +1,125 @@ +import { plainToClass, plainToInstance } from 'class-transformer'; + +import { AuthUser } from '@kordis/shared/model'; + +import { UserProducer } from '../../entity/partials/producer-partial.entity'; +import { + RegisteredUnit, + UnknownUnit, +} from '../../entity/partials/unit-partial.entity'; +import { RescueStationMessagePayload } from '../../entity/protocol-entries/rescue-station/rescue-station-message-payload.entity'; +import { RescueStationSignOnMessage } from '../../entity/protocol-entries/rescue-station/rescue-station-sign-on-message.entity'; +import { RescueStationUpdateMessage } from '../../entity/protocol-entries/rescue-station/rescue-station-update-message.entity'; +import { CreateRescueStationSignOnMessageCommand } from '../rescue-station/create-rescue-station-sign-on-message.command'; +import { CreateRescueStationUpdateMessageCommand } from '../rescue-station/create-rescue-station-update-message.command'; +import { RescueStationMessageFactory } from './rescue-station-message.factory'; + +const RESCUE_STATION_DETAILS = Object.freeze({ + id: 'rescueStationId', + name: 'rescueStationName', + callSign: 'rescueStationCallSign', + strength: { + leaders: 1, + subLeaders: 1, + helpers: 1, + }, + units: [{ name: 'unitName1', callSign: 'unitCallSign1', id: 'unitId1' }], + alertGroups: [ + { + id: 'alertGroupId', + name: 'alertGroupName', + units: [{ name: 'unitName2', callSign: 'unitCallSign2', id: 'unitId2' }], + }, + ], +}); +const AUTH_USER = Object.freeze({ + id: 'userId', + organizationId: 'organizationId', + firstName: 'firstName', + lastName: 'lastName', +} as AuthUser); +const SENDING_TIME = new Date(); +const MESSAGE = Object.freeze({ + orgId: 'organizationId', + time: SENDING_TIME, + sender: plainToInstance(RegisteredUnit, { + unit: { id: 'knownSenderUnit' }, + }), + recipient: plainToInstance(UnknownUnit, { name: 'unknownReceivingUnit' }), + channel: 'channel', + producer: plainToInstance(UserProducer, { + userId: 'userId', + firstName: 'firstName', + lastName: 'lastName', + }), + payload: plainToClass(RescueStationMessagePayload, { + rescueStationId: 'rescueStationId', + rescueStationName: 'rescueStationName', + rescueStationCallSign: 'rescueStationCallSign', + strength: { + leaders: 1, + subLeaders: 1, + helpers: 1, + }, + units: [{ name: 'unitName1', callSign: 'unitCallSign1', id: 'unitId1' }], + alertGroups: [ + { + id: 'alertGroupId', + name: 'alertGroupName', + units: [ + { name: 'unitName2', callSign: 'unitCallSign2', id: 'unitId2' }, + ], + }, + ], + }), +}); + +describe('RescueStationMessageFactory', () => { + let factory: RescueStationMessageFactory; + + beforeEach(() => { + factory = new RescueStationMessageFactory(); + }); + + it('should create RescueStationSignOnMessage from command', async () => { + const command = new CreateRescueStationSignOnMessageCommand( + SENDING_TIME, + { unit: { id: 'knownSenderUnit' } }, + { name: 'unknownReceivingUnit' }, + RESCUE_STATION_DETAILS, + 'channel', + AUTH_USER, + ); + + const message = await factory.createSignOnMessageFromCommand(command); + const expectedMessage = plainToInstance( + RescueStationSignOnMessage, + MESSAGE, + ); + (expectedMessage as any).createdAt = expect.any(Date); + expectedMessage.searchableText = + 'anmeldung rettungswache rescueStationName rescueStationCallSign stärke 1/1/1/3 einheiten unitName1 unitCallSign1 alarmgruppen alertGroupName unitName2 unitCallSign2'; + expect(message).toEqual(expectedMessage); + }); + + it('should create RescueStationUpdateMessage from command', async () => { + const command = new CreateRescueStationUpdateMessageCommand( + SENDING_TIME, + { unit: { id: 'knownSenderUnit' } }, + { name: 'unknownReceivingUnit' }, + RESCUE_STATION_DETAILS, + 'channel', + AUTH_USER, + ); + + const message = await factory.createUpdateMessageFromCommand(command); + const expectedMessage = plainToInstance( + RescueStationUpdateMessage, + MESSAGE, + ); + (expectedMessage as any).createdAt = expect.any(Date); + expectedMessage.searchableText = + 'nachmeldung rettungswache rescueStationName rescueStationCallSign stärke 1/1/1/3 einheiten unitName1 unitCallSign1 alarmgruppen alertGroupName unitName2 unitCallSign2'; + expect(message).toEqual(expectedMessage); + }); +}); diff --git a/libs/api/protocol/src/lib/core/command/helper/rescue-station-message.factory.ts b/libs/api/protocol/src/lib/core/command/helper/rescue-station-message.factory.ts new file mode 100644 index 00000000..adf286b3 --- /dev/null +++ b/libs/api/protocol/src/lib/core/command/helper/rescue-station-message.factory.ts @@ -0,0 +1,75 @@ +import { Injectable } from '@nestjs/common'; + +import { RescueStationMessagePayload } from '../../entity/protocol-entries/rescue-station/rescue-station-message-payload.entity'; +import { RescueStationSignOnMessage } from '../../entity/protocol-entries/rescue-station/rescue-station-sign-on-message.entity'; +import { RescueStationUpdateMessage } from '../../entity/protocol-entries/rescue-station/rescue-station-update-message.entity'; +import { CreateRescueStationSignOnMessageCommand } from '../rescue-station/create-rescue-station-sign-on-message.command'; +import { CreateRescueStationUpdateMessageCommand } from '../rescue-station/create-rescue-station-update-message.command'; +import { setProtocolMessageBaseFromCommandHelper } from './set-protocol-message-base-from-command.helper'; + +@Injectable() +export class RescueStationMessageFactory { + async createSignOnMessageFromCommand( + cmd: CreateRescueStationSignOnMessageCommand, + ): Promise { + const msg = new RescueStationSignOnMessage(); + msg.searchableText = this.makeSearchableText('anmeldung', cmd); + setProtocolMessageBaseFromCommandHelper(cmd, msg); + msg.payload = this.makeMessagePayload(cmd); + + return msg; + } + + async createUpdateMessageFromCommand( + cmd: CreateRescueStationUpdateMessageCommand, + ): Promise { + const msg = new RescueStationUpdateMessage(); + msg.searchableText = this.makeSearchableText('nachmeldung', cmd); + setProtocolMessageBaseFromCommandHelper(cmd, msg); + msg.payload = this.makeMessagePayload(cmd); + + return msg; + } + + private makeMessagePayload( + cmd: + | CreateRescueStationSignOnMessageCommand + | CreateRescueStationUpdateMessageCommand, + ): RescueStationMessagePayload { + const msgPayload = new RescueStationMessagePayload(); + msgPayload.rescueStationId = cmd.rescueStation.id; + msgPayload.rescueStationName = cmd.rescueStation.name; + msgPayload.rescueStationCallSign = cmd.rescueStation.callSign; + msgPayload.strength = cmd.rescueStation.strength; + msgPayload.units = cmd.rescueStation.units; + msgPayload.alertGroups = cmd.rescueStation.alertGroups; + return msgPayload; + } + + private makeSearchableText( + actionPrefix: string, + { rescueStation }: CreateRescueStationSignOnMessageCommand, + ): string { + const { strength, units, alertGroups, name, callSign } = rescueStation; + const strengthString = `${strength.leaders}/${strength.subLeaders}/${strength.helpers}/${strength.leaders + strength.subLeaders + strength.helpers}`; + + const unitStrings = units.map( + ({ name, callSign }) => `${name} ${callSign}`, + ); + const unitString = unitStrings.length + ? `einheiten ${unitStrings.join(', ')} ` + : ''; + + const alertGroupStrings = alertGroups.map(({ name, units }) => { + const unitNames = units + .map(({ name, callSign }) => `${name} ${callSign}`) + .join(', '); + return `${name} ${unitNames}`; + }); + const alertGroupString = alertGroupStrings.length + ? `alarmgruppen ${alertGroupStrings.join(', ')}` + : ''; + + return `${actionPrefix} rettungswache ${name} ${callSign} stärke ${strengthString} ${unitString}${alertGroupString}`.trimEnd(); + } +} diff --git a/libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-sign-off-message.command.spec.ts b/libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-sign-off-message.command.spec.ts new file mode 100644 index 00000000..7305260b --- /dev/null +++ b/libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-sign-off-message.command.spec.ts @@ -0,0 +1,95 @@ +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { EventBus } from '@nestjs/cqrs'; +import { plainToClass, plainToInstance } from 'class-transformer'; + +import { AuthUser } from '@kordis/shared/model'; + +import { UserProducer } from '../../entity/partials/producer-partial.entity'; +import { + RegisteredUnit, + UnknownUnit, +} from '../../entity/partials/unit-partial.entity'; +import { + RescueStationSignOffMessage, + RescueStationSignOffMessagePayload, +} from '../../entity/protocol-entries/rescue-station/rescue-station-sign-off-message.entity'; +import { ProtocolEntryRepository } from '../../repository/protocol-entry.repository'; +import { + CreateRescueStationSignOffMessageCommand, + CreateRescueStationSignOffMessageHandler, +} from './create-rescue-station-sign-off-message.command'; + +describe('CreateRescueStationSignOffMessageHandler', () => { + let mockRepository: DeepMocked; + let handler: CreateRescueStationSignOffMessageHandler; + + let mockEventBus: DeepMocked; + + beforeEach(async () => { + mockEventBus = createMock(); + mockRepository = createMock(); + handler = new CreateRescueStationSignOffMessageHandler( + mockRepository, + mockEventBus, + ); + }); + + it('should create a message and publish an event', async () => { + const sendingTime = new Date(); + const expectedMessage = plainToInstance(RescueStationSignOffMessage, { + orgId: 'organizationId', + time: sendingTime, + sender: plainToInstance(RegisteredUnit, { + unit: { id: 'knownSenderUnit' }, + }), + recipient: plainToInstance(UnknownUnit, { name: 'unknownReceivingUnit' }), + channel: 'channel', + producer: plainToInstance(UserProducer, { + userId: 'userId', + firstName: 'firstName', + lastName: 'lastName', + }), + payload: plainToClass(RescueStationSignOffMessagePayload, { + rescueStationId: 'rescueStationId', + rescueStationName: 'rescueStationName', + rescueStationCallSign: 'rescueStationCallSign', + }), + searchableText: `ausmeldung rettungswache rescueStationName rescueStationCallSign`, + }); + (expectedMessage as any).createdAt = expect.any(Date); + + mockRepository.create.mockResolvedValueOnce(expectedMessage); + + const res = await handler.execute( + new CreateRescueStationSignOffMessageCommand( + sendingTime, + plainToInstance(RegisteredUnit, { + unit: { id: 'knownSenderUnit' }, + }), + plainToInstance(UnknownUnit, { + name: 'unknownReceivingUnit', + }), + { + id: 'rescueStationId', + name: 'rescueStationName', + callSign: 'rescueStationCallSign', + }, + 'channel', + { + id: 'userId', + organizationId: 'organizationId', + firstName: 'firstName', + lastName: 'lastName', + } as AuthUser, + ), + ); + + expect(mockRepository.create).toHaveBeenCalledWith(expectedMessage); + expect(res).toEqual(expectedMessage); + expect(mockEventBus.publish).toHaveBeenCalledWith( + expect.objectContaining({ + protocolEntry: expectedMessage, + }), + ); + }); +}); diff --git a/libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-sign-off-message.command.ts b/libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-sign-off-message.command.ts new file mode 100644 index 00000000..d6aced8d --- /dev/null +++ b/libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-sign-off-message.command.ts @@ -0,0 +1,87 @@ +import { Inject, Logger } from '@nestjs/common'; +import { CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs'; + +import type { KordisLogger } from '@kordis/api/observability'; +import { AuthUser } from '@kordis/shared/model'; + +import { MessageUnit } from '../../entity/partials/unit-partial.entity'; +import { + RescueStationSignOffMessage, + RescueStationSignOffMessagePayload, +} from '../../entity/protocol-entries/rescue-station/rescue-station-sign-off-message.entity'; +import { ProtocolEntryCreatedEvent } from '../../event/protocol-entry-created.event'; +import { + PROTOCOL_ENTRY_REPOSITORY, + ProtocolEntryRepository, +} from '../../repository/protocol-entry.repository'; +import { BaseCreateMessageCommand } from '../base-create-message.command'; +import { setProtocolMessageBaseFromCommandHelper } from '../helper/set-protocol-message-base-from-command.helper'; + +export class CreateRescueStationSignOffMessageCommand + implements BaseCreateMessageCommand +{ + constructor( + readonly time: Date, + readonly sender: MessageUnit, + readonly recipient: MessageUnit, + readonly rescueStation: { + id: string; + name: string; + callSign: string; + }, + readonly channel: string, + readonly requestUser: AuthUser, + ) {} +} + +@CommandHandler(CreateRescueStationSignOffMessageCommand) +export class CreateRescueStationSignOffMessageHandler + implements ICommandHandler +{ + private readonly logger: KordisLogger = new Logger( + CreateRescueStationSignOffMessageCommand.name, + ); + + constructor( + @Inject(PROTOCOL_ENTRY_REPOSITORY) + private readonly repository: ProtocolEntryRepository, + private readonly eventBus: EventBus, + ) {} + + async execute( + command: CreateRescueStationSignOffMessageCommand, + ): Promise { + let msg = this.makeMessageFromCommand(command); + + await msg.validOrThrow(); + + msg = await this.repository.create(msg); + + this.logger.log('Rescue station sign off message created', { + commMsgId: msg.id, + }); + + this.eventBus.publish( + new ProtocolEntryCreatedEvent(command.requestUser.organizationId, msg), + ); + + return msg; + } + + private makeMessageFromCommand( + cmd: CreateRescueStationSignOffMessageCommand, + ): RescueStationSignOffMessage { + const msgPayload = new RescueStationSignOffMessagePayload(); + msgPayload.rescueStationId = cmd.rescueStation.id; + msgPayload.rescueStationName = cmd.rescueStation.name; + msgPayload.rescueStationCallSign = cmd.rescueStation.callSign; + + const msg = new RescueStationSignOffMessage(); + setProtocolMessageBaseFromCommandHelper(cmd, msg); + + msg.payload = msgPayload; + msg.searchableText = `ausmeldung rettungswache ${cmd.rescueStation.name} ${cmd.rescueStation.callSign}`; + + return msg; + } +} diff --git a/libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-sign-on-message.command.spec.ts b/libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-sign-on-message.command.spec.ts new file mode 100644 index 00000000..a6620487 --- /dev/null +++ b/libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-sign-on-message.command.spec.ts @@ -0,0 +1,129 @@ +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { EventBus } from '@nestjs/cqrs'; +import { plainToClass, plainToInstance } from 'class-transformer'; + +import { AuthUser } from '@kordis/shared/model'; + +import { UserProducer } from '../../entity/partials/producer-partial.entity'; +import { + RegisteredUnit, + UnknownUnit, +} from '../../entity/partials/unit-partial.entity'; +import { RescueStationMessagePayload } from '../../entity/protocol-entries/rescue-station/rescue-station-message-payload.entity'; +import { RescueStationSignOnMessage } from '../../entity/protocol-entries/rescue-station/rescue-station-sign-on-message.entity'; +import { ProtocolEntryRepository } from '../../repository/protocol-entry.repository'; +import { RescueStationMessageFactory } from '../helper/rescue-station-message.factory'; +import { + CreateRescueStationSignOnMessageCommand, + CreateRescueStationSignOnMessageHandler, +} from './create-rescue-station-sign-on-message.command'; + +describe('CreateRescueStationSignOnMessageHandler', () => { + let mockRepository: DeepMocked; + let handler: CreateRescueStationSignOnMessageHandler; + let mockEventBus: DeepMocked; + + beforeEach(async () => { + mockEventBus = createMock(); + mockRepository = createMock(); + + handler = new CreateRescueStationSignOnMessageHandler( + mockRepository, + mockEventBus, + new RescueStationMessageFactory(), + ); + }); + + it('should create a message and publish an event', async () => { + const sendingTime = new Date(); + const expectedMessage = plainToInstance(RescueStationSignOnMessage, { + orgId: 'organizationId', + time: sendingTime, + sender: plainToInstance(RegisteredUnit, { + unit: { id: 'knownSenderUnit' }, + }), + recipient: plainToInstance(UnknownUnit, { name: 'unknownReceivingUnit' }), + channel: 'channel', + producer: plainToInstance(UserProducer, { + userId: 'userId', + firstName: 'firstName', + lastName: 'lastName', + }), + payload: plainToClass(RescueStationMessagePayload, { + rescueStationId: 'rescueStationId', + rescueStationName: 'rescueStationName', + rescueStationCallSign: 'rescueStationCallSign', + strength: { + leaders: 1, + subLeaders: 1, + helpers: 1, + }, + units: [ + { name: 'unitName1', callSign: 'unitCallSign1', id: 'unitId1' }, + ], + alertGroups: [ + { + id: 'alertGroupId', + name: 'alertGroupName', + units: [ + { name: 'unitName2', callSign: 'unitCallSign2', id: 'unitId2' }, + ], + }, + ], + }), + searchableText: `anmeldung rettungswache rescueStationName rescueStationCallSign stärke 1/1/1/3 einheiten unitName1 unitCallSign1 alarmgruppen alertGroupName unitName2 unitCallSign2`, + }); + (expectedMessage as any).createdAt = expect.any(Date); + + mockRepository.create.mockResolvedValueOnce(expectedMessage); + + const res = await handler.execute( + new CreateRescueStationSignOnMessageCommand( + sendingTime, + plainToInstance(RegisteredUnit, { + unit: { id: 'knownSenderUnit' }, + }), + plainToInstance(UnknownUnit, { + name: 'unknownReceivingUnit', + }), + { + id: 'rescueStationId', + name: 'rescueStationName', + callSign: 'rescueStationCallSign', + strength: { + leaders: 1, + subLeaders: 1, + helpers: 1, + }, + units: [ + { name: 'unitName1', callSign: 'unitCallSign1', id: 'unitId1' }, + ], + alertGroups: [ + { + id: 'alertGroupId', + name: 'alertGroupName', + units: [ + { name: 'unitName2', callSign: 'unitCallSign2', id: 'unitId2' }, + ], + }, + ], + }, + 'channel', + { + id: 'userId', + organizationId: 'organizationId', + firstName: 'firstName', + lastName: 'lastName', + } as AuthUser, + ), + ); + + expect(mockRepository.create).toHaveBeenCalledWith(expectedMessage); + expect(res).toEqual(expectedMessage); + expect(mockEventBus.publish).toHaveBeenCalledWith( + expect.objectContaining({ + protocolEntry: expectedMessage, + }), + ); + }); +}); diff --git a/libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-sign-on-message.command.ts b/libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-sign-on-message.command.ts new file mode 100644 index 00000000..70a7e29a --- /dev/null +++ b/libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-sign-on-message.command.ts @@ -0,0 +1,68 @@ +import { Inject, Logger } from '@nestjs/common'; +import { CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs'; + +import type { KordisLogger } from '@kordis/api/observability'; +import { AuthUser } from '@kordis/shared/model'; + +import { MessageUnit } from '../../entity/partials/unit-partial.entity'; +import { RescueStationSignOnMessage } from '../../entity/protocol-entries/rescue-station/rescue-station-sign-on-message.entity'; +import { ProtocolEntryCreatedEvent } from '../../event/protocol-entry-created.event'; +import { + PROTOCOL_ENTRY_REPOSITORY, + ProtocolEntryRepository, +} from '../../repository/protocol-entry.repository'; +import { BaseCreateMessageCommand } from '../base-create-message.command'; +import { RescueStationMessageFactory } from '../helper/rescue-station-message.factory'; +import { MessageCommandRescueStationDetails } from './message-command-rescue-station-details.model'; + +export class CreateRescueStationSignOnMessageCommand + implements BaseCreateMessageCommand +{ + constructor( + readonly time: Date, + readonly sender: MessageUnit, + readonly recipient: MessageUnit, + readonly rescueStation: MessageCommandRescueStationDetails, + readonly channel: string, + readonly requestUser: AuthUser, + ) {} +} + +@CommandHandler(CreateRescueStationSignOnMessageCommand) +export class CreateRescueStationSignOnMessageHandler + implements ICommandHandler +{ + private readonly logger: KordisLogger = new Logger( + CreateRescueStationSignOnMessageHandler.name, + ); + + constructor( + @Inject(PROTOCOL_ENTRY_REPOSITORY) + private readonly repository: ProtocolEntryRepository, + private readonly eventBus: EventBus, + private readonly rescueStationMessageFactory: RescueStationMessageFactory, + ) {} + + async execute( + cmd: CreateRescueStationSignOnMessageCommand, + ): Promise { + let msg = + await this.rescueStationMessageFactory.createSignOnMessageFromCommand( + cmd, + ); + + await msg.validOrThrow(); + + msg = await this.repository.create(msg); + + this.logger.log('Rescue station sign on message created', { + msgId: msg.id, + }); + + this.eventBus.publish( + new ProtocolEntryCreatedEvent(cmd.requestUser.organizationId, msg), + ); + + return msg; + } +} diff --git a/libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-update-message.command.spec.ts b/libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-update-message.command.spec.ts new file mode 100644 index 00000000..8a95d1ff --- /dev/null +++ b/libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-update-message.command.spec.ts @@ -0,0 +1,128 @@ +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { EventBus } from '@nestjs/cqrs'; +import { plainToClass, plainToInstance } from 'class-transformer'; + +import { AuthUser } from '@kordis/shared/model'; + +import { UserProducer } from '../../entity/partials/producer-partial.entity'; +import { + RegisteredUnit, + UnknownUnit, +} from '../../entity/partials/unit-partial.entity'; +import { RescueStationMessagePayload } from '../../entity/protocol-entries/rescue-station/rescue-station-message-payload.entity'; +import { RescueStationUpdateMessage } from '../../entity/protocol-entries/rescue-station/rescue-station-update-message.entity'; +import { ProtocolEntryRepository } from '../../repository/protocol-entry.repository'; +import { RescueStationMessageFactory } from '../helper/rescue-station-message.factory'; +import { + CreateRescueStationUpdateMessageCommand, + CreateRescueStationUpdateMessageHandler, +} from './create-rescue-station-update-message.command'; + +describe('CreateRescueStationUpdateMessageHandler', () => { + let mockRepository: DeepMocked; + let handler: CreateRescueStationUpdateMessageHandler; + let mockEventBus: DeepMocked; + + beforeEach(async () => { + mockEventBus = createMock(); + mockRepository = createMock(); + handler = new CreateRescueStationUpdateMessageHandler( + mockRepository, + mockEventBus, + new RescueStationMessageFactory(), + ); + }); + + it('should create a message and publish an event', async () => { + const sendingTime = new Date(); + const expectedMessage = plainToInstance(RescueStationUpdateMessage, { + orgId: 'organizationId', + time: sendingTime, + sender: plainToInstance(RegisteredUnit, { + unit: { id: 'knownSenderUnit' }, + }), + recipient: plainToInstance(UnknownUnit, { name: 'unknownReceivingUnit' }), + channel: 'channel', + producer: plainToInstance(UserProducer, { + userId: 'userId', + firstName: 'firstName', + lastName: 'lastName', + }), + payload: plainToClass(RescueStationMessagePayload, { + rescueStationId: 'rescueStationId', + rescueStationName: 'rescueStationName', + rescueStationCallSign: 'rescueStationCallSign', + strength: { + leaders: 1, + subLeaders: 1, + helpers: 1, + }, + units: [ + { name: 'unitName1', callSign: 'unitCallSign1', id: 'unitId1' }, + ], + alertGroups: [ + { + id: 'alertGroupId', + name: 'alertGroupName', + units: [ + { name: 'unitName2', callSign: 'unitCallSign2', id: 'unitId2' }, + ], + }, + ], + }), + searchableText: `nachmeldung rettungswache rescueStationName rescueStationCallSign stärke 1/1/1/3 einheiten unitName1 unitCallSign1 alarmgruppen alertGroupName unitName2 unitCallSign2`, + }); + (expectedMessage as any).createdAt = expect.any(Date); + + mockRepository.create.mockResolvedValueOnce(expectedMessage); + + const res = await handler.execute( + new CreateRescueStationUpdateMessageCommand( + sendingTime, + plainToInstance(RegisteredUnit, { + unit: { id: 'knownSenderUnit' }, + }), + plainToInstance(UnknownUnit, { + name: 'unknownReceivingUnit', + }), + { + id: 'rescueStationId', + name: 'rescueStationName', + callSign: 'rescueStationCallSign', + strength: { + leaders: 1, + subLeaders: 1, + helpers: 1, + }, + units: [ + { name: 'unitName1', callSign: 'unitCallSign1', id: 'unitId1' }, + ], + alertGroups: [ + { + id: 'alertGroupId', + name: 'alertGroupName', + units: [ + { name: 'unitName2', callSign: 'unitCallSign2', id: 'unitId2' }, + ], + }, + ], + }, + 'channel', + { + id: 'userId', + organizationId: 'organizationId', + firstName: 'firstName', + lastName: 'lastName', + } as AuthUser, + ), + ); + + expect(mockRepository.create).toHaveBeenCalledWith(expectedMessage); + expect(res).toEqual(expectedMessage); + expect(mockEventBus.publish).toHaveBeenCalledWith( + expect.objectContaining({ + protocolEntry: expectedMessage, + }), + ); + }); +}); diff --git a/libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-update-message.command.ts b/libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-update-message.command.ts new file mode 100644 index 00000000..83f996c5 --- /dev/null +++ b/libs/api/protocol/src/lib/core/command/rescue-station/create-rescue-station-update-message.command.ts @@ -0,0 +1,68 @@ +import { Inject, Logger } from '@nestjs/common'; +import { CommandHandler, EventBus, ICommandHandler } from '@nestjs/cqrs'; + +import type { KordisLogger } from '@kordis/api/observability'; +import { AuthUser } from '@kordis/shared/model'; + +import { MessageUnit } from '../../entity/partials/unit-partial.entity'; +import { RescueStationUpdateMessage } from '../../entity/protocol-entries/rescue-station/rescue-station-update-message.entity'; +import { ProtocolEntryCreatedEvent } from '../../event/protocol-entry-created.event'; +import { + PROTOCOL_ENTRY_REPOSITORY, + ProtocolEntryRepository, +} from '../../repository/protocol-entry.repository'; +import { BaseCreateMessageCommand } from '../base-create-message.command'; +import { RescueStationMessageFactory } from '../helper/rescue-station-message.factory'; +import { MessageCommandRescueStationDetails } from './message-command-rescue-station-details.model'; + +export class CreateRescueStationUpdateMessageCommand + implements BaseCreateMessageCommand +{ + constructor( + readonly time: Date, + readonly sender: MessageUnit, + readonly recipient: MessageUnit, + readonly rescueStation: MessageCommandRescueStationDetails, + readonly channel: string, + readonly requestUser: AuthUser, + ) {} +} + +@CommandHandler(CreateRescueStationUpdateMessageCommand) +export class CreateRescueStationUpdateMessageHandler + implements ICommandHandler +{ + private readonly logger: KordisLogger = new Logger( + CreateRescueStationUpdateMessageHandler.name, + ); + + constructor( + @Inject(PROTOCOL_ENTRY_REPOSITORY) + private readonly repository: ProtocolEntryRepository, + private readonly eventBus: EventBus, + private readonly rescueStationMessageFactory: RescueStationMessageFactory, + ) {} + + async execute( + cmd: CreateRescueStationUpdateMessageCommand, + ): Promise { + let msg = + await this.rescueStationMessageFactory.createUpdateMessageFromCommand( + cmd, + ); + + await msg.validOrThrow(); + + msg = await this.repository.create(msg); + + this.logger.log('Rescue station update message created', { + msgId: msg.id, + }); + + this.eventBus.publish( + new ProtocolEntryCreatedEvent(cmd.requestUser.organizationId, msg), + ); + + return msg; + } +} diff --git a/libs/api/protocol/src/lib/core/command/rescue-station/message-command-rescue-station-details.model.ts b/libs/api/protocol/src/lib/core/command/rescue-station/message-command-rescue-station-details.model.ts new file mode 100644 index 00000000..d0e04765 --- /dev/null +++ b/libs/api/protocol/src/lib/core/command/rescue-station/message-command-rescue-station-details.model.ts @@ -0,0 +1,24 @@ +export interface MessageCommandRescueStationDetails { + id: string; + name: string; + callSign: string; + strength: { + leaders: number; + subLeaders: number; + helpers: number; + }; + units: { + id: string; + name: string; + callSign: string; + }[]; + alertGroups: { + id: string; + name: string; + units: { + id: string; + name: string; + callSign: string; + }[]; + }[]; +} diff --git a/libs/api/protocol/src/lib/core/entity/partials/unit-partial.entity.ts b/libs/api/protocol/src/lib/core/entity/partials/unit-partial.entity.ts index 0f483273..e33e2e51 100644 --- a/libs/api/protocol/src/lib/core/entity/partials/unit-partial.entity.ts +++ b/libs/api/protocol/src/lib/core/entity/partials/unit-partial.entity.ts @@ -3,6 +3,8 @@ import { Field, InputType, ObjectType, createUnionType } from '@nestjs/graphql'; import { UnitViewModel } from '@kordis/api/unit'; +export type MessageUnit = typeof UnitUnion; + @ObjectType({ isAbstract: true }) export abstract class Unit {} diff --git a/libs/api/protocol/src/lib/core/entity/protocol-entries/rescue-station/rescue-station-message-payload.entity.ts b/libs/api/protocol/src/lib/core/entity/protocol-entries/rescue-station/rescue-station-message-payload.entity.ts new file mode 100644 index 00000000..fd10357e --- /dev/null +++ b/libs/api/protocol/src/lib/core/entity/protocol-entries/rescue-station/rescue-station-message-payload.entity.ts @@ -0,0 +1,74 @@ +import { AutoMap } from '@automapper/classes'; +import { Field, ObjectType } from '@nestjs/graphql'; + +@ObjectType() +export class RescueStationMessageStrength { + @Field() + @AutoMap() + leaders: number; + + @Field() + @AutoMap() + subLeaders: number; + + @Field() + @AutoMap() + helpers: number; +} + +@ObjectType() +export class RescueStationMessageAssignedUnit { + @Field() + @AutoMap() + id: string; + + @Field() + @AutoMap() + name: string; + + @Field() + @AutoMap() + callSign: string; +} + +@ObjectType() +export class RescueStationMessageAssignedAlertGroup { + @Field() + @AutoMap() + id: string; + + @Field() + @AutoMap() + name: string; + + @Field(() => [RescueStationMessageAssignedUnit]) + @AutoMap(() => [RescueStationMessageAssignedUnit]) + units: RescueStationMessageAssignedUnit[]; +} + +@ObjectType() +export class RescueStationMessagePayload { + @Field() + @AutoMap() + rescueStationId: string; + + @Field() + @AutoMap() + rescueStationName: string; + + @Field() + @AutoMap() + rescueStationCallSign: string; + + @Field() + @AutoMap() + strength: RescueStationMessageStrength; + + @Field(() => [RescueStationMessageAssignedUnit]) + @AutoMap(() => [RescueStationMessageAssignedUnit]) + units: RescueStationMessageAssignedUnit[]; + + @Field(() => [RescueStationMessageAssignedAlertGroup]) + @AutoMap(() => [RescueStationMessageAssignedAlertGroup]) + alertGroups: RescueStationMessageAssignedAlertGroup[]; +} diff --git a/libs/api/protocol/src/lib/core/entity/protocol-entries/rescue-station/rescue-station-sign-off-message.entity.ts b/libs/api/protocol/src/lib/core/entity/protocol-entries/rescue-station/rescue-station-sign-off-message.entity.ts new file mode 100644 index 00000000..9f043aee --- /dev/null +++ b/libs/api/protocol/src/lib/core/entity/protocol-entries/rescue-station/rescue-station-sign-off-message.entity.ts @@ -0,0 +1,26 @@ +import { AutoMap } from '@automapper/classes'; +import { Field, ObjectType } from '@nestjs/graphql'; + +import { ProtocolMessageEntryBase } from '../protocol-entry-base.entity'; + +@ObjectType() +export class RescueStationSignOffMessagePayload { + @Field() + @AutoMap() + rescueStationId: string; + + @Field() + @AutoMap() + rescueStationName: string; + + @Field() + @AutoMap() + rescueStationCallSign: string; +} + +@ObjectType() +export class RescueStationSignOffMessage extends ProtocolMessageEntryBase { + @Field(() => RescueStationSignOffMessagePayload) + @AutoMap() + payload: RescueStationSignOffMessagePayload; +} diff --git a/libs/api/protocol/src/lib/core/entity/protocol-entries/rescue-station/rescue-station-sign-on-message.entity.ts b/libs/api/protocol/src/lib/core/entity/protocol-entries/rescue-station/rescue-station-sign-on-message.entity.ts new file mode 100644 index 00000000..cb80d394 --- /dev/null +++ b/libs/api/protocol/src/lib/core/entity/protocol-entries/rescue-station/rescue-station-sign-on-message.entity.ts @@ -0,0 +1,12 @@ +import { AutoMap } from '@automapper/classes'; +import { Field, ObjectType } from '@nestjs/graphql'; + +import { ProtocolMessageEntryBase } from '../protocol-entry-base.entity'; +import { RescueStationMessagePayload } from './rescue-station-message-payload.entity'; + +@ObjectType() +export class RescueStationSignOnMessage extends ProtocolMessageEntryBase { + @Field(() => RescueStationMessagePayload) + @AutoMap() + payload: RescueStationMessagePayload; +} diff --git a/libs/api/protocol/src/lib/core/entity/protocol-entries/rescue-station/rescue-station-update-message.entity.ts b/libs/api/protocol/src/lib/core/entity/protocol-entries/rescue-station/rescue-station-update-message.entity.ts new file mode 100644 index 00000000..ad1105ee --- /dev/null +++ b/libs/api/protocol/src/lib/core/entity/protocol-entries/rescue-station/rescue-station-update-message.entity.ts @@ -0,0 +1,12 @@ +import { AutoMap } from '@automapper/classes'; +import { Field, ObjectType } from '@nestjs/graphql'; + +import { ProtocolMessageEntryBase } from '../protocol-entry-base.entity'; +import { RescueStationMessagePayload } from './rescue-station-message-payload.entity'; + +@ObjectType() +export class RescueStationUpdateMessage extends ProtocolMessageEntryBase { + @Field(() => RescueStationMessagePayload) + @AutoMap() + payload: RescueStationMessagePayload; +} diff --git a/libs/api/protocol/src/lib/infra/schema/communication-message.schema.ts b/libs/api/protocol/src/lib/infra/schema/communication/communication-message.schema.ts similarity index 94% rename from libs/api/protocol/src/lib/infra/schema/communication-message.schema.ts rename to libs/api/protocol/src/lib/infra/schema/communication/communication-message.schema.ts index 61702a02..baeae2c4 100644 --- a/libs/api/protocol/src/lib/infra/schema/communication-message.schema.ts +++ b/libs/api/protocol/src/lib/infra/schema/communication/communication-message.schema.ts @@ -4,7 +4,7 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { ProtocolEntryType, ProtocolMessageEntryBaseDocument, -} from './protocol-entry-base.schema'; +} from '../protocol-entry-base.schema'; @Schema({ _id: false }) export class CommunicationMessagePayloadDocument { diff --git a/libs/api/protocol/src/lib/infra/schema/protocol-entry-base.schema.ts b/libs/api/protocol/src/lib/infra/schema/protocol-entry-base.schema.ts index 41a8b57f..1f20bf7b 100644 --- a/libs/api/protocol/src/lib/infra/schema/protocol-entry-base.schema.ts +++ b/libs/api/protocol/src/lib/infra/schema/protocol-entry-base.schema.ts @@ -20,10 +20,10 @@ import { export enum ProtocolEntryType { COMMUNICATION_MESSAGE_ENTRY = 'COMMUNICATION_MESSAGE_ENTRY', + RESCUE_STATION_SIGN_ON_ENTRY = 'RESCUE_STATION_SIGN_ON_ENTRY', + RESCUE_STATION_UPDATE_ENTRY = 'RESCUE_STATION_UPDATE_ENTRY', + RESCUE_STATION_SIGN_OFF_ENTRY = 'RESCUE_STATION_SIGN_OFF_ENTRY', // UNIT_STATUS_ENTRY = 'UNIT_STATUS_ENTRY', - // RESCUE_STATION_SIGN_ON_ENTRY = 'RESCUE_STATION_SIGN_ON_ENTRY', - // RESCUE_STATION_UPDATE_ENTRY = 'RESCUE_STATION_UPDATE_ENTRY', - // RESCUE_STATION_SIGN_OFF_ENTRY = 'RESCUE_STATION_SIGN_OFF_ENTRY', // OPERATION_STARTED_ENTRY = 'OPERATION_STARTED_ENTRY', // OPERATION_ENDED_ENTRY = 'OPERATION_ENDED_ENTRY', // OPERATION_UNITS_UPDATED_ENTRY = 'OPERATION_UNITS_UPDATED_ENTRY', @@ -42,7 +42,6 @@ export class ProtocolEntryBaseDocument extends BaseDocument { time: Date; @Prop() - @AutoMap() sender: UnitDocument; @Prop() @@ -50,7 +49,6 @@ export class ProtocolEntryBaseDocument extends BaseDocument { searchableText: string; @Prop() - @AutoMap() producer: ProducerDocument; } diff --git a/libs/api/protocol/src/lib/infra/schema/rescue-station/rescue-station-message-payload.schema.ts b/libs/api/protocol/src/lib/infra/schema/rescue-station/rescue-station-message-payload.schema.ts new file mode 100644 index 00000000..4772d3c5 --- /dev/null +++ b/libs/api/protocol/src/lib/infra/schema/rescue-station/rescue-station-message-payload.schema.ts @@ -0,0 +1,74 @@ +import { AutoMap } from '@automapper/classes'; +import { Prop, Schema } from '@nestjs/mongoose'; + +@Schema({ _id: false }) +export class RescueStationMessageStrengthDocument { + @Prop() + @AutoMap() + leaders: number; + + @Prop() + @AutoMap() + subLeaders: number; + + @Prop() + @AutoMap() + helpers: number; +} + +@Schema({ _id: false }) +export class RescueStationMessageAssignedUnitDocument { + @Prop() + @AutoMap() + id: string; + + @Prop() + @AutoMap() + name: string; + + @Prop() + @AutoMap() + callSign: string; +} + +@Schema({ _id: false }) +export class RescueStationMessageAssignedAlertGroupDocument { + @Prop() + @AutoMap() + id: string; + + @Prop() + @AutoMap() + name: string; + + @Prop({ type: [RescueStationMessageAssignedUnitDocument] }) + @AutoMap(() => [RescueStationMessageAssignedUnitDocument]) + units: RescueStationMessageAssignedUnitDocument[]; +} + +@Schema({ _id: false }) +export class RescueStationMessagePayloadDocument { + @Prop() + @AutoMap() + rescueStationId: string; + + @Prop() + @AutoMap() + rescueStationName: string; + + @Prop() + @AutoMap() + rescueStationCallSign: string; + + @Prop({ type: RescueStationMessageStrengthDocument }) + @AutoMap() + strength: RescueStationMessageStrengthDocument; + + @Prop({ type: [RescueStationMessageAssignedUnitDocument] }) + @AutoMap(() => [RescueStationMessageAssignedUnitDocument]) + units: RescueStationMessageAssignedUnitDocument[]; + + @Prop({ type: [RescueStationMessageAssignedAlertGroupDocument] }) + @AutoMap(() => [RescueStationMessageAssignedAlertGroupDocument]) + alertGroups: RescueStationMessageAssignedAlertGroupDocument[]; +} diff --git a/libs/api/protocol/src/lib/infra/schema/rescue-station/rescue-station-sign-off-message.schema.ts b/libs/api/protocol/src/lib/infra/schema/rescue-station/rescue-station-sign-off-message.schema.ts new file mode 100644 index 00000000..83480b8c --- /dev/null +++ b/libs/api/protocol/src/lib/infra/schema/rescue-station/rescue-station-sign-off-message.schema.ts @@ -0,0 +1,35 @@ +import { AutoMap } from '@automapper/classes'; +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; + +import { + ProtocolEntryType, + ProtocolMessageEntryBaseDocument, +} from '../protocol-entry-base.schema'; + +@Schema({ _id: false }) +export class RescueStationSignOffMessagePayloadDocument { + @Prop() + @AutoMap() + rescueStationId: string; + + @Prop() + @AutoMap() + rescueStationName: string; + + @Prop() + @AutoMap() + rescueStationCallSign: string; +} + +@Schema() +export class RescueStationSignOffMessageDocument extends ProtocolMessageEntryBaseDocument { + override type = ProtocolEntryType.RESCUE_STATION_SIGN_OFF_ENTRY; + + @Prop() + @AutoMap() + payload: RescueStationSignOffMessagePayloadDocument; +} + +export const RescueStationSignOffMessageSchema = SchemaFactory.createForClass( + RescueStationSignOffMessageDocument, +); diff --git a/libs/api/protocol/src/lib/infra/schema/rescue-station/rescue-station-sign-on-message.schema.ts b/libs/api/protocol/src/lib/infra/schema/rescue-station/rescue-station-sign-on-message.schema.ts new file mode 100644 index 00000000..c281e13f --- /dev/null +++ b/libs/api/protocol/src/lib/infra/schema/rescue-station/rescue-station-sign-on-message.schema.ts @@ -0,0 +1,21 @@ +import { AutoMap } from '@automapper/classes'; +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; + +import { + ProtocolEntryType, + ProtocolMessageEntryBaseDocument, +} from '../protocol-entry-base.schema'; +import { RescueStationMessagePayloadDocument } from './rescue-station-message-payload.schema'; + +@Schema() +export class RescueStationSignOnMessageDocument extends ProtocolMessageEntryBaseDocument { + override type = ProtocolEntryType.RESCUE_STATION_SIGN_ON_ENTRY; + + @Prop() + @AutoMap() + payload: RescueStationMessagePayloadDocument; +} + +export const RescueStationSignOnMessageSchema = SchemaFactory.createForClass( + RescueStationSignOnMessageDocument, +); diff --git a/libs/api/protocol/src/lib/infra/schema/rescue-station/rescue-station-updated-message.schema.ts b/libs/api/protocol/src/lib/infra/schema/rescue-station/rescue-station-updated-message.schema.ts new file mode 100644 index 00000000..11925145 --- /dev/null +++ b/libs/api/protocol/src/lib/infra/schema/rescue-station/rescue-station-updated-message.schema.ts @@ -0,0 +1,21 @@ +import { AutoMap } from '@automapper/classes'; +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; + +import { + ProtocolEntryType, + ProtocolMessageEntryBaseDocument, +} from '../protocol-entry-base.schema'; +import { RescueStationMessagePayloadDocument } from './rescue-station-message-payload.schema'; + +@Schema() +export class RescueStationUpdateMessageDocument extends ProtocolMessageEntryBaseDocument { + override type = ProtocolEntryType.RESCUE_STATION_UPDATE_ENTRY; + + @Prop() + @AutoMap() + payload: RescueStationMessagePayloadDocument; +} + +export const RescueStationUpdateMessageSchema = SchemaFactory.createForClass( + RescueStationUpdateMessageDocument, +); diff --git a/libs/api/protocol/src/lib/mapper-profile/protocol-document.mapper-profile.ts b/libs/api/protocol/src/lib/mapper-profile/protocol-document.mapper-profile.ts index 3da2c975..90816504 100644 --- a/libs/api/protocol/src/lib/mapper-profile/protocol-document.mapper-profile.ts +++ b/libs/api/protocol/src/lib/mapper-profile/protocol-document.mapper-profile.ts @@ -22,7 +22,7 @@ import { import { CommunicationMessageDocument, CommunicationMessagePayloadDocument, -} from '../infra/schema/communication-message.schema'; +} from '../infra/schema/communication/communication-message.schema'; import { UserProducerDocument } from '../infra/schema/producer-partial.schema'; import { ProtocolEntryBaseDocument, diff --git a/libs/api/protocol/src/lib/mapper-profile/protocol-entity.mapper-profile.ts b/libs/api/protocol/src/lib/mapper-profile/protocol-entity.mapper-profile.ts index a1748fe1..a9aaabb2 100644 --- a/libs/api/protocol/src/lib/mapper-profile/protocol-entity.mapper-profile.ts +++ b/libs/api/protocol/src/lib/mapper-profile/protocol-entity.mapper-profile.ts @@ -24,7 +24,7 @@ import { import { CommunicationMessageDocument, CommunicationMessagePayloadDocument, -} from '../infra/schema/communication-message.schema'; +} from '../infra/schema/communication/communication-message.schema'; import { UserProducerDocument } from '../infra/schema/producer-partial.schema'; import { ProtocolEntryBaseDocument, diff --git a/libs/api/protocol/src/lib/mapper-profile/protocol-entry.mapper.spec.ts b/libs/api/protocol/src/lib/mapper-profile/protocol-entry.mapper.spec.ts index 997f5df2..bb130da0 100644 --- a/libs/api/protocol/src/lib/mapper-profile/protocol-entry.mapper.spec.ts +++ b/libs/api/protocol/src/lib/mapper-profile/protocol-entry.mapper.spec.ts @@ -2,7 +2,6 @@ import { classes } from '@automapper/classes'; import { AutomapperModule } from '@automapper/nestjs'; import { Test, TestingModule } from '@nestjs/testing'; import { plainToInstance } from 'class-transformer'; -import { before } from 'node:test'; import { BaseModelProfile } from '@kordis/api/shared'; @@ -16,12 +15,29 @@ import { CommunicationMessagePayload, } from '../core/entity/protocol-entries/communication-message.entity'; import { ProtocolEntryBase } from '../core/entity/protocol-entries/protocol-entry-base.entity'; +import { + RescueStationMessageAssignedAlertGroup, + RescueStationMessageAssignedUnit, + RescueStationMessagePayload, + RescueStationMessageStrength, +} from '../core/entity/protocol-entries/rescue-station/rescue-station-message-payload.entity'; +import { RescueStationSignOffMessage } from '../core/entity/protocol-entries/rescue-station/rescue-station-sign-off-message.entity'; +import { RescueStationSignOnMessage } from '../core/entity/protocol-entries/rescue-station/rescue-station-sign-on-message.entity'; +import { RescueStationUpdateMessage } from '../core/entity/protocol-entries/rescue-station/rescue-station-update-message.entity'; import { CommunicationMessageDocument, CommunicationMessagePayloadDocument, -} from '../infra/schema/communication-message.schema'; +} from '../infra/schema/communication/communication-message.schema'; import { UserProducerDocument } from '../infra/schema/producer-partial.schema'; import { ProtocolEntryBaseDocument } from '../infra/schema/protocol-entry-base.schema'; +import { + RescueStationMessageAssignedAlertGroupDocument, + RescueStationMessageAssignedUnitDocument, + RescueStationMessagePayloadDocument, + RescueStationMessageStrengthDocument, +} from '../infra/schema/rescue-station/rescue-station-message-payload.schema'; +import { RescueStationSignOnMessageDocument } from '../infra/schema/rescue-station/rescue-station-sign-on-message.schema'; +import { RescueStationUpdateMessageDocument } from '../infra/schema/rescue-station/rescue-station-updated-message.schema'; import { RegisteredUnitDocument, UnknownUnitDocument, @@ -36,6 +52,20 @@ import { CommunicationMessageProfile, } from './protocol-entity.mapper-profile'; import { ProtocolEntryMapper } from './protocol-entry.mapper'; +import { RescueStationMessagePayloadDocumentProfile } from './rescue-station/rescue-station-message-payload-document.mapper-profile'; +import { RescueStationMessagePayloadProfile } from './rescue-station/rescue-station-message-payload-entity.mapper-profile'; +import { + RescueStationSignOffMessageDocumentProfile, + RescueStationSignOffMessagePayloadDocumentProfile, +} from './rescue-station/rescue-station-sign-off-message-document.mapper-profile'; +import { + RescueStationSignOffMessageEntityProfile, + RescueStationSignOffMessagePayloadEntityProfile, +} from './rescue-station/rescue-station-sign-off-message-entity.mapper-profile'; +import { RescueStationSignOnMessageDocumentProfile } from './rescue-station/rescue-station-sign-on-message-document.mapper-profile'; +import { RescueStationSignOnMessageEntityProfile } from './rescue-station/rescue-station-sign-on-message-entity.mapper-profile'; +import { RescueStationUpdateMessageDocumentProfile } from './rescue-station/rescue-station-update-message-document.mapper-profile'; +import { RescueStationUpdateMessageEntityProfile } from './rescue-station/rescue-station-update-message-entity.mapper-profile'; import { UnitPartialProfile } from './unit-partial.mapper-profile'; const fakeTime = new Date('1953-04-13'); @@ -100,12 +130,274 @@ const testCases: { updatedAt: undefined, }), }, + { + entityName: RescueStationSignOnMessage.name, + entity: plainToInstance(RescueStationSignOnMessage, { + producer: plainToInstance(UserProducer, { + userId: '007', + firstName: 'James', + lastName: 'Bond', + }), + sender: plainToInstance(UnknownUnit, { name: 'James Bond' }), + recipient: plainToInstance(RegisteredUnit, { + unit: { id: '6662c25962bc301aac2cbcd6' }, + }), + payload: plainToInstance(RescueStationMessagePayload, { + rescueStationId: '6662c25962bc301aac2cbcd6', + rescueStationName: 'MI6', + rescueStationCallSign: 'MI6', + strength: plainToInstance(RescueStationMessageStrength, { + helpers: 3, + subLeaders: 2, + leaders: 1, + }), + units: [ + plainToInstance(RescueStationMessageAssignedUnit, { + id: '6662c25962bc301aac2cb007', + name: 'James Bond', + callSign: '007', + }), + ], + alertGroups: [ + plainToInstance(RescueStationMessageAssignedAlertGroup, { + id: '6662c25962bc301aac2cbcde2', + name: 'James Bond Alliance', + units: [ + plainToInstance(RescueStationMessageAssignedUnit, { + id: '6662c25962bc301aac2c008', + name: 'James Bond Jr.', + callSign: '008', + }), + ], + }), + ], + }), + searchableText: + 'one medium dry vodka martini mixed like you said, sir, but not stirred.', + channel: 'Walter', + orgId: 'orgId-123', + createdAt: undefined, + updatedAt: undefined, + }), + documentName: RescueStationSignOnMessageDocument.name, + document: plainToInstance(RescueStationSignOnMessageDocument, { + type: 'RESCUE_STATION_SIGN_ON_ENTRY', + producer: plainToInstance(UserProducerDocument, { + type: 'USER_PRODUCER', + userId: '007', + firstName: 'James', + lastName: 'Bond', + }), + payload: plainToInstance(RescueStationMessagePayloadDocument, { + rescueStationId: '6662c25962bc301aac2cbcd6', + rescueStationName: 'MI6', + rescueStationCallSign: 'MI6', + strength: plainToInstance(RescueStationMessageStrengthDocument, { + helpers: 3, + subLeaders: 2, + leaders: 1, + }), + units: [ + plainToInstance(RescueStationMessageAssignedUnitDocument, { + id: '6662c25962bc301aac2cb007', + name: 'James Bond', + callSign: '007', + }), + ], + alertGroups: [ + plainToInstance(RescueStationMessageAssignedAlertGroupDocument, { + id: '6662c25962bc301aac2cbcde2', + name: 'James Bond Alliance', + units: [ + plainToInstance(RescueStationMessageAssignedUnitDocument, { + id: '6662c25962bc301aac2c008', + name: 'James Bond Jr.', + callSign: '008', + }), + ], + }), + ], + }), + searchableText: + 'one medium dry vodka martini mixed like you said, sir, but not stirred.', + sender: plainToInstance(UnknownUnitDocument, { + name: 'James Bond', + type: 'UNKNOWN_UNIT', + }), + recipient: plainToInstance(RegisteredUnitDocument, { + type: 'REGISTERED_UNIT', + unitId: '6662c25962bc301aac2cbcd6', + }), + channel: 'Walter', + orgId: 'orgId-123', + createdAt: undefined, + updatedAt: undefined, + }), + }, + { + entityName: RescueStationUpdateMessage.name, + entity: plainToInstance(RescueStationUpdateMessage, { + producer: plainToInstance(UserProducer, { + userId: '007', + firstName: 'James', + lastName: 'Bond', + }), + sender: plainToInstance(UnknownUnit, { name: 'James Bond' }), + recipient: plainToInstance(RegisteredUnit, { + unit: { id: '6662c25962bc301aac2cbcd6' }, + }), + payload: plainToInstance(RescueStationMessagePayload, { + rescueStationId: '6662c25962bc301aac2cbcd6', + rescueStationName: 'MI6', + rescueStationCallSign: 'MI6', + strength: plainToInstance(RescueStationMessageStrength, { + helpers: 3, + subLeaders: 2, + leaders: 1, + }), + units: [ + plainToInstance(RescueStationMessageAssignedUnit, { + id: '6662c25962bc301aac2cb007', + name: 'James Bond', + callSign: '007', + }), + ], + alertGroups: [ + plainToInstance(RescueStationMessageAssignedAlertGroup, { + id: '6662c25962bc301aac2cbcde2', + name: 'James Bond Alliance', + units: [ + plainToInstance(RescueStationMessageAssignedUnit, { + id: '6662c25962bc301aac2c008', + name: 'James Bond Jr.', + callSign: '008', + }), + ], + }), + ], + }), + searchableText: + 'one medium dry vodka martini mixed like you said, sir, but not stirred.', + channel: 'Walter', + orgId: 'orgId-123', + createdAt: undefined, + updatedAt: undefined, + }), + documentName: RescueStationUpdateMessageDocument.name, + document: plainToInstance(RescueStationUpdateMessageDocument, { + type: 'RESCUE_STATION_UPDATE_ENTRY', + producer: plainToInstance(UserProducerDocument, { + type: 'USER_PRODUCER', + userId: '007', + firstName: 'James', + lastName: 'Bond', + }), + payload: plainToInstance(RescueStationMessagePayloadDocument, { + rescueStationId: '6662c25962bc301aac2cbcd6', + rescueStationName: 'MI6', + rescueStationCallSign: 'MI6', + strength: plainToInstance(RescueStationMessageStrengthDocument, { + helpers: 3, + subLeaders: 2, + leaders: 1, + }), + units: [ + plainToInstance(RescueStationMessageAssignedUnitDocument, { + id: '6662c25962bc301aac2cb007', + name: 'James Bond', + callSign: '007', + }), + ], + alertGroups: [ + plainToInstance(RescueStationMessageAssignedAlertGroupDocument, { + id: '6662c25962bc301aac2cbcde2', + name: 'James Bond Alliance', + units: [ + plainToInstance(RescueStationMessageAssignedUnitDocument, { + id: '6662c25962bc301aac2c008', + name: 'James Bond Jr.', + callSign: '008', + }), + ], + }), + ], + }), + searchableText: + 'one medium dry vodka martini mixed like you said, sir, but not stirred.', + sender: plainToInstance(UnknownUnitDocument, { + name: 'James Bond', + type: 'UNKNOWN_UNIT', + }), + recipient: plainToInstance(RegisteredUnitDocument, { + type: 'REGISTERED_UNIT', + unitId: '6662c25962bc301aac2cbcd6', + }), + channel: 'Walter', + orgId: 'orgId-123', + createdAt: undefined, + updatedAt: undefined, + }), + }, + { + entityName: RescueStationSignOffMessage.name, + entity: plainToInstance(RescueStationSignOffMessage, { + producer: plainToInstance(UserProducer, { + userId: '007', + firstName: 'James', + lastName: 'Bond', + }), + sender: plainToInstance(UnknownUnit, { name: 'James Bond' }), + recipient: plainToInstance(RegisteredUnit, { + unit: { id: '6662c25962bc301aac2cbcd6' }, + }), + payload: plainToInstance(RescueStationMessagePayload, { + rescueStationId: '6662c25962bc301aac2cbcd6', + rescueStationName: 'MI6', + rescueStationCallSign: 'MI6', + }), + searchableText: + 'one medium dry vodka martini mixed like you said, sir, but not stirred.', + channel: 'Walter', + orgId: 'orgId-123', + createdAt: undefined, + updatedAt: undefined, + }), + documentName: RescueStationUpdateMessageDocument.name, + document: plainToInstance(RescueStationUpdateMessageDocument, { + type: 'RESCUE_STATION_SIGN_OFF_ENTRY', + producer: plainToInstance(UserProducerDocument, { + type: 'USER_PRODUCER', + userId: '007', + firstName: 'James', + lastName: 'Bond', + }), + payload: plainToInstance(RescueStationMessagePayloadDocument, { + rescueStationId: '6662c25962bc301aac2cbcd6', + rescueStationName: 'MI6', + rescueStationCallSign: 'MI6', + }), + searchableText: + 'one medium dry vodka martini mixed like you said, sir, but not stirred.', + sender: plainToInstance(UnknownUnitDocument, { + name: 'James Bond', + type: 'UNKNOWN_UNIT', + }), + recipient: plainToInstance(RegisteredUnitDocument, { + type: 'REGISTERED_UNIT', + unitId: '6662c25962bc301aac2cbcd6', + }), + channel: 'Walter', + orgId: 'orgId-123', + createdAt: undefined, + updatedAt: undefined, + }), + }, ]; describe('ProtocolEntryMapper - Test all protocol entry mappings', () => { let mapper: ProtocolEntryMapper; - before(() => { + beforeAll(() => { jest.useFakeTimers({ now: fakeTime }); }); @@ -118,13 +410,24 @@ describe('ProtocolEntryMapper - Test all protocol entry mappings', () => { ], providers: [ BaseModelProfile, - ProducerPartialProfile, - UnitPartialProfile, CommunicationMessageDocumentProfile, CommunicationMessagePayloadDocumentProfile, - CommunicationMessageProfile, CommunicationMessagePayloadProfile, + CommunicationMessageProfile, + ProducerPartialProfile, ProtocolEntryMapper, + RescueStationMessagePayloadDocumentProfile, + RescueStationMessagePayloadProfile, + RescueStationSignOffMessageDocumentProfile, + RescueStationSignOffMessageDocumentProfile, + RescueStationSignOffMessageEntityProfile, + RescueStationSignOffMessagePayloadEntityProfile, + RescueStationSignOffMessagePayloadDocumentProfile, + RescueStationSignOnMessageDocumentProfile, + RescueStationSignOnMessageEntityProfile, + RescueStationUpdateMessageDocumentProfile, + RescueStationUpdateMessageEntityProfile, + UnitPartialProfile, ], }).compile(); @@ -150,9 +453,9 @@ describe('ProtocolEntryMapper - Test all protocol entry mappings', () => { ); it('Should error for unknown child classes of ProtocolEntryBase', () => { - class UnknwonProtocolEntryClass extends ProtocolEntryBase {} + class UnknownProtocolEntryClass extends ProtocolEntryBase {} - const unknownClassInstance = plainToInstance(UnknwonProtocolEntryClass, {}); + const unknownClassInstance = plainToInstance(UnknownProtocolEntryClass, {}); expect(() => mapper.map(unknownClassInstance)).toThrow(); }); diff --git a/libs/api/protocol/src/lib/mapper-profile/protocol-entry.mapper.ts b/libs/api/protocol/src/lib/mapper-profile/protocol-entry.mapper.ts index 6cb89fa7..6546e067 100644 --- a/libs/api/protocol/src/lib/mapper-profile/protocol-entry.mapper.ts +++ b/libs/api/protocol/src/lib/mapper-profile/protocol-entry.mapper.ts @@ -4,11 +4,17 @@ import { Inject, Injectable } from '@nestjs/common'; import { CommunicationMessage } from '../core/entity/protocol-entries/communication-message.entity'; import { ProtocolEntryBase } from '../core/entity/protocol-entries/protocol-entry-base.entity'; -import { CommunicationMessageDocument } from '../infra/schema/communication-message.schema'; +import { RescueStationSignOffMessage } from '../core/entity/protocol-entries/rescue-station/rescue-station-sign-off-message.entity'; +import { RescueStationSignOnMessage } from '../core/entity/protocol-entries/rescue-station/rescue-station-sign-on-message.entity'; +import { RescueStationUpdateMessage } from '../core/entity/protocol-entries/rescue-station/rescue-station-update-message.entity'; +import { CommunicationMessageDocument } from '../infra/schema/communication/communication-message.schema'; import { ProtocolEntryBaseDocument, ProtocolEntryType, } from '../infra/schema/protocol-entry-base.schema'; +import { RescueStationSignOffMessageDocument } from '../infra/schema/rescue-station/rescue-station-sign-off-message.schema'; +import { RescueStationSignOnMessageDocument } from '../infra/schema/rescue-station/rescue-station-sign-on-message.schema'; +import { RescueStationUpdateMessageDocument } from '../infra/schema/rescue-station/rescue-station-updated-message.schema'; @Injectable() export class ProtocolEntryMapper { @@ -36,6 +42,24 @@ export class ProtocolEntryMapper { CommunicationMessage, CommunicationMessageDocument, ); + case entity instanceof RescueStationSignOnMessage: + return this.mapper.map( + entity, + RescueStationSignOnMessage, + RescueStationSignOnMessageDocument, + ); + case entity instanceof RescueStationSignOffMessage: + return this.mapper.map( + entity, + RescueStationSignOffMessage, + RescueStationSignOffMessageDocument, + ); + case entity instanceof RescueStationUpdateMessage: + return this.mapper.map( + entity, + RescueStationUpdateMessage, + RescueStationUpdateMessageDocument, + ); default: throw new Error( `Protocol Entity type ${entity.constructor.name} not supported by mapper`, @@ -53,6 +77,24 @@ export class ProtocolEntryMapper { CommunicationMessageDocument, CommunicationMessage, ); + case ProtocolEntryType.RESCUE_STATION_SIGN_ON_ENTRY: + return this.mapper.map( + document, + RescueStationSignOnMessageDocument, + RescueStationSignOnMessage, + ); + case ProtocolEntryType.RESCUE_STATION_SIGN_OFF_ENTRY: + return this.mapper.map( + document, + RescueStationSignOffMessageDocument, + RescueStationSignOffMessage, + ); + case ProtocolEntryType.RESCUE_STATION_UPDATE_ENTRY: + return this.mapper.map( + document, + RescueStationUpdateMessageDocument, + RescueStationUpdateMessage, + ); default: throw new Error( `Protocol Document type ${document.type} not supported by mapper`, diff --git a/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-message-payload-document.mapper-profile.ts b/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-message-payload-document.mapper-profile.ts new file mode 100644 index 00000000..eb8cd6c0 --- /dev/null +++ b/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-message-payload-document.mapper-profile.ts @@ -0,0 +1,48 @@ +import { Mapper, createMap } from '@automapper/core'; +import { AutomapperProfile, getMapperToken } from '@automapper/nestjs'; +import { Inject, Injectable } from '@nestjs/common'; + +import { + RescueStationMessageAssignedAlertGroup, + RescueStationMessageAssignedUnit, + RescueStationMessagePayload, + RescueStationMessageStrength, +} from '../../core/entity/protocol-entries/rescue-station/rescue-station-message-payload.entity'; +import { + RescueStationMessageAssignedAlertGroupDocument, + RescueStationMessageAssignedUnitDocument, + RescueStationMessagePayloadDocument, + RescueStationMessageStrengthDocument, +} from '../../infra/schema/rescue-station/rescue-station-message-payload.schema'; + +@Injectable() +export class RescueStationMessagePayloadDocumentProfile extends AutomapperProfile { + constructor(@Inject(getMapperToken()) mapper: Mapper) { + super(mapper); + } + + override get profile() { + return (mapper: Mapper): void => { + createMap( + mapper, + RescueStationMessagePayload, + RescueStationMessagePayloadDocument, + ); + createMap( + mapper, + RescueStationMessageStrength, + RescueStationMessageStrengthDocument, + ); + createMap( + mapper, + RescueStationMessageAssignedUnit, + RescueStationMessageAssignedUnitDocument, + ); + createMap( + mapper, + RescueStationMessageAssignedAlertGroup, + RescueStationMessageAssignedAlertGroupDocument, + ); + }; + } +} diff --git a/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-message-payload-entity.mapper-profile.ts b/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-message-payload-entity.mapper-profile.ts new file mode 100644 index 00000000..85e5f4ed --- /dev/null +++ b/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-message-payload-entity.mapper-profile.ts @@ -0,0 +1,48 @@ +import { Mapper, createMap } from '@automapper/core'; +import { AutomapperProfile, getMapperToken } from '@automapper/nestjs'; +import { Inject, Injectable } from '@nestjs/common'; + +import { + RescueStationMessageAssignedAlertGroup, + RescueStationMessageAssignedUnit, + RescueStationMessagePayload, + RescueStationMessageStrength, +} from '../../core/entity/protocol-entries/rescue-station/rescue-station-message-payload.entity'; +import { + RescueStationMessageAssignedAlertGroupDocument, + RescueStationMessageAssignedUnitDocument, + RescueStationMessagePayloadDocument, + RescueStationMessageStrengthDocument, +} from '../../infra/schema/rescue-station/rescue-station-message-payload.schema'; + +@Injectable() +export class RescueStationMessagePayloadProfile extends AutomapperProfile { + constructor(@Inject(getMapperToken()) mapper: Mapper) { + super(mapper); + } + + override get profile() { + return (mapper: Mapper): void => { + createMap( + mapper, + RescueStationMessagePayloadDocument, + RescueStationMessagePayload, + ); + createMap( + mapper, + RescueStationMessageStrengthDocument, + RescueStationMessageStrength, + ); + createMap( + mapper, + RescueStationMessageAssignedUnitDocument, + RescueStationMessageAssignedUnit, + ); + createMap( + mapper, + RescueStationMessageAssignedAlertGroupDocument, + RescueStationMessageAssignedAlertGroup, + ); + }; + } +} diff --git a/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-sign-off-message-document.mapper-profile.ts b/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-sign-off-message-document.mapper-profile.ts new file mode 100644 index 00000000..2ae9b31e --- /dev/null +++ b/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-sign-off-message-document.mapper-profile.ts @@ -0,0 +1,47 @@ +import { Mapper, createMap } from '@automapper/core'; +import { AutomapperProfile, getMapperToken } from '@automapper/nestjs'; +import { Inject, Injectable } from '@nestjs/common'; + +import { + RescueStationSignOffMessage, + RescueStationSignOffMessagePayload, +} from '../../core/entity/protocol-entries/rescue-station/rescue-station-sign-off-message.entity'; +import { + RescueStationSignOffMessageDocument, + RescueStationSignOffMessagePayloadDocument, +} from '../../infra/schema/rescue-station/rescue-station-sign-off-message.schema'; +import { ProtocolMessageEntryBaseDocumentProfile } from '../protocol-document.mapper-profile'; + +@Injectable() +export class RescueStationSignOffMessagePayloadDocumentProfile extends AutomapperProfile { + constructor(@Inject(getMapperToken()) mapper: Mapper) { + super(mapper); + } + + override get profile() { + return (mapper: Mapper): void => { + createMap( + mapper, + RescueStationSignOffMessagePayload, + RescueStationSignOffMessagePayloadDocument, + ); + }; + } +} + +@Injectable() +export class RescueStationSignOffMessageDocumentProfile extends ProtocolMessageEntryBaseDocumentProfile { + constructor(@Inject(getMapperToken()) mapper: Mapper) { + super(mapper); + } + + override get profile() { + return (mapper: Mapper): void => { + createMap( + mapper, + RescueStationSignOffMessage, + RescueStationSignOffMessageDocument, + ); + }; + } +} diff --git a/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-sign-off-message-entity.mapper-profile.ts b/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-sign-off-message-entity.mapper-profile.ts new file mode 100644 index 00000000..8b6bc32b --- /dev/null +++ b/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-sign-off-message-entity.mapper-profile.ts @@ -0,0 +1,47 @@ +import { Mapper, createMap } from '@automapper/core'; +import { AutomapperProfile, getMapperToken } from '@automapper/nestjs'; +import { Inject, Injectable } from '@nestjs/common'; + +import { + RescueStationSignOffMessage, + RescueStationSignOffMessagePayload, +} from '../../core/entity/protocol-entries/rescue-station/rescue-station-sign-off-message.entity'; +import { + RescueStationSignOffMessageDocument, + RescueStationSignOffMessagePayloadDocument, +} from '../../infra/schema/rescue-station/rescue-station-sign-off-message.schema'; +import { ProtocolMessageEntryBaseProfile } from '../protocol-entity.mapper-profile'; + +@Injectable() +export class RescueStationSignOffMessagePayloadEntityProfile extends AutomapperProfile { + constructor(@Inject(getMapperToken()) mapper: Mapper) { + super(mapper); + } + + override get profile() { + return (mapper: Mapper): void => { + createMap( + mapper, + RescueStationSignOffMessagePayloadDocument, + RescueStationSignOffMessagePayload, + ); + }; + } +} + +@Injectable() +export class RescueStationSignOffMessageEntityProfile extends ProtocolMessageEntryBaseProfile { + constructor(@Inject(getMapperToken()) mapper: Mapper) { + super(mapper); + } + + override get profile() { + return (mapper: Mapper): void => { + createMap( + mapper, + RescueStationSignOffMessageDocument, + RescueStationSignOffMessage, + ); + }; + } +} diff --git a/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-sign-on-message-document.mapper-profile.ts b/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-sign-on-message-document.mapper-profile.ts new file mode 100644 index 00000000..d0b910dd --- /dev/null +++ b/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-sign-on-message-document.mapper-profile.ts @@ -0,0 +1,24 @@ +import { Mapper, createMap } from '@automapper/core'; +import { getMapperToken } from '@automapper/nestjs'; +import { Inject, Injectable } from '@nestjs/common'; + +import { RescueStationSignOnMessage } from '../../core/entity/protocol-entries/rescue-station/rescue-station-sign-on-message.entity'; +import { RescueStationSignOnMessageDocument } from '../../infra/schema/rescue-station/rescue-station-sign-on-message.schema'; +import { ProtocolMessageEntryBaseDocumentProfile } from '../protocol-document.mapper-profile'; + +@Injectable() +export class RescueStationSignOnMessageDocumentProfile extends ProtocolMessageEntryBaseDocumentProfile { + constructor(@Inject(getMapperToken()) mapper: Mapper) { + super(mapper); + } + + override get profile() { + return (mapper: Mapper): void => { + createMap( + mapper, + RescueStationSignOnMessage, + RescueStationSignOnMessageDocument, + ); + }; + } +} diff --git a/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-sign-on-message-entity.mapper-profile.ts b/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-sign-on-message-entity.mapper-profile.ts new file mode 100644 index 00000000..ee865e81 --- /dev/null +++ b/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-sign-on-message-entity.mapper-profile.ts @@ -0,0 +1,24 @@ +import { Mapper, createMap } from '@automapper/core'; +import { getMapperToken } from '@automapper/nestjs'; +import { Inject, Injectable } from '@nestjs/common'; + +import { RescueStationSignOnMessage } from '../../core/entity/protocol-entries/rescue-station/rescue-station-sign-on-message.entity'; +import { RescueStationSignOnMessageDocument } from '../../infra/schema/rescue-station/rescue-station-sign-on-message.schema'; +import { ProtocolMessageEntryBaseProfile } from '../protocol-entity.mapper-profile'; + +@Injectable() +export class RescueStationSignOnMessageEntityProfile extends ProtocolMessageEntryBaseProfile { + constructor(@Inject(getMapperToken()) mapper: Mapper) { + super(mapper); + } + + override get profile() { + return (mapper: Mapper): void => { + createMap( + mapper, + RescueStationSignOnMessageDocument, + RescueStationSignOnMessage, + ); + }; + } +} diff --git a/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-update-message-document.mapper-profile.ts b/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-update-message-document.mapper-profile.ts new file mode 100644 index 00000000..9870009d --- /dev/null +++ b/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-update-message-document.mapper-profile.ts @@ -0,0 +1,24 @@ +import { Mapper, createMap } from '@automapper/core'; +import { getMapperToken } from '@automapper/nestjs'; +import { Inject, Injectable } from '@nestjs/common'; + +import { RescueStationUpdateMessage } from '../../core/entity/protocol-entries/rescue-station/rescue-station-update-message.entity'; +import { RescueStationUpdateMessageDocument } from '../../infra/schema/rescue-station/rescue-station-updated-message.schema'; +import { ProtocolMessageEntryBaseDocumentProfile } from '../protocol-document.mapper-profile'; + +@Injectable() +export class RescueStationUpdateMessageDocumentProfile extends ProtocolMessageEntryBaseDocumentProfile { + constructor(@Inject(getMapperToken()) mapper: Mapper) { + super(mapper); + } + + override get profile() { + return (mapper: Mapper): void => { + createMap( + mapper, + RescueStationUpdateMessage, + RescueStationUpdateMessageDocument, + ); + }; + } +} diff --git a/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-update-message-entity.mapper-profile.ts b/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-update-message-entity.mapper-profile.ts new file mode 100644 index 00000000..61c40fb7 --- /dev/null +++ b/libs/api/protocol/src/lib/mapper-profile/rescue-station/rescue-station-update-message-entity.mapper-profile.ts @@ -0,0 +1,24 @@ +import { Mapper, createMap } from '@automapper/core'; +import { getMapperToken } from '@automapper/nestjs'; +import { Inject, Injectable } from '@nestjs/common'; + +import { RescueStationUpdateMessage } from '../../core/entity/protocol-entries/rescue-station/rescue-station-update-message.entity'; +import { RescueStationUpdateMessageDocument } from '../../infra/schema/rescue-station/rescue-station-updated-message.schema'; +import { ProtocolMessageEntryBaseProfile } from '../protocol-entity.mapper-profile'; + +@Injectable() +export class RescueStationUpdateMessageEntityProfile extends ProtocolMessageEntryBaseProfile { + constructor(@Inject(getMapperToken()) mapper: Mapper) { + super(mapper); + } + + override get profile() { + return (mapper: Mapper): void => { + createMap( + mapper, + RescueStationUpdateMessageDocument, + RescueStationUpdateMessage, + ); + }; + } +} diff --git a/libs/api/protocol/src/lib/protocol.module.ts b/libs/api/protocol/src/lib/protocol.module.ts index 916491fe..2e4604e7 100644 --- a/libs/api/protocol/src/lib/protocol.module.ts +++ b/libs/api/protocol/src/lib/protocol.module.ts @@ -2,6 +2,10 @@ import { Module } from '@nestjs/common'; import { MongooseModule } from '@nestjs/mongoose'; import { CreateCommunicationMessageHandler } from './core/command/create-communication-message.command'; +import { RescueStationMessageFactory } from './core/command/helper/rescue-station-message.factory'; +import { CreateRescueStationSignOffMessageHandler } from './core/command/rescue-station/create-rescue-station-sign-off-message.command'; +import { CreateRescueStationSignOnMessageHandler } from './core/command/rescue-station/create-rescue-station-sign-on-message.command'; +import { CreateRescueStationUpdateMessageHandler } from './core/command/rescue-station/create-rescue-station-update-message.command'; import { GetProtocolEntriesHandler } from './core/query/get-protocol-entries.query'; import { PROTOCOL_ENTRY_REPOSITORY } from './core/repository/protocol-entry.repository'; import { CommunicationMessageResolver } from './infra/controller/communication-message.resolver'; @@ -10,12 +14,15 @@ import { RegisteredUnitResolver, } from './infra/controller/protocol-entry.resolver'; import { ImplProtocolEntryRepository } from './infra/repository/protocol-entry.repository'; -import { CommunicationMessageSchema } from './infra/schema/communication-message.schema'; +import { CommunicationMessageSchema } from './infra/schema/communication/communication-message.schema'; import { ProtocolEntryBaseDocument, ProtocolEntryBaseSchema, ProtocolEntryType, } from './infra/schema/protocol-entry-base.schema'; +import { RescueStationSignOffMessageSchema } from './infra/schema/rescue-station/rescue-station-sign-off-message.schema'; +import { RescueStationSignOnMessageSchema } from './infra/schema/rescue-station/rescue-station-sign-on-message.schema'; +import { RescueStationUpdateMessageSchema } from './infra/schema/rescue-station/rescue-station-updated-message.schema'; import { ProducerPartialProfile } from './mapper-profile/producer-partial.mapper-profile'; import { CommunicationMessageDocumentProfile, @@ -26,23 +33,47 @@ import { CommunicationMessageProfile, } from './mapper-profile/protocol-entity.mapper-profile'; import { ProtocolEntryMapper } from './mapper-profile/protocol-entry.mapper'; +import { RescueStationMessagePayloadDocumentProfile } from './mapper-profile/rescue-station/rescue-station-message-payload-document.mapper-profile'; +import { RescueStationMessagePayloadProfile } from './mapper-profile/rescue-station/rescue-station-message-payload-entity.mapper-profile'; +import { RescueStationSignOffMessageDocumentProfile } from './mapper-profile/rescue-station/rescue-station-sign-off-message-document.mapper-profile'; +import { + RescueStationSignOffMessageEntityProfile, + RescueStationSignOffMessagePayloadEntityProfile, +} from './mapper-profile/rescue-station/rescue-station-sign-off-message-entity.mapper-profile'; +import { RescueStationSignOnMessageDocumentProfile } from './mapper-profile/rescue-station/rescue-station-sign-on-message-document.mapper-profile'; +import { RescueStationSignOnMessageEntityProfile } from './mapper-profile/rescue-station/rescue-station-sign-on-message-entity.mapper-profile'; +import { RescueStationUpdateMessageDocumentProfile } from './mapper-profile/rescue-station/rescue-station-update-message-document.mapper-profile'; +import { RescueStationUpdateMessageEntityProfile } from './mapper-profile/rescue-station/rescue-station-update-message-entity.mapper-profile'; import { UnitPartialProfile } from './mapper-profile/unit-partial.mapper-profile'; const MAPPER_PROFILES = [ - UnitPartialProfile, - ProducerPartialProfile, CommunicationMessageDocumentProfile, CommunicationMessagePayloadDocumentProfile, - CommunicationMessageProfile, CommunicationMessagePayloadProfile, + CommunicationMessageProfile, + ProducerPartialProfile, + ProtocolEntryMapper, + RescueStationMessagePayloadDocumentProfile, + RescueStationMessagePayloadProfile, + RescueStationSignOffMessageDocumentProfile, + RescueStationSignOffMessageEntityProfile, + RescueStationSignOffMessagePayloadEntityProfile, + RescueStationSignOnMessageDocumentProfile, + RescueStationSignOnMessageEntityProfile, + RescueStationUpdateMessageDocumentProfile, + RescueStationUpdateMessageEntityProfile, + UnitPartialProfile, ]; const COMMAND_HANDLERS = [ CreateCommunicationMessageHandler, + CreateRescueStationSignOffMessageHandler, + CreateRescueStationSignOnMessageHandler, + CreateRescueStationUpdateMessageHandler, GetProtocolEntriesHandler, ]; const RESOLVERS = [ - ProtocolEntryResolver, CommunicationMessageResolver, + ProtocolEntryResolver, RegisteredUnitResolver, ]; @@ -57,6 +88,18 @@ const RESOLVERS = [ name: ProtocolEntryType.COMMUNICATION_MESSAGE_ENTRY, schema: CommunicationMessageSchema, }, + { + name: ProtocolEntryType.RESCUE_STATION_SIGN_ON_ENTRY, + schema: RescueStationSignOnMessageSchema, + }, + { + name: ProtocolEntryType.RESCUE_STATION_SIGN_OFF_ENTRY, + schema: RescueStationSignOffMessageSchema, + }, + { + name: ProtocolEntryType.RESCUE_STATION_UPDATE_ENTRY, + schema: RescueStationUpdateMessageSchema, + }, ], }, ]), @@ -65,11 +108,11 @@ const RESOLVERS = [ ...MAPPER_PROFILES, ...RESOLVERS, ...COMMAND_HANDLERS, - ProtocolEntryMapper, { provide: PROTOCOL_ENTRY_REPOSITORY, useClass: ImplProtocolEntryRepository, }, + RescueStationMessageFactory, ], }) export class ProtocolModule {} diff --git a/libs/api/rescue-station-manager/.eslintrc.json b/libs/api/rescue-station-manager/.eslintrc.json new file mode 100644 index 00000000..79fd7c1d --- /dev/null +++ b/libs/api/rescue-station-manager/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/libs/api/rescue-station-manager/README.md b/libs/api/rescue-station-manager/README.md new file mode 100644 index 00000000..112015c3 --- /dev/null +++ b/libs/api/rescue-station-manager/README.md @@ -0,0 +1,11 @@ +# Rescue Station Manager + +The Rescue Station Manager is responsible for signing in and off rescue station, +as well as updating. It handles incoming requests and manages the process by +communication with the Protocol and Deployment domain (where the actual Rescue +Stations and most of the logic live). + +## Running unit tests + +Run `nx test api-rescue-station-manager` to execute the unit tests via +[Jest](https://jestjs.io). diff --git a/libs/api/rescue-station-manager/jest.config.ts b/libs/api/rescue-station-manager/jest.config.ts new file mode 100644 index 00000000..5b3b79b1 --- /dev/null +++ b/libs/api/rescue-station-manager/jest.config.ts @@ -0,0 +1,11 @@ +/* eslint-disable */ +export default { + displayName: 'api-rescue-station-manager', + preset: '../../../jest.preset.js', + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../../coverage/libs/api/rescue-station-manager', +}; diff --git a/libs/api/rescue-station-manager/project.json b/libs/api/rescue-station-manager/project.json new file mode 100644 index 00000000..cff772e9 --- /dev/null +++ b/libs/api/rescue-station-manager/project.json @@ -0,0 +1,20 @@ +{ + "name": "api-rescue-station-manager", + "$schema": "../../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "libs/api/rescue-station-manager/src", + "projectType": "library", + "targets": { + "lint": { + "executor": "@nx/eslint:lint", + "outputs": ["{options.outputFile}"] + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "libs/api/rescue-station-manager/jest.config.ts" + } + } + }, + "tags": [] +} diff --git a/libs/api/rescue-station-manager/src/index.ts b/libs/api/rescue-station-manager/src/index.ts new file mode 100644 index 00000000..fb4d12db --- /dev/null +++ b/libs/api/rescue-station-manager/src/index.ts @@ -0,0 +1 @@ +export * from './lib/infra/rescue-station-manager.module'; diff --git a/libs/api/rescue-station-manager/src/lib/core/command/command-rescue-station-data.model.ts b/libs/api/rescue-station-manager/src/lib/core/command/command-rescue-station-data.model.ts new file mode 100644 index 00000000..59287f3d --- /dev/null +++ b/libs/api/rescue-station-manager/src/lib/core/command/command-rescue-station-data.model.ts @@ -0,0 +1,14 @@ +export interface CommandRescueStationData { + rescueStationId: string; + assignedAlertGroups: { + alertGroupId: string; + unitIds: string[]; + }[]; + assignedUnitIds: string[]; + note: string; + strength: { + leaders: number; + subLeaders: number; + helpers: number; + }; +} diff --git a/libs/api/rescue-station-manager/src/lib/core/command/launch-sign-off-process.command.spec.ts b/libs/api/rescue-station-manager/src/lib/core/command/launch-sign-off-process.command.spec.ts new file mode 100644 index 00000000..8c6ea4d4 --- /dev/null +++ b/libs/api/rescue-station-manager/src/lib/core/command/launch-sign-off-process.command.spec.ts @@ -0,0 +1,122 @@ +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { CommandBus, CqrsModule, EventBus, QueryBus } from '@nestjs/cqrs'; +import { Test, TestingModule } from '@nestjs/testing'; + +import { SignOffRescueStationCommand } from '@kordis/api/deployment'; +import { CreateRescueStationSignOffMessageCommand } from '@kordis/api/protocol'; +import { AuthUser } from '@kordis/shared/model'; + +import { RescueStationSignedOffEvent } from '../event/rescue-station-signed-off.event'; +import { + LaunchSignOffProcessCommand, + LaunchSignOffProcessHandler, +} from './launch-sign-off-process.command'; + +describe('LaunchSignOffProcessCommand', () => { + let handler: LaunchSignOffProcessHandler; + let commandBus: CommandBus; + let mockQueryBus: DeepMocked; + let eventBus: EventBus; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [CqrsModule], + providers: [LaunchSignOffProcessHandler], + }) + .overrideProvider(CommandBus) + .useValue(createMock()) + .overrideProvider(QueryBus) + .useValue(createMock()) + .compile(); + + handler = module.get( + LaunchSignOffProcessHandler, + ); + commandBus = module.get(CommandBus); + mockQueryBus = module.get>(QueryBus); + eventBus = module.get(EventBus); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should fire StartSignOffProcessCommand and CreateRescueStationSignOffMessageCommand', async () => { + const command = new LaunchSignOffProcessCommand( + { + organizationId: 'orgId', + } as AuthUser, + 'rescueStationId', + { + sender: { + name: 'senderName', + }, + recipient: { + unit: { id: 'unitId' }, + }, + channel: 'channel', + }, + ); + + mockQueryBus.execute.mockResolvedValueOnce({ + id: 'rescueStationId', + name: 'rescueStationName', + callSign: 'rescueStationCallSign', + }); + + await handler.execute(command); + + expect(commandBus.execute).toHaveBeenCalledWith( + new CreateRescueStationSignOffMessageCommand( + expect.any(Date), + { + name: 'senderName', + }, + { + unit: { id: 'unitId' }, + }, + { + id: 'rescueStationId', + name: 'rescueStationName', + callSign: 'rescueStationCallSign', + }, + 'channel', + { + organizationId: 'orgId', + } as AuthUser, + ), + ); + expect(commandBus.execute).toHaveBeenCalledWith( + new SignOffRescueStationCommand('orgId', 'rescueStationId'), + ); + }); + + it('should RescueStationSignedOffEvent event after station signed off', async () => { + const command = new LaunchSignOffProcessCommand( + { + organizationId: 'orgId', + } as AuthUser, + 'rescueStationId', + { + sender: { + name: 'senderName', + }, + recipient: { + unit: { id: 'unitId' }, + }, + channel: 'channel', + }, + ); + + return new Promise((done) => { + eventBus.subscribe((event) => { + expect(event).toEqual( + new RescueStationSignedOffEvent('orgId', 'rescueStationId'), + ); + done(); + }); + + handler.execute(command); + }); + }); +}); diff --git a/libs/api/rescue-station-manager/src/lib/core/command/launch-sign-off-process.command.ts b/libs/api/rescue-station-manager/src/lib/core/command/launch-sign-off-process.command.ts new file mode 100644 index 00000000..6f456e54 --- /dev/null +++ b/libs/api/rescue-station-manager/src/lib/core/command/launch-sign-off-process.command.ts @@ -0,0 +1,86 @@ +import { + CommandBus, + CommandHandler, + EventBus, + ICommandHandler, + QueryBus, +} from '@nestjs/cqrs'; + +import { + GetRescueStationDeploymentQuery, + RescueStationDeploymentViewModel, + SignOffRescueStationCommand, +} from '@kordis/api/deployment'; +import { + CreateRescueStationSignOffMessageCommand, + MessageUnit, +} from '@kordis/api/protocol'; +import { AuthUser } from '@kordis/shared/model'; + +import { RescueStationSignedOffEvent } from '../event/rescue-station-signed-off.event'; + +export class LaunchSignOffProcessCommand { + constructor( + readonly reqUser: AuthUser, + readonly rescueStationId: string, + readonly communicationMessageData: { + sender: MessageUnit; + recipient: MessageUnit; + channel: string; + }, + ) {} +} + +@CommandHandler(LaunchSignOffProcessCommand) +export class LaunchSignOffProcessHandler + implements ICommandHandler +{ + constructor( + private readonly commandBus: CommandBus, + private readonly queryBus: QueryBus, + private readonly eventBus: EventBus, + ) {} + + async execute(cmd: LaunchSignOffProcessCommand): Promise { + await this.commandBus.execute( + new SignOffRescueStationCommand( + cmd.reqUser.organizationId, + cmd.rescueStationId, + ), + ); + + this.eventBus.publish( + new RescueStationSignedOffEvent( + cmd.reqUser.organizationId, + cmd.rescueStationId, + ), + ); + + await this.executeMessageCommand(cmd); + } + + private async executeMessageCommand( + cmd: LaunchSignOffProcessCommand, + ): Promise { + const rs: RescueStationDeploymentViewModel = await this.queryBus.execute( + new GetRescueStationDeploymentQuery( + cmd.reqUser.organizationId, + cmd.rescueStationId, + ), + ); + await this.commandBus.execute( + new CreateRescueStationSignOffMessageCommand( + new Date(), + cmd.communicationMessageData.sender, + cmd.communicationMessageData.recipient, + { + id: rs.id, + name: rs.name, + callSign: rs.callSign, + }, + cmd.communicationMessageData.channel, + cmd.reqUser, + ), + ); + } +} diff --git a/libs/api/rescue-station-manager/src/lib/core/command/launch-sign-on-process.command.spec.ts b/libs/api/rescue-station-manager/src/lib/core/command/launch-sign-on-process.command.spec.ts new file mode 100644 index 00000000..dbf203d8 --- /dev/null +++ b/libs/api/rescue-station-manager/src/lib/core/command/launch-sign-on-process.command.spec.ts @@ -0,0 +1,103 @@ +import { createMock } from '@golevelup/ts-jest'; +import { CommandBus, CqrsModule, EventBus } from '@nestjs/cqrs'; +import { Test, TestingModule } from '@nestjs/testing'; + +import { SignInRescueStationCommand } from '@kordis/api/deployment'; +import { AuthUser } from '@kordis/shared/model'; + +import { RescueStationSignedOnEvent } from '../event/rescue-station-signed-on.event'; +import { + LaunchSignOnProcessCommand, + LaunchSignOnProcessHandler, +} from './launch-sign-on-process.command'; +import { MessageCommandRescueStationDetailsFactory } from './message-command-rescue-station-details.factory'; + +const COMMAND = new LaunchSignOnProcessCommand( + { + organizationId: 'orgId', + } as AuthUser, + { + rescueStationId: 'stationId', + assignedAlertGroups: [ + { + alertGroupId: 'alertGroupId', + unitIds: ['unitId1'], + }, + ], + assignedUnitIds: ['unitId2'], + note: 'note', + strength: { + leaders: 1, + subLeaders: 1, + helpers: 1, + }, + }, + { + sender: { + name: 'senderName', + }, + recipient: { + unit: { id: 'unitId' }, + }, + channel: 'channel', + }, +); + +describe('LaunchSignOnProcessHandler', () => { + let handler: LaunchSignOnProcessHandler; + let commandBus: CommandBus; + let eventBus: EventBus; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [CqrsModule], + providers: [ + { + provide: MessageCommandRescueStationDetailsFactory, + useValue: createMock(), + }, + LaunchSignOnProcessHandler, + ], + }) + .overrideProvider(CommandBus) + .useValue(createMock()) + .compile(); + + handler = module.get( + LaunchSignOnProcessHandler, + ); + commandBus = module.get(CommandBus); + eventBus = module.get(EventBus); + }); + + it('should fire SignOnRescueStationCommand', async () => { + await handler.execute(COMMAND); + + expect(commandBus.execute).toHaveBeenCalledWith( + new SignInRescueStationCommand( + COMMAND.reqUser.organizationId, + COMMAND.rescueStationData.rescueStationId, + COMMAND.rescueStationData.strength, + COMMAND.rescueStationData.note, + COMMAND.rescueStationData.assignedUnitIds, + COMMAND.rescueStationData.assignedAlertGroups, + ), + ); + }); + + it('should RescueStationSignedOnEvent event after station signing on', async () => { + return new Promise((done) => { + eventBus.subscribe((event) => { + expect(event).toEqual( + new RescueStationSignedOnEvent( + COMMAND.reqUser.organizationId, + COMMAND.rescueStationData.rescueStationId, + ), + ); + done(); + }); + + handler.execute(COMMAND); + }); + }); +}); diff --git a/libs/api/rescue-station-manager/src/lib/core/command/launch-sign-on-process.command.ts b/libs/api/rescue-station-manager/src/lib/core/command/launch-sign-on-process.command.ts new file mode 100644 index 00000000..b72bfa02 --- /dev/null +++ b/libs/api/rescue-station-manager/src/lib/core/command/launch-sign-on-process.command.ts @@ -0,0 +1,81 @@ +import { + CommandBus, + CommandHandler, + EventBus, + ICommandHandler, +} from '@nestjs/cqrs'; + +import { SignInRescueStationCommand } from '@kordis/api/deployment'; +import { + CreateRescueStationSignOnMessageCommand, + MessageUnit, +} from '@kordis/api/protocol'; +import { AuthUser } from '@kordis/shared/model'; + +import { RescueStationSignedOnEvent } from '../event/rescue-station-signed-on.event'; +import { CommandRescueStationData } from './command-rescue-station-data.model'; +import { MessageCommandRescueStationDetailsFactory } from './message-command-rescue-station-details.factory'; + +export class LaunchSignOnProcessCommand { + constructor( + readonly reqUser: AuthUser, + readonly rescueStationData: CommandRescueStationData, + readonly communicationMessageData: { + sender: MessageUnit; + recipient: MessageUnit; + channel: string; + }, + ) {} +} + +@CommandHandler(LaunchSignOnProcessCommand) +export class LaunchSignOnProcessHandler + implements ICommandHandler +{ + constructor( + private readonly commandBus: CommandBus, + private readonly eventBus: EventBus, + private readonly messageCommandRescueStationDetailsFactory: MessageCommandRescueStationDetailsFactory, + ) {} + + async execute({ + reqUser, + rescueStationData, + communicationMessageData, + }: LaunchSignOnProcessCommand): Promise { + await this.commandBus.execute( + new SignInRescueStationCommand( + reqUser.organizationId, + rescueStationData.rescueStationId, + rescueStationData.strength, + rescueStationData.note, + rescueStationData.assignedUnitIds, + rescueStationData.assignedAlertGroups, + ), + ); + + this.eventBus.publish( + new RescueStationSignedOnEvent( + reqUser.organizationId, + rescueStationData.rescueStationId, + ), + ); + + const rsDetails = + await this.messageCommandRescueStationDetailsFactory.createFromCommandRescueStationData( + reqUser.organizationId, + rescueStationData, + ); + + await this.commandBus.execute( + new CreateRescueStationSignOnMessageCommand( + new Date(), + communicationMessageData.sender, + communicationMessageData.recipient, + rsDetails, + communicationMessageData.channel, + reqUser, + ), + ); + } +} diff --git a/libs/api/rescue-station-manager/src/lib/core/command/launch-update-signed-in-rescue-station-process.command.spec.ts b/libs/api/rescue-station-manager/src/lib/core/command/launch-update-signed-in-rescue-station-process.command.spec.ts new file mode 100644 index 00000000..3f1aef17 --- /dev/null +++ b/libs/api/rescue-station-manager/src/lib/core/command/launch-update-signed-in-rescue-station-process.command.spec.ts @@ -0,0 +1,109 @@ +import { createMock } from '@golevelup/ts-jest'; +import { CommandBus, CqrsModule, EventBus } from '@nestjs/cqrs'; +import { Test, TestingModule } from '@nestjs/testing'; + +import { UpdateSignedInRescueStationCommand } from '@kordis/api/deployment'; +import { AuthUser } from '@kordis/shared/model'; + +import { SignedInRescueStationUpdatedEvent } from '../event/signed-in-rescue-station-updated.event'; +import { + LaunchUpdateSignedInRescueStationProcessCommand, + LaunchUpdateSignedInRescueStationProcessHandler, +} from './launch-update-signed-in-rescue-station-process.command'; +import { MessageCommandRescueStationDetailsFactory } from './message-command-rescue-station-details.factory'; + +const COMMAND = new LaunchUpdateSignedInRescueStationProcessCommand( + { + organizationId: 'orgId', + } as AuthUser, + { + rescueStationId: 'rescueStationId', + assignedAlertGroups: [ + { + alertGroupId: 'alertGroupId', + unitIds: ['unitId1'], + }, + ], + assignedUnitIds: ['unitId2'], + note: 'note', + strength: { + leaders: 1, + subLeaders: 1, + helpers: 1, + }, + }, + { + sender: { + name: 'senderName', + }, + recipient: { + unit: { id: 'unitId' }, + }, + channel: 'channel', + }, +); + +describe('LaunchUpdateSignedInRescueStationProcessHandler', () => { + let handler: LaunchUpdateSignedInRescueStationProcessHandler; + let commandBus: CommandBus; + let eventBus: EventBus; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [CqrsModule], + providers: [ + { + provide: MessageCommandRescueStationDetailsFactory, + useValue: createMock(), + }, + LaunchUpdateSignedInRescueStationProcessHandler, + ], + }) + .overrideProvider(CommandBus) + .useValue(createMock()) + .compile(); + + handler = module.get( + LaunchUpdateSignedInRescueStationProcessHandler, + ); + commandBus = module.get(CommandBus); + eventBus = module.get(EventBus); + }); + + it('should fire UpdateSignedInRescueStationCommand', async () => { + await handler.execute(COMMAND); + + expect(commandBus.execute).toHaveBeenCalledWith( + new UpdateSignedInRescueStationCommand( + 'orgId', + 'rescueStationId', + { + leaders: 1, + subLeaders: 1, + helpers: 1, + }, + 'note', + ['unitId2'], + [ + { + alertGroupId: 'alertGroupId', + unitIds: ['unitId1'], + }, + ], + ), + ); + }); + + it('should publish SignedInRescueStationUpdatedEvent after station update', async () => { + return new Promise((done) => { + eventBus.subscribe((event) => { + expect(event).toEqual( + new SignedInRescueStationUpdatedEvent('orgId', 'rescueStationId'), + ); + done(); + }); + + handler.execute(COMMAND); + }); + }); +}); diff --git a/libs/api/rescue-station-manager/src/lib/core/command/launch-update-signed-in-rescue-station-process.command.ts b/libs/api/rescue-station-manager/src/lib/core/command/launch-update-signed-in-rescue-station-process.command.ts new file mode 100644 index 00000000..0f36f57f --- /dev/null +++ b/libs/api/rescue-station-manager/src/lib/core/command/launch-update-signed-in-rescue-station-process.command.ts @@ -0,0 +1,80 @@ +import { + CommandBus, + CommandHandler, + EventBus, + ICommandHandler, +} from '@nestjs/cqrs'; + +import { UpdateSignedInRescueStationCommand } from '@kordis/api/deployment'; +import { + CreateRescueStationSignOnMessageCommand, + MessageUnit, +} from '@kordis/api/protocol'; +import { AuthUser } from '@kordis/shared/model'; + +import { SignedInRescueStationUpdatedEvent } from '../event/signed-in-rescue-station-updated.event'; +import { CommandRescueStationData } from './command-rescue-station-data.model'; +import { MessageCommandRescueStationDetailsFactory } from './message-command-rescue-station-details.factory'; + +export class LaunchUpdateSignedInRescueStationProcessCommand { + constructor( + readonly reqUser: AuthUser, + readonly rescueStationData: CommandRescueStationData, + readonly communicationMessageData: { + sender: MessageUnit; + recipient: MessageUnit; + channel: string; + }, + ) {} +} + +@CommandHandler(LaunchUpdateSignedInRescueStationProcessCommand) +export class LaunchUpdateSignedInRescueStationProcessHandler + implements ICommandHandler +{ + constructor( + private readonly commandBus: CommandBus, + private readonly eventBus: EventBus, + private readonly messageCommandRescueStationDetailsFactory: MessageCommandRescueStationDetailsFactory, + ) {} + + async execute({ + reqUser, + rescueStationData, + communicationMessageData, + }: LaunchUpdateSignedInRescueStationProcessCommand): Promise { + await this.commandBus.execute( + new UpdateSignedInRescueStationCommand( + reqUser.organizationId, + rescueStationData.rescueStationId, + rescueStationData.strength, + rescueStationData.note, + rescueStationData.assignedUnitIds, + rescueStationData.assignedAlertGroups, + ), + ); + await this.eventBus.publish( + new SignedInRescueStationUpdatedEvent( + reqUser.organizationId, + rescueStationData.rescueStationId, + ), + ); + + const rsDetails = + await this.messageCommandRescueStationDetailsFactory.createFromCommandRescueStationData( + reqUser.organizationId, + rescueStationData, + ); + + await this.commandBus.execute( + new CreateRescueStationSignOnMessageCommand( + new Date(), + communicationMessageData.sender, + communicationMessageData.recipient, + rsDetails, + communicationMessageData.channel, + reqUser, + ), + ); + } +} diff --git a/libs/api/rescue-station-manager/src/lib/core/command/message-command-rescue-station-details.factory.spec.ts b/libs/api/rescue-station-manager/src/lib/core/command/message-command-rescue-station-details.factory.spec.ts new file mode 100644 index 00000000..036d1985 --- /dev/null +++ b/libs/api/rescue-station-manager/src/lib/core/command/message-command-rescue-station-details.factory.spec.ts @@ -0,0 +1,139 @@ +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { QueryBus } from '@nestjs/cqrs'; +import { Test, TestingModule } from '@nestjs/testing'; + +import { MessageCommandRescueStationDetails } from '@kordis/api/protocol'; + +import { CommandRescueStationData } from './command-rescue-station-data.model'; +import { MessageCommandRescueStationDetailsFactory } from './message-command-rescue-station-details.factory'; + +describe('MessageCommandRescueStationDetailsFactory', () => { + let factory: MessageCommandRescueStationDetailsFactory; + let mockQueryBus: DeepMocked; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + MessageCommandRescueStationDetailsFactory, + { provide: QueryBus, useValue: createMock() }, + ], + }).compile(); + + factory = module.get( + MessageCommandRescueStationDetailsFactory, + ); + mockQueryBus = module.get>(QueryBus); + }); + + it('should create MessageCommandRescueStationDetails from CommandRescueStationData', async () => { + const orgId = 'orgId'; + const commandRescueStationData: CommandRescueStationData = { + rescueStationId: 'rescueStationId', + assignedAlertGroups: [ + { + alertGroupId: 'alertGroupId1', + unitIds: ['unitId2'], + }, + { + alertGroupId: 'alertGroupId2', + unitIds: ['unitId3', 'unitId4'], + }, + ], + assignedUnitIds: ['unitId1'], + note: 'note', + strength: { + leaders: 1, + subLeaders: 1, + helpers: 1, + }, + }; + + mockQueryBus.execute.mockResolvedValueOnce({ + id: 'rescueStationId', + callSign: 'callSign', + name: 'name', + }); + mockQueryBus.execute.mockResolvedValueOnce([ + { + id: 'unitId1', + name: 'unitName1', + callSign: 'unitCallSign1', + }, + { + id: 'unitId2', + name: 'unitName2', + callSign: 'unitCallSign2', + }, + { + id: 'unitId3', + name: 'unitName3', + callSign: 'unitCallSign3', + }, + { + id: 'unitId4', + name: 'unitName4', + callSign: 'unitCallSign4', + }, + ]); + mockQueryBus.execute.mockResolvedValueOnce([ + { + id: 'alertGroupId1', + name: 'alertGroupName1', + }, + { + id: 'alertGroupId2', + name: 'alertGroupName2', + }, + ]); + + const result = await factory.createFromCommandRescueStationData( + orgId, + commandRescueStationData, + ); + + const expected: MessageCommandRescueStationDetails = { + units: [ + { + id: 'unitId1', + name: 'unitName1', + callSign: 'unitCallSign1', + }, + ], + alertGroups: [ + { + id: 'alertGroupId1', + name: 'alertGroupName1', + units: [ + { + id: 'unitId2', + name: 'unitName2', + callSign: 'unitCallSign2', + }, + ], + }, + { + id: 'alertGroupId2', + name: 'alertGroupName2', + units: [ + { + id: 'unitId3', + name: 'unitName3', + callSign: 'unitCallSign3', + }, + { + id: 'unitId4', + name: 'unitName4', + callSign: 'unitCallSign4', + }, + ], + }, + ], + name: 'name', + callSign: 'callSign', + id: 'rescueStationId', + strength: commandRescueStationData.strength, + }; + + expect(result).toEqual(expected); + }); +}); diff --git a/libs/api/rescue-station-manager/src/lib/core/command/message-command-rescue-station-details.factory.ts b/libs/api/rescue-station-manager/src/lib/core/command/message-command-rescue-station-details.factory.ts new file mode 100644 index 00000000..8a745232 --- /dev/null +++ b/libs/api/rescue-station-manager/src/lib/core/command/message-command-rescue-station-details.factory.ts @@ -0,0 +1,93 @@ +import { Injectable } from '@nestjs/common'; +import { QueryBus } from '@nestjs/cqrs'; + +import { + GetRescueStationDeploymentQuery, + RescueStationDeploymentViewModel, +} from '@kordis/api/deployment'; +import { MessageCommandRescueStationDetails } from '@kordis/api/protocol'; +import { + AlertGroupViewModel, + GetAlertGroupsByIdsQuery, + GetUnitsByIdsQuery, + UnitViewModel, +} from '@kordis/api/unit'; + +import { CommandRescueStationData } from './command-rescue-station-data.model'; + +@Injectable() +export class MessageCommandRescueStationDetailsFactory { + constructor(private readonly queryBus: QueryBus) {} + + async createFromCommandRescueStationData( + orgId: string, + commandRescueStationData: CommandRescueStationData, + ): Promise { + const rs: RescueStationDeploymentViewModel = await this.queryBus.execute( + new GetRescueStationDeploymentQuery( + orgId, + commandRescueStationData.rescueStationId, + ), + ); + + const { units, alertGroups } = await this.getPopulatedUnitsAndAlertGroups( + commandRescueStationData, + ); + + return { + id: rs.id, + callSign: rs.callSign, + name: rs.name, + units, + alertGroups, + strength: commandRescueStationData.strength, + }; + } + + // as units and alert groups are foreign fields the domain query will only return their ids we have to populate them by querying them from their respective domains + private async getPopulatedUnitsAndAlertGroups( + rescueStationData: CommandRescueStationData, + ): Promise<{ + units: MessageCommandRescueStationDetails['units']; + alertGroups: MessageCommandRescueStationDetails['alertGroups']; + }> { + const units: UnitViewModel[] = await this.queryBus.execute( + new GetUnitsByIdsQuery([ + ...rescueStationData.assignedUnitIds, + ...rescueStationData.assignedAlertGroups.flatMap( + (involvement) => involvement.unitIds, + ), + ]), + ); + + const commandUnits = units + .splice(0, rescueStationData.assignedUnitIds.length) + .map((unit) => ({ + name: unit.name, + callSign: unit.callSign, + id: unit.id, + })); + + const alertGroups: AlertGroupViewModel[] = await this.queryBus.execute( + new GetAlertGroupsByIdsQuery( + rescueStationData.assignedAlertGroups.map( + (involvement) => involvement.alertGroupId, + ), + ), + ); + + const commandAlertGroups = alertGroups.map((value, i) => ({ + id: value.id, + name: value.name, + units: units + .splice(0, rescueStationData.assignedAlertGroups[i].unitIds.length) + .map((unit) => ({ + id: unit.id, + name: unit.name, + callSign: unit.callSign, + })), + })); + + return { units: commandUnits, alertGroups: commandAlertGroups }; + } +} diff --git a/libs/api/rescue-station-manager/src/lib/core/event/rescue-station-signed-off.event.ts b/libs/api/rescue-station-manager/src/lib/core/event/rescue-station-signed-off.event.ts new file mode 100644 index 00000000..7c812cd0 --- /dev/null +++ b/libs/api/rescue-station-manager/src/lib/core/event/rescue-station-signed-off.event.ts @@ -0,0 +1,6 @@ +export class RescueStationSignedOffEvent { + constructor( + readonly orgId: string, + readonly rescueStationId: string, + ) {} +} diff --git a/libs/api/rescue-station-manager/src/lib/core/event/rescue-station-signed-on.event.ts b/libs/api/rescue-station-manager/src/lib/core/event/rescue-station-signed-on.event.ts new file mode 100644 index 00000000..edf1240b --- /dev/null +++ b/libs/api/rescue-station-manager/src/lib/core/event/rescue-station-signed-on.event.ts @@ -0,0 +1,6 @@ +export class RescueStationSignedOnEvent { + constructor( + readonly orgId: string, + readonly rescueStationId: string, + ) {} +} diff --git a/libs/api/rescue-station-manager/src/lib/core/event/signed-in-rescue-station-updated.event.ts b/libs/api/rescue-station-manager/src/lib/core/event/signed-in-rescue-station-updated.event.ts new file mode 100644 index 00000000..248ccda8 --- /dev/null +++ b/libs/api/rescue-station-manager/src/lib/core/event/signed-in-rescue-station-updated.event.ts @@ -0,0 +1,6 @@ +export class SignedInRescueStationUpdatedEvent { + constructor( + readonly orgId: string, + readonly rescueStationId: string, + ) {} +} diff --git a/libs/api/rescue-station-manager/src/lib/infra/argument/rescue-station.argument.ts b/libs/api/rescue-station-manager/src/lib/infra/argument/rescue-station.argument.ts new file mode 100644 index 00000000..b7f215bc --- /dev/null +++ b/libs/api/rescue-station-manager/src/lib/infra/argument/rescue-station.argument.ts @@ -0,0 +1,46 @@ +import { Field, InputType } from '@nestjs/graphql'; +import { + ArrayNotEmpty, + IsInt, + IsMongoId, + IsNotEmpty, + IsString, + Min, +} from 'class-validator'; + +@InputType('UpdateRescueStationStrength') +export class RescueStationStrengthArg { + @Field() + @IsInt() + @Min(0, { message: 'Die Anzahl der Führungskräfte muss mindestens 0 sein.' }) + leaders: number; + + @Field() + @IsInt() + @Min(0, { + message: 'Die Anzahl der Unterführungskräfte muss mindestens 0 sein.', + }) + subLeaders: number; + + @Field() + @IsInt() + @Min(0, { message: 'Die Anzahl der Helfer muss mindestens 0 sein.' }) + helpers: number; +} + +@InputType('UpdateRescueStationAssignedAlertGroup') +export class RescueStationAssignedAlertGroupArg { + @Field() + @IsString() + @IsNotEmpty() + alertGroupId: string; + + @Field(() => [String]) + @IsMongoId({ each: true }) + @IsNotEmpty({ each: true }) + @ArrayNotEmpty({ + message: + 'Es muss mindestens eine Einheit der Alarmgruppe zugewiesen werden', + }) + unitIds: string[]; +} diff --git a/libs/api/rescue-station-manager/src/lib/infra/argument/update-rescue-station.argument.ts b/libs/api/rescue-station-manager/src/lib/infra/argument/update-rescue-station.argument.ts new file mode 100644 index 00000000..6c39e1c2 --- /dev/null +++ b/libs/api/rescue-station-manager/src/lib/infra/argument/update-rescue-station.argument.ts @@ -0,0 +1,45 @@ +import { Field, InputType } from '@nestjs/graphql'; +import { Type } from 'class-transformer'; +import { + IsMongoId, + IsNotEmpty, + IsString, + ValidateNested, +} from 'class-validator'; + +import { + RescueStationAssignedAlertGroupArg, + RescueStationStrengthArg, +} from './rescue-station.argument'; + +@InputType() +export class UpdateRescueStationInput { + @Field() + @IsMongoId() + @IsNotEmpty() + rescueStationId: string; + + @Field(() => RescueStationStrengthArg) + @ValidateNested() + @Type(() => RescueStationStrengthArg) + strength: RescueStationStrengthArg; + + @Field({ defaultValue: '' }) + @IsString() + note: string; + + @Field(() => [String], { + description: + 'The Units to assign. If a Unit is currently assigned to another Rescue Station it will be released first. Units currently assigned to an operation will result in an error!', + }) + @IsMongoId({ each: true }) + assignedUnitIds: string[]; + + @Field(() => [RescueStationAssignedAlertGroupArg], { + description: + 'The Alert Groups to assign. If a Unit is currently assigned to another Rescue Station it will be released first. If the alert group is assigned to another deployment, it will be released and units that are not assigned within the new assignment will be kept as normally assigned units in the deployment. Alert Groups and Units currently assigned to an operation will result in an error!', + }) + @ValidateNested({ each: true }) + @Type(() => RescueStationAssignedAlertGroupArg) + assignedAlertGroups: RescueStationAssignedAlertGroupArg[]; +} diff --git a/libs/api/rescue-station-manager/src/lib/infra/controller/rescue-station-subscription.resolver.spec.ts b/libs/api/rescue-station-manager/src/lib/infra/controller/rescue-station-subscription.resolver.spec.ts new file mode 100644 index 00000000..39a1f282 --- /dev/null +++ b/libs/api/rescue-station-manager/src/lib/infra/controller/rescue-station-subscription.resolver.spec.ts @@ -0,0 +1,84 @@ +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { CqrsModule, EventBus, QueryBus } from '@nestjs/cqrs'; +import { Test, TestingModule } from '@nestjs/testing'; + +import { GraphQLSubscriptionService } from '@kordis/api/shared'; +import { AuthUser } from '@kordis/shared/model'; + +import { RescueStationSignedOffEvent } from '../../core/event/rescue-station-signed-off.event'; +import { RescueStationSignedOnEvent } from '../../core/event/rescue-station-signed-on.event'; +import { SignedInRescueStationUpdatedEvent } from '../../core/event/signed-in-rescue-station-updated.event'; +import { RescueStationSubscriptionResolver } from './rescue-station-subscription.resolver'; + +describe('RescueStationSubscriptionResolver', () => { + let resolver: RescueStationSubscriptionResolver; + let eventBus: EventBus; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [CqrsModule], + providers: [ + RescueStationSubscriptionResolver, + GraphQLSubscriptionService, + ], + }) + .overrideProvider(QueryBus) + .useValue(createMock()) + .compile(); + + resolver = module.get( + RescueStationSubscriptionResolver, + ); + eventBus = module.get(EventBus); + module.get>(QueryBus).execute.mockResolvedValue({ + id: 'rescueStationId', + }); + }); + + it('should return an iterator when rescueStationSignedIn event is fired', async () => { + const iterator = resolver.rescueStationSignedIn({ + organizationId: 'orgId', + } as AuthUser); + + eventBus.publish( + new RescueStationSignedOnEvent('orgId', 'rescueStationId'), + ); + const result = await iterator.next(); + + expect(result.value.rescueStationSignedIn).toBeDefined(); + expect(result.done).toBeFalsy(); + }); + + it('should return an iterator when signedInRescueStationUpdated event is fired', async () => { + const iterator = resolver.signedInRescueStationUpdated({ + organizationId: 'orgId', + } as AuthUser); + + eventBus.publish( + new SignedInRescueStationUpdatedEvent('orgId', 'rescueStationId'), + ); + const result = await iterator.next(); + + expect(result.value.signedInRescueStationUpdated).toEqual({ + id: 'rescueStationId', + }); + expect(result.done).toBeFalsy(); + }); + + it('should return an iterator when rescueStationSignedOff event is fired', async () => { + const iterator = resolver.rescueStationSignedOff({ + organizationId: 'orgId', + } as AuthUser); + + eventBus.publish( + new RescueStationSignedOffEvent('orgId', 'rescueStationId'), + ); + + const result = await iterator.next(); + + expect(result.value.rescueStationSignedOff).toEqual({ + id: 'rescueStationId', + }); + expect(result.done).toBeFalsy(); + }); +}); diff --git a/libs/api/rescue-station-manager/src/lib/infra/controller/rescue-station-subscription.resolver.ts b/libs/api/rescue-station-manager/src/lib/infra/controller/rescue-station-subscription.resolver.ts new file mode 100644 index 00000000..c1e7855a --- /dev/null +++ b/libs/api/rescue-station-manager/src/lib/infra/controller/rescue-station-subscription.resolver.ts @@ -0,0 +1,75 @@ +import { QueryBus } from '@nestjs/cqrs'; +import { Resolver, Subscription } from '@nestjs/graphql'; + +import { RequestUser } from '@kordis/api/auth'; +import { + GetRescueStationDeploymentQuery, + RescueStationDeploymentViewModel, +} from '@kordis/api/deployment'; +import { + GraphQLSubscriptionService, + SubscriptionOperators, +} from '@kordis/api/shared'; +import { AuthUser } from '@kordis/shared/model'; + +import { RescueStationSignedOffEvent } from '../../core/event/rescue-station-signed-off.event'; +import { RescueStationSignedOnEvent } from '../../core/event/rescue-station-signed-on.event'; +import { SignedInRescueStationUpdatedEvent } from '../../core/event/signed-in-rescue-station-updated.event'; + +@Resolver() +export class RescueStationSubscriptionResolver { + constructor( + private readonly queryBus: QueryBus, + private readonly gqlSubscriptionService: GraphQLSubscriptionService, + ) {} + + @Subscription(() => RescueStationDeploymentViewModel) + rescueStationSignedIn( + @RequestUser() { organizationId: authUserOrgId }: AuthUser, + ): AsyncIterableIterator { + return this.gqlSubscriptionService.getSubscriptionIteratorForEvent( + RescueStationSignedOnEvent, + 'rescueStationSignedIn', + this.operatorFactory(authUserOrgId), + ); + } + + @Subscription(() => RescueStationDeploymentViewModel) + signedInRescueStationUpdated( + @RequestUser() { organizationId: authUserOrgId }: AuthUser, + ): AsyncIterableIterator { + return this.gqlSubscriptionService.getSubscriptionIteratorForEvent( + SignedInRescueStationUpdatedEvent, + 'signedInRescueStationUpdated', + this.operatorFactory(authUserOrgId), + ); + } + + @Subscription(() => RescueStationDeploymentViewModel) + rescueStationSignedOff( + @RequestUser() { organizationId: authUserOrgId }: AuthUser, + ): AsyncIterableIterator { + return this.gqlSubscriptionService.getSubscriptionIteratorForEvent( + RescueStationSignedOffEvent, + 'rescueStationSignedOff', + this.operatorFactory(authUserOrgId), + ); + } + + private operatorFactory( + authOrgId: string, + ): SubscriptionOperators< + | RescueStationSignedOffEvent + | RescueStationSignedOnEvent + | SignedInRescueStationUpdatedEvent, + RescueStationDeploymentViewModel + > { + return { + filter: ({ orgId }) => orgId === authOrgId, + map: ({ rescueStationId }) => + this.queryBus.execute( + new GetRescueStationDeploymentQuery(authOrgId, rescueStationId), + ), + }; + } +} diff --git a/libs/api/rescue-station-manager/src/lib/infra/controller/rescue-station.resolver.spec.ts b/libs/api/rescue-station-manager/src/lib/infra/controller/rescue-station.resolver.spec.ts new file mode 100644 index 00000000..cc24bc69 --- /dev/null +++ b/libs/api/rescue-station-manager/src/lib/infra/controller/rescue-station.resolver.spec.ts @@ -0,0 +1,230 @@ +import { DeepMocked, createMock } from '@golevelup/ts-jest'; +import { CommandBus, CqrsModule, QueryBus } from '@nestjs/cqrs'; +import { Test, TestingModule } from '@nestjs/testing'; +import { plainToInstance } from 'class-transformer'; + +import { DeploymentNotFoundException } from '@kordis/api/deployment'; +import { BaseCreateMessageArgs, UnitInput } from '@kordis/api/protocol'; +import { PresentableNotFoundException } from '@kordis/api/shared'; +import { AuthUser } from '@kordis/shared/model'; + +import { LaunchSignOffProcessCommand } from '../../core/command/launch-sign-off-process.command'; +import { LaunchSignOnProcessCommand } from '../../core/command/launch-sign-on-process.command'; +import { LaunchUpdateSignedInRescueStationProcessCommand } from '../../core/command/launch-update-signed-in-rescue-station-process.command'; +import { RescueStationResolver } from './rescue-station.resolver'; + +const PROTOCOL_ARGS_DATA = plainToInstance(BaseCreateMessageArgs, { + sender: plainToInstance(UnitInput, { + type: 'REGISTERED_UNIT', + id: 'senderId', + }), + recipient: plainToInstance(UnitInput, { + type: 'UNKNOWN_UNIT', + name: 'recipientName', + }), + channel: 'channel', +}); + +const RS_ARGS_DATA = { + rescueStationId: 'rescueStationId', + assignedAlertGroups: [], + assignedUnitIds: [], + note: 'note', + strength: { + leaders: 1, + subLeaders: 1, + helpers: 1, + }, +}; + +const EXPECTED_PROTOCOL_DATA = { + sender: { + unit: { id: 'senderId' }, + }, + recipient: { + name: 'recipientName', + }, + channel: 'channel', +}; + +const AUTH_USER = { organizationId: 'orgId' } as AuthUser; + +describe('RescueStationResolver', () => { + let resolver: RescueStationResolver; + let commandBus: DeepMocked; + let queryBus: DeepMocked; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [CqrsModule], + providers: [RescueStationResolver], + }) + .overrideProvider(CommandBus) + .useValue(createMock()) + .overrideProvider(QueryBus) + .useValue(createMock()) + .compile(); + + resolver = module.get(RescueStationResolver); + commandBus = module.get>(CommandBus); + queryBus = module.get>(QueryBus); + }); + + describe('updateSignedInRescueStation', () => { + it('should update signed in rescue station', async () => { + await resolver.updateSignedInRescueStation( + AUTH_USER, + RS_ARGS_DATA, + PROTOCOL_ARGS_DATA, + ); + + expect(commandBus.execute).toHaveBeenCalledWith( + new LaunchUpdateSignedInRescueStationProcessCommand( + AUTH_USER, + RS_ARGS_DATA, + EXPECTED_PROTOCOL_DATA, + ), + ); + }); + + it('should return deployment', async () => { + const expectedResult = { + id: 'rescueStationId', + }; + + queryBus.execute.mockResolvedValue(expectedResult); + + const result = await resolver.updateSignedInRescueStation( + { organizationId: 'orgId' } as AuthUser, + RS_ARGS_DATA, + PROTOCOL_ARGS_DATA, + ); + + expect(result).toEqual(expectedResult); + expect(commandBus.execute).toHaveBeenCalledWith( + new LaunchUpdateSignedInRescueStationProcessCommand( + AUTH_USER, + RS_ARGS_DATA, + EXPECTED_PROTOCOL_DATA, + ), + ); + }); + + it('should throw presentable not found exception', async () => { + commandBus.execute.mockRejectedValue(new DeploymentNotFoundException()); + + await expect( + resolver.updateSignedInRescueStation( + AUTH_USER, + RS_ARGS_DATA, + PROTOCOL_ARGS_DATA, + ), + ).rejects.toThrow(PresentableNotFoundException); + }); + }); + + describe('signOffRescueStation', () => { + it('should sign off rescue station', async () => { + const rescueStationId = 'rescueStationId'; + await resolver.signOffRescueStation( + AUTH_USER, + rescueStationId, + PROTOCOL_ARGS_DATA, + ); + + expect(commandBus.execute).toHaveBeenCalledWith( + new LaunchSignOffProcessCommand( + AUTH_USER, + rescueStationId, + EXPECTED_PROTOCOL_DATA, + ), + ); + }); + + it('should return deployment', async () => { + const expectedResult = { id: 'rescueStationId' }; + + queryBus.execute.mockResolvedValue(expectedResult); + + const result = await resolver.signOffRescueStation( + AUTH_USER, + 'rescueStationId', + PROTOCOL_ARGS_DATA, + ); + + expect(result).toEqual(expectedResult); + expect(commandBus.execute).toHaveBeenCalledWith( + new LaunchSignOffProcessCommand( + AUTH_USER, + 'rescueStationId', + EXPECTED_PROTOCOL_DATA, + ), + ); + }); + + it('should throw presentable not found exception', async () => { + commandBus.execute.mockRejectedValue(new DeploymentNotFoundException()); + + await expect( + resolver.signOffRescueStation( + AUTH_USER, + 'rescueStationId', + PROTOCOL_ARGS_DATA, + ), + ).rejects.toThrow(PresentableNotFoundException); + }); + }); + + describe('signInRescueStation', () => { + it('should sign in rescue station', async () => { + await resolver.signInRescueStation( + AUTH_USER, + RS_ARGS_DATA, + PROTOCOL_ARGS_DATA, + ); + + expect(commandBus.execute).toHaveBeenCalledWith( + new LaunchSignOnProcessCommand( + AUTH_USER, + RS_ARGS_DATA, + EXPECTED_PROTOCOL_DATA, + ), + ); + }); + + it('should return deployment', async () => { + const expectedResult = { + id: 'rescueStationId', + }; + + queryBus.execute.mockResolvedValue(expectedResult); + + const result = await resolver.signInRescueStation( + AUTH_USER, + RS_ARGS_DATA, + PROTOCOL_ARGS_DATA, + ); + + expect(result).toEqual(expectedResult); + expect(commandBus.execute).toHaveBeenCalledWith( + new LaunchSignOnProcessCommand( + AUTH_USER, + RS_ARGS_DATA, + EXPECTED_PROTOCOL_DATA, + ), + ); + }); + + it('should throw a presentable when an error occurs', async () => { + commandBus.execute.mockRejectedValue(new DeploymentNotFoundException()); + + await expect( + resolver.signInRescueStation( + AUTH_USER, + RS_ARGS_DATA, + PROTOCOL_ARGS_DATA, + ), + ).rejects.toThrow(PresentableNotFoundException); + }); + }); +}); diff --git a/libs/api/rescue-station-manager/src/lib/infra/controller/rescue-station.resolver.ts b/libs/api/rescue-station-manager/src/lib/infra/controller/rescue-station.resolver.ts new file mode 100644 index 00000000..fba450c0 --- /dev/null +++ b/libs/api/rescue-station-manager/src/lib/infra/controller/rescue-station.resolver.ts @@ -0,0 +1,111 @@ +import { CommandBus, QueryBus } from '@nestjs/cqrs'; +import { Args, Mutation, Resolver } from '@nestjs/graphql'; + +import { RequestUser } from '@kordis/api/auth'; +import { + DeploymentNotFoundException, + GetRescueStationDeploymentQuery, + RescueStationDeploymentViewModel, +} from '@kordis/api/deployment'; +import { BaseCreateMessageArgs } from '@kordis/api/protocol'; +import { PresentableNotFoundException } from '@kordis/api/shared'; +import { AuthUser } from '@kordis/shared/model'; + +import { LaunchSignOffProcessCommand } from '../../core/command/launch-sign-off-process.command'; +import { LaunchSignOnProcessCommand } from '../../core/command/launch-sign-on-process.command'; +import { LaunchUpdateSignedInRescueStationProcessCommand } from '../../core/command/launch-update-signed-in-rescue-station-process.command'; +import { UpdateRescueStationInput } from '../argument/update-rescue-station.argument'; + +@Resolver() +export class RescueStationResolver { + constructor( + private readonly commandBus: CommandBus, + private readonly queryBus: QueryBus, + ) {} + + @Mutation(() => RescueStationDeploymentViewModel) + async updateSignedInRescueStation( + @RequestUser() reqUser: AuthUser, + @Args('rescueStationData') rescueStationData: UpdateRescueStationInput, + @Args('protocolMessageData') protocolMessageData: BaseCreateMessageArgs, + ): Promise { + try { + await this.commandBus.execute( + new LaunchUpdateSignedInRescueStationProcessCommand( + reqUser, + rescueStationData, + await protocolMessageData.asTransformedPayload(), + ), + ); + return this.queryBus.execute( + new GetRescueStationDeploymentQuery( + reqUser.organizationId, + rescueStationData.rescueStationId, + ), + ); + } catch (e) { + this.throwPresentable(e); + throw e; + } + } + + @Mutation(() => RescueStationDeploymentViewModel) + async signOffRescueStation( + @RequestUser() reqUser: AuthUser, + @Args('rescueStationId') rescueStationId: string, + @Args('protocolMessageData') protocolMessageData: BaseCreateMessageArgs, + ): Promise { + try { + await this.commandBus.execute( + new LaunchSignOffProcessCommand( + reqUser, + rescueStationId, + await protocolMessageData.asTransformedPayload(), + ), + ); + return this.queryBus.execute( + new GetRescueStationDeploymentQuery( + reqUser.organizationId, + rescueStationId, + ), + ); + } catch (e) { + this.throwPresentable(e); + throw e; + } + } + + @Mutation(() => RescueStationDeploymentViewModel) + async signInRescueStation( + @RequestUser() reqUser: AuthUser, + @Args('rescueStationData') rescueStationData: UpdateRescueStationInput, + @Args('protocolMessageData') protocolMessageData: BaseCreateMessageArgs, + ): Promise { + try { + await this.commandBus.execute( + new LaunchSignOnProcessCommand( + reqUser, + rescueStationData, + await protocolMessageData.asTransformedPayload(), + ), + ); + return this.queryBus.execute( + new GetRescueStationDeploymentQuery( + reqUser.organizationId, + rescueStationData.rescueStationId, + ), + ); + } catch (e) { + this.throwPresentable(e); + throw e; + } + } + + private throwPresentable(e: unknown): void { + if (e instanceof DeploymentNotFoundException) { + throw new PresentableNotFoundException( + 'Die Rettungswache konnte nicht gefunden werden.', + ); + } + } +} diff --git a/libs/api/rescue-station-manager/src/lib/infra/rescue-station-manager.module.ts b/libs/api/rescue-station-manager/src/lib/infra/rescue-station-manager.module.ts new file mode 100644 index 00000000..9efc5006 --- /dev/null +++ b/libs/api/rescue-station-manager/src/lib/infra/rescue-station-manager.module.ts @@ -0,0 +1,20 @@ +import { Module } from '@nestjs/common'; + +import { LaunchSignOffProcessHandler } from '../core/command/launch-sign-off-process.command'; +import { LaunchSignOnProcessHandler } from '../core/command/launch-sign-on-process.command'; +import { LaunchUpdateSignedInRescueStationProcessHandler } from '../core/command/launch-update-signed-in-rescue-station-process.command'; +import { MessageCommandRescueStationDetailsFactory } from '../core/command/message-command-rescue-station-details.factory'; +import { RescueStationSubscriptionResolver } from './controller/rescue-station-subscription.resolver'; +import { RescueStationResolver } from './controller/rescue-station.resolver'; + +@Module({ + providers: [ + RescueStationSubscriptionResolver, + RescueStationResolver, + LaunchSignOnProcessHandler, + LaunchSignOffProcessHandler, + LaunchUpdateSignedInRescueStationProcessHandler, + MessageCommandRescueStationDetailsFactory, + ], +}) +export class RescueStationManagerModule {} diff --git a/libs/api/rescue-station-manager/tsconfig.json b/libs/api/rescue-station-manager/tsconfig.json new file mode 100644 index 00000000..7bbfed93 --- /dev/null +++ b/libs/api/rescue-station-manager/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs", + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "strictPropertyInitialization": false, + "esModuleInterop": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/libs/api/rescue-station-manager/tsconfig.lib.json b/libs/api/rescue-station-manager/tsconfig.lib.json new file mode 100644 index 00000000..f7abb4b6 --- /dev/null +++ b/libs/api/rescue-station-manager/tsconfig.lib.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "declaration": true, + "types": ["node"], + "target": "es2021", + "strictNullChecks": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "../../../reset.d.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/libs/api/rescue-station-manager/tsconfig.spec.json b/libs/api/rescue-station-manager/tsconfig.spec.json new file mode 100644 index 00000000..231650b3 --- /dev/null +++ b/libs/api/rescue-station-manager/tsconfig.spec.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/tsconfig.base.json b/tsconfig.base.json index 5b936b6d..4c220c65 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -20,6 +20,9 @@ "@kordis/api/observability": ["libs/api/observability/src/index.ts"], "@kordis/api/organization": ["libs/api/organization/src/index.ts"], "@kordis/api/protocol": ["libs/api/protocol/src/index.ts"], + "@kordis/api/rescue-station-manager": [ + "libs/api/rescue-station-manager/src/index.ts" + ], "@kordis/api/shared": ["libs/api/shared/src/index.ts"], "@kordis/api/test-helpers": ["libs/api/test-helpers/src/index.ts"], "@kordis/api/tetra": ["libs/api/tetra/src/index.ts"],