diff --git a/myteamwallet_backend/src/penalty/penalty.controller.ts b/myteamwallet_backend/src/penalty/penalty.controller.ts index c08c509..afdd657 100644 --- a/myteamwallet_backend/src/penalty/penalty.controller.ts +++ b/myteamwallet_backend/src/penalty/penalty.controller.ts @@ -26,6 +26,11 @@ type AuthenticatedRequest = { user: { id: number } }; export class PenaltyController { constructor(private readonly service: PenaltyService) {} + @Get() + getUserPenalties(@Request() request: AuthenticatedRequest) { + return this.service.getUserPenalties(request.user.id); + } + @Get(':teamId') getTeamPenalties( @Request() request: AuthenticatedRequest, diff --git a/myteamwallet_backend/src/penalty/penalty.http.spec.ts b/myteamwallet_backend/src/penalty/penalty.http.spec.ts index 92afa36..17e8f7d 100644 --- a/myteamwallet_backend/src/penalty/penalty.http.spec.ts +++ b/myteamwallet_backend/src/penalty/penalty.http.spec.ts @@ -20,6 +20,7 @@ describe('penalty catalog HTTP boundary', () => { createdAt: '2026-01-02T00:00:00.000Z', }; const service = { + getUserPenalties: jest.fn(() => [penalty]), getTeamPenalties: jest.fn(() => [penalty]), createPenalty: jest.fn(() => penalty), updatePenalty: jest.fn(() => penalty), @@ -62,6 +63,15 @@ describe('penalty catalog HTTP boundary', () => { expect(service.getTeamPenalties).toHaveBeenCalledWith(42, 5); }); + it('keeps the authenticated cross-team catalog route available', async () => { + await request(app.getHttpServer()).get('/api/v1/penalty').expect(401); + await request(app.getHttpServer()) + .get('/api/v1/penalty') + .set('Authorization', 'Bearer user') + .expect(200, [penalty]); + expect(service.getUserPenalties).toHaveBeenCalledWith(42); + }); + it('validates and strips create fields before invoking the service', async () => { await request(app.getHttpServer()) .post('/api/v1/penalty') diff --git a/myteamwallet_backend/src/penalty/penalty.service.spec.ts b/myteamwallet_backend/src/penalty/penalty.service.spec.ts index 5e46b28..65995ac 100644 --- a/myteamwallet_backend/src/penalty/penalty.service.spec.ts +++ b/myteamwallet_backend/src/penalty/penalty.service.spec.ts @@ -55,6 +55,33 @@ describe('PenaltyService catalog management', () => { expect(access.assertMember).not.toHaveBeenCalled(); }); + it('keeps the authenticated cross-team catalog route compatible and safely mapped', async () => { + readRepository.find.mockResolvedValue([ + { + id: 8, + description: 'Zu spät', + amount: '5.50', + createdAt: new Date('2026-01-02T00:00:00.000Z'), + team: { id: 5, secret: 'hidden' }, + }, + ]); + + await expect(service.getUserPenalties(42)).resolves.toEqual([ + { + id: 8, + description: 'Zu spät', + amount: 5.5, + createdAt: new Date('2026-01-02T00:00:00.000Z'), + }, + ]); + expect(readRepository.find).toHaveBeenCalledWith({ + where: { + team: { players: { user: { id: 42 }, active: true } }, + }, + order: { description: 'ASC' }, + }); + }); + it('authorizes team reads, sorts them, and maps only safe fields', async () => { readRepository.find.mockResolvedValue([ { @@ -136,11 +163,7 @@ describe('PenaltyService catalog management', () => { writeRepository.save.mockImplementation(async (value) => value); await expect( - service.updatePenalty( - 8, - { description: ' Neu ', amount: 2.5 }, - 42, - ), + service.updatePenalty(8, { description: ' Neu ', amount: 2.5 }, 42), ).resolves.toMatchObject({ id: 8, description: 'Neu', amount: 2.5 }); expect(duplicateQuery.andWhere).toHaveBeenCalledWith( 'penalty.id != :penaltyId', @@ -194,10 +217,7 @@ describe('PenaltyService catalog management', () => { logger.info.mockRejectedValue(new Error('audit unavailable')); await expect( - service.createPenalty( - { teamId: 5, description: 'Neu', amount: 2 }, - 42, - ), + service.createPenalty({ teamId: 5, description: 'Neu', amount: 2 }, 42), ).rejects.toThrow('audit unavailable'); }); }); diff --git a/myteamwallet_backend/src/penalty/penalty.service.ts b/myteamwallet_backend/src/penalty/penalty.service.ts index e476bbd..205e2dc 100644 --- a/myteamwallet_backend/src/penalty/penalty.service.ts +++ b/myteamwallet_backend/src/penalty/penalty.service.ts @@ -25,6 +25,16 @@ export class PenaltyService { private readonly logger: LoggingService, ) {} + async getUserPenalties(userId: number): Promise { + const penalties = await this.repository.find({ + where: { + team: { players: { user: { id: userId }, active: true } }, + }, + order: { description: 'ASC' }, + }); + return penalties.map((penalty) => this.toResponse(penalty)); + } + async getTeamPenalties( userId: number, teamId: number, @@ -133,7 +143,10 @@ export class PenaltyService { return penalty; } - private async lockTeam(manager: EntityManager, teamId: number): Promise { + private async lockTeam( + manager: EntityManager, + teamId: number, + ): Promise { const team = await manager .getRepository(Team) .createQueryBuilder('team') diff --git a/myteamwallet_frontend_modern/src/app/features/team/more/penalties/penalties.html b/myteamwallet_frontend_modern/src/app/features/team/more/penalties/penalties.html index 9bf4888..f868461 100644 --- a/myteamwallet_frontend_modern/src/app/features/team/more/penalties/penalties.html +++ b/myteamwallet_frontend_modern/src/app/features/team/more/penalties/penalties.html @@ -17,7 +17,7 @@ -