import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { randomBytes } from 'crypto'; import { PenaltyEntity } from '../penalty/entities/penalty.entity'; import { Player } from '../players/entities/player.entity'; import { Transaction } from '../transactions/entitites/transaction.entity'; import { Repository } from 'typeorm'; import { PublicAccessStatusDto, PublicPlayerDto, PublicPlayerHistoryDto, PublicTeamDto, PublicTransactionDto, } from './dto/public-access.dto'; import { Team } from './entities/team.entity'; import { TeamAccessService } from './team-access.service'; @Injectable() export class PublicTeamAccessService { constructor( @InjectRepository(Team) private readonly teamRepository: Repository, @InjectRepository(Player) private readonly playerRepository: Repository, @InjectRepository(Transaction) private readonly transactionRepository: Repository, @InjectRepository(PenaltyEntity) private readonly penaltyRepository: Repository, private readonly access: TeamAccessService, ) {} async getStatus( userId: number, teamId: number, ): Promise { await this.access.assertMember(userId, teamId); return this.toStatus(await this.loadManagedTeam(teamId)); } async setEnabled( userId: number, teamId: number, enabled: boolean, ): Promise { await this.access.assertManager(userId, teamId); const team = await this.loadManagedTeam(teamId); if (enabled && !team.publicAccessToken) { team.publicAccessToken = this.createToken(); } team.publicAccessEnabled = enabled; await this.teamRepository.save(team); return this.toStatus(team); } async rotate(userId: number, teamId: number): Promise { await this.access.assertManager(userId, teamId); const team = await this.loadManagedTeam(teamId); team.publicAccessToken = this.createToken(); await this.teamRepository.save(team); return this.toStatus(team); } async getPublicTeam(token: string): Promise { const team = await this.loadPublicTeam(token); const [players, penalties] = await Promise.all([ this.playerRepository.find({ where: { team: { id: team.id }, active: true }, order: { lastName: 'ASC', firstName: 'ASC' }, }), this.penaltyRepository.find({ where: { team: { id: team.id } }, order: { description: 'ASC' }, }), ]); const publicPlayers = players.map((player) => this.toPublicPlayer(player)); return { name: team.name, balance: Number(team.balance), outstanding: publicPlayers.reduce( (total, player) => total - Number(player.balance), 0, ), players: publicPlayers, penalties: penalties.map((penalty) => ({ id: penalty.id, description: penalty.description, amount: Number(penalty.amount), })), }; } async getPublicPlayerHistory( token: string, playerId: number, ): Promise { const team = await this.loadPublicTeam(token); const player = await this.playerRepository.findOne({ where: { id: playerId, team: { id: team.id }, active: true }, }); if (!player) throw new NotFoundException('Freigabe nicht gefunden.'); return { player: this.toPublicPlayer(player), transactions: await this.loadTransactions(player.id), }; } async getMemberPlayerTransactions( userId: number, teamId: number, playerId: number, ): Promise { await this.access.assertMember(userId, teamId); const player = await this.playerRepository.findOne({ where: { id: playerId, team: { id: teamId } }, }); if (!player) throw new NotFoundException('Spieler nicht gefunden.'); return this.loadTransactions(player.id); } private async loadManagedTeam(teamId: number): Promise { const team = await this.teamRepository .createQueryBuilder('team') .addSelect('team.publicAccessToken') .where('team.id = :teamId', { teamId }) .getOne(); if (!team) throw new NotFoundException('Team nicht gefunden.'); return team; } private async loadPublicTeam(token: string): Promise { if (!/^[a-f0-9]{64}$/.test(token)) { throw new NotFoundException('Freigabe nicht gefunden.'); } const team = await this.teamRepository.findOne({ where: { publicAccessToken: token, publicAccessEnabled: true }, select: { id: true, name: true, balance: true, publicAccessEnabled: true, publicAccessToken: true, }, }); if (!team) throw new NotFoundException('Freigabe nicht gefunden.'); return team; } private async loadTransactions( playerId: number, ): Promise { const transactions = await this.transactionRepository.find({ where: { player: { id: playerId } }, order: { date: 'DESC' }, take: 20, }); return transactions.map((transaction) => ({ id: transaction.id, note: transaction.note, date: transaction.date, amount: Number(transaction.amount), type: transaction.type ? { id: transaction.type.id, name: transaction.type.name } : null, })); } private toPublicPlayer(player: Player): PublicPlayerDto { return { id: player.id, firstName: player.firstName, lastName: player.lastName, balance: Number(player.balance), active: player.active, }; } private toStatus(team: Team): PublicAccessStatusDto { return { enabled: team.publicAccessEnabled, token: team.publicAccessToken ?? null, }; } private createToken(): string { return randomBytes(32).toString('hex'); } }