feat: log and emit notification events on public-access enable/rotate
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user