feat: emit notification events on player active/role changes
This commit is contained in:
@@ -18,6 +18,7 @@ describe('TeamMembersService', () => {
|
|||||||
let dataSource: any;
|
let dataSource: any;
|
||||||
let logger: any;
|
let logger: any;
|
||||||
let access: any;
|
let access: any;
|
||||||
|
let eventEmitter: any;
|
||||||
let service: TeamMembersService;
|
let service: TeamMembersService;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
@@ -44,7 +45,8 @@ describe('TeamMembersService', () => {
|
|||||||
dataSource = { transaction: jest.fn((work) => work(manager)) };
|
dataSource = { transaction: jest.fn((work) => work(manager)) };
|
||||||
logger = { info: jest.fn() };
|
logger = { info: jest.fn() };
|
||||||
access = { assertAtLeast: jest.fn(() => Promise.resolve()) };
|
access = { assertAtLeast: jest.fn(() => Promise.resolve()) };
|
||||||
service = new TeamMembersService(dataSource, logger, access as any);
|
eventEmitter = { emit: jest.fn() };
|
||||||
|
service = new TeamMembersService(dataSource, logger, access as any, eventEmitter as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('checks the team-manager permission before touching the database', async () => {
|
it('checks the team-manager permission before touching the database', async () => {
|
||||||
@@ -188,6 +190,53 @@ describe('TeamMembersService', () => {
|
|||||||
).rejects.toBeInstanceOf(NotFoundException);
|
).rejects.toBeInstanceOf(NotFoundException);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('emits a player-active-changed event after a real deactivation', async () => {
|
||||||
|
player.balance = 42;
|
||||||
|
treasurers = [player, makePlayer(102, true, TeamRolesEnum.treasurer, 0)];
|
||||||
|
|
||||||
|
await service.setActive(5, teamId, player.id, false);
|
||||||
|
|
||||||
|
expect(eventEmitter.emit).toHaveBeenCalledWith(
|
||||||
|
'notifications.player.active_changed',
|
||||||
|
expect.objectContaining({
|
||||||
|
teamId,
|
||||||
|
actorUserId: 5,
|
||||||
|
playerId: player.id,
|
||||||
|
playerName: 'Pat Player',
|
||||||
|
active: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not emit when the active state is unchanged (idempotent)', async () => {
|
||||||
|
player = makePlayer(101, true, TeamRolesEnum.player, 0);
|
||||||
|
lockedPlayerQuery = chain({ getOne: jest.fn(() => player) });
|
||||||
|
playerRepository.createQueryBuilder = jest.fn((alias: string) =>
|
||||||
|
alias === 'lockedPlayer' ? lockedPlayerQuery : treasurerLockQuery,
|
||||||
|
);
|
||||||
|
|
||||||
|
await service.setActive(5, teamId, player.id, true);
|
||||||
|
|
||||||
|
expect(eventEmitter.emit).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('emits a player-role-changed event after a real role change', async () => {
|
||||||
|
treasurers = [player, makePlayer(102, true, TeamRolesEnum.treasurer, 0)];
|
||||||
|
|
||||||
|
await service.setTeamRole(5, teamId, player.id, TeamRolesEnum.captain);
|
||||||
|
|
||||||
|
expect(eventEmitter.emit).toHaveBeenCalledWith(
|
||||||
|
'notifications.player.role_changed',
|
||||||
|
expect.objectContaining({
|
||||||
|
teamId,
|
||||||
|
actorUserId: 5,
|
||||||
|
playerId: player.id,
|
||||||
|
playerName: 'Pat Player',
|
||||||
|
teamRoleId: TeamRolesEnum.captain,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
function makePlayer(
|
function makePlayer(
|
||||||
id: number,
|
id: number,
|
||||||
active: boolean,
|
active: boolean,
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
|
import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import { DataSource, EntityManager, Repository } from 'typeorm';
|
import { DataSource, EntityManager, Repository } from 'typeorm';
|
||||||
import { LoggingService } from '../database/logging/logging.service';
|
import { LoggingService } from '../database/logging/logging.service';
|
||||||
|
import { NOTIFICATION_EVENT_NAME } from '../notifications/events/notification-event-names';
|
||||||
|
import { PlayerActiveChangedEvent } from '../notifications/events/player-active-changed.event';
|
||||||
|
import { PlayerRoleChangedEvent } from '../notifications/events/player-role-changed.event';
|
||||||
import { Player } from '../players/entities/player.entity';
|
import { Player } from '../players/entities/player.entity';
|
||||||
import { TeamRole } from '../team-roles/entities/team-roles.entity';
|
import { TeamRole } from '../team-roles/entities/team-roles.entity';
|
||||||
import { TeamRolesEnum } from '../team-roles/team-roles.enum';
|
import { TeamRolesEnum } from '../team-roles/team-roles.enum';
|
||||||
@@ -18,6 +22,7 @@ export class TeamMembersService {
|
|||||||
private readonly dataSource: DataSource,
|
private readonly dataSource: DataSource,
|
||||||
private readonly logger: LoggingService,
|
private readonly logger: LoggingService,
|
||||||
private readonly access: TeamAccessService,
|
private readonly access: TeamAccessService,
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async setActive(
|
async setActive(
|
||||||
@@ -33,12 +38,12 @@ export class TeamMembersService {
|
|||||||
TeamRolesEnum.captain,
|
TeamRolesEnum.captain,
|
||||||
);
|
);
|
||||||
|
|
||||||
return this.dataSource.transaction(async (manager) => {
|
const result = await this.dataSource.transaction(async (manager) => {
|
||||||
const activeTreasurers = await this.lockActiveTreasurers(manager, teamId);
|
const activeTreasurers = await this.lockActiveTreasurers(manager, teamId);
|
||||||
const playerRepository = manager.getRepository(Player);
|
const playerRepository = manager.getRepository(Player);
|
||||||
const player = await this.findLockedPlayer(playerRepository, playerId, teamId);
|
const player = await this.findLockedPlayer(playerRepository, playerId, teamId);
|
||||||
|
|
||||||
if (player.active === active) return player;
|
if (player.active === active) return { player, changed: false };
|
||||||
|
|
||||||
const isDeactivation = player.active && !active;
|
const isDeactivation = player.active && !active;
|
||||||
if (
|
if (
|
||||||
@@ -66,8 +71,23 @@ export class TeamMembersService {
|
|||||||
actorUserId,
|
actorUserId,
|
||||||
`teamId=${teamId} playerId=${playerId} active=${active}`,
|
`teamId=${teamId} playerId=${playerId} active=${active}`,
|
||||||
);
|
);
|
||||||
return player;
|
return { player, changed: true };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (result.changed) {
|
||||||
|
this.eventEmitter.emit(
|
||||||
|
NOTIFICATION_EVENT_NAME.playerActiveChanged,
|
||||||
|
new PlayerActiveChangedEvent(
|
||||||
|
teamId,
|
||||||
|
actorUserId,
|
||||||
|
playerId,
|
||||||
|
`${result.player.firstName} ${result.player.lastName}`,
|
||||||
|
active,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.player;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setTeamRole(
|
async setTeamRole(
|
||||||
@@ -83,12 +103,12 @@ export class TeamMembersService {
|
|||||||
TeamRolesEnum.captain,
|
TeamRolesEnum.captain,
|
||||||
);
|
);
|
||||||
|
|
||||||
return this.dataSource.transaction(async (manager) => {
|
const result = await this.dataSource.transaction(async (manager) => {
|
||||||
const activeTreasurers = await this.lockActiveTreasurers(manager, teamId);
|
const activeTreasurers = await this.lockActiveTreasurers(manager, teamId);
|
||||||
const playerRepository = manager.getRepository(Player);
|
const playerRepository = manager.getRepository(Player);
|
||||||
const player = await this.findLockedPlayer(playerRepository, playerId, teamId);
|
const player = await this.findLockedPlayer(playerRepository, playerId, teamId);
|
||||||
|
|
||||||
if (player.teamRole?.id === teamRoleId) return player;
|
if (player.teamRole?.id === teamRoleId) return { player, changed: false };
|
||||||
|
|
||||||
const isDemotionFromTreasurer =
|
const isDemotionFromTreasurer =
|
||||||
player.active &&
|
player.active &&
|
||||||
@@ -108,8 +128,23 @@ export class TeamMembersService {
|
|||||||
actorUserId,
|
actorUserId,
|
||||||
`teamId=${teamId} playerId=${playerId} teamRoleId=${teamRoleId}`,
|
`teamId=${teamId} playerId=${playerId} teamRoleId=${teamRoleId}`,
|
||||||
);
|
);
|
||||||
return player;
|
return { player, changed: true };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (result.changed) {
|
||||||
|
this.eventEmitter.emit(
|
||||||
|
NOTIFICATION_EVENT_NAME.playerRoleChanged,
|
||||||
|
new PlayerRoleChangedEvent(
|
||||||
|
teamId,
|
||||||
|
actorUserId,
|
||||||
|
playerId,
|
||||||
|
`${result.player.firstName} ${result.player.lastName}`,
|
||||||
|
teamRoleId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.player;
|
||||||
}
|
}
|
||||||
|
|
||||||
// insert() statt save(): umgeht bewusst @BeforeInsert setBalance() auf Transaction,
|
// insert() statt save(): umgeht bewusst @BeforeInsert setBalance() auf Transaction,
|
||||||
|
|||||||
Reference in New Issue
Block a user