feat: log and emit notification events on public-access enable/rotate

This commit is contained in:
Bastian Wagner
2026-08-04 19:21:01 +02:00
parent d6733eff0d
commit 017e6445fa
3 changed files with 85 additions and 1 deletions

View File

@@ -39,7 +39,9 @@ export type LOGEVENT =
| 'cashbox_export_subscription_run_fail'
| 'log_retention_cleanup_run'
| 'log_retention_cleanup_run_fail'
| 'notification_create_fail';
| 'notification_create_fail'
| 'public_access_enabled'
| 'public_access_rotated';
export const LOGEVENT_VALUES: LOGEVENT[] = [
'user_create',
@@ -82,6 +84,8 @@ export const LOGEVENT_VALUES: LOGEVENT[] = [
'log_retention_cleanup_run',
'log_retention_cleanup_run_fail',
'notification_create_fail',
'public_access_enabled',
'public_access_rotated',
];
export type LOGLEVEL = 'FATAL' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'TRACE';

View File

@@ -13,6 +13,8 @@ describe('PublicTeamAccessService', () => {
const penaltyRepository = { find: jest.fn() };
const access = { assertMember: jest.fn(), assertAtLeast: jest.fn() };
let service: PublicTeamAccessService;
let logger: any;
let eventEmitter: any;
const managedTeam = {
id: 7,
@@ -25,12 +27,16 @@ describe('PublicTeamAccessService', () => {
beforeEach(() => {
jest.resetAllMocks();
teamRepository.save.mockImplementation(async (team) => team);
logger = { info: jest.fn() };
eventEmitter = { emit: jest.fn() };
service = new PublicTeamAccessService(
teamRepository as any,
playerRepository as any,
transactionRepository as any,
penaltyRepository as any,
access as any,
logger as any,
eventEmitter as any,
);
});
@@ -82,6 +88,47 @@ describe('PublicTeamAccessService', () => {
expect(status.token).not.toBe('a'.repeat(64));
});
it('logs and emits when public access is enabled', async () => {
mockManagedTeam();
await service.setEnabled(4, 7, true);
expect(logger.info).toHaveBeenCalledWith({
event: 'public_access_enabled',
details: 'teamId=7',
userId: 4,
});
expect(eventEmitter.emit).toHaveBeenCalledWith(
'notifications.public_access.enabled',
expect.objectContaining({ teamId: 7, actorUserId: 4 }),
);
});
it('does not log or emit when public access is disabled', async () => {
mockManagedTeam({ ...managedTeam, publicAccessEnabled: true, publicAccessToken: 'a'.repeat(64) });
await service.setEnabled(4, 7, false);
expect(logger.info).not.toHaveBeenCalled();
expect(eventEmitter.emit).not.toHaveBeenCalled();
});
it('logs and emits when the token is rotated', async () => {
mockManagedTeam({ ...managedTeam, publicAccessEnabled: true, publicAccessToken: 'a'.repeat(64) });
await service.rotate(4, 7);
expect(logger.info).toHaveBeenCalledWith({
event: 'public_access_rotated',
details: 'teamId=7',
userId: 4,
});
expect(eventEmitter.emit).toHaveBeenCalledWith(
'notifications.public_access.rotated',
expect.objectContaining({ teamId: 7, actorUserId: 4 }),
);
});
it('returns only whitelisted public team fields and active players', async () => {
teamRepository.findOne.mockResolvedValue({
id: 7,

View File

@@ -1,6 +1,13 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { randomBytes } from 'crypto';
import { LoggingService } from '../database/logging/logging.service';
import { NOTIFICATION_EVENT_NAME } from '../notifications/events/notification-event-names';
import {
PublicAccessEnabledEvent,
PublicAccessRotatedEvent,
} from '../notifications/events/public-access-changed.event';
import { PenaltyEntity } from '../penalty/entities/penalty.entity';
import { Player } from '../players/entities/player.entity';
import { TeamRolesEnum } from '../team-roles/team-roles.enum';
@@ -28,6 +35,8 @@ export class PublicTeamAccessService {
@InjectRepository(PenaltyEntity)
private readonly penaltyRepository: Repository<PenaltyEntity>,
private readonly access: TeamAccessService,
private readonly logger: LoggingService,
private readonly eventEmitter: EventEmitter2,
) {}
async getStatus(
@@ -55,6 +64,19 @@ export class PublicTeamAccessService {
}
team.publicAccessEnabled = enabled;
await this.teamRepository.save(team);
if (enabled) {
await this.logger.info({
event: 'public_access_enabled',
details: `teamId=${teamId}`,
userId,
});
this.eventEmitter.emit(
NOTIFICATION_EVENT_NAME.publicAccessEnabled,
new PublicAccessEnabledEvent(teamId, userId),
);
}
return this.toStatus(team);
}
@@ -68,6 +90,17 @@ export class PublicTeamAccessService {
const team = await this.loadManagedTeam(teamId);
team.publicAccessToken = this.createToken();
await this.teamRepository.save(team);
await this.logger.info({
event: 'public_access_rotated',
details: `teamId=${teamId}`,
userId,
});
this.eventEmitter.emit(
NOTIFICATION_EVENT_NAME.publicAccessRotated,
new PublicAccessRotatedEvent(teamId, userId),
);
return this.toStatus(team);
}