/
kochkatech
/
CorpGame
Обзор
Документация
Войти
/
kochkatech
/
CorpGame
Код
Запросы
1
Задачи
Вики
Пакеты
0
Релизы
1
CI/CD
Аналитика
dev
backend/src/admin/admin-team.service.ts
106 строк
5 KB
kochkareal
chore: добавлен .gitattributes, нормализованы переносы строк (LF везде, кроме .bat/.cmd)
06 авг 2026, 15:18
06 авг 2026, 15:18
c25ef1b
Код
Авторство
О чём код?
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { RealtimeGateway } from '../realtime/realtime.gateway'; import { EventsService } from '../events/events.service'; import { GameConfig } from '../game/default-config'; import { withTeamLock } from '../common/team-lock'; /** Управление отдельными командами из панели администратора: переименование, удаление, «приколы и штрафы» */ @Injectable() export class AdminTeamService { constructor( private readonly prisma: PrismaService, private readonly realtime: RealtimeGateway, private readonly events: EventsService, ) {} private async getTeamOrThrow(teamId: string) { const team = await this.prisma.team.findUnique({ where: { id: teamId } }); if (!team) throw new NotFoundException('Команда не найдена'); return team; } async rename(teamId: string, name: string) { if (!name?.trim()) throw new BadRequestException('Название не может быть пустым'); const team = await this.getTeamOrThrow(teamId); const updated = await this.prisma.team.update({ where: { id: teamId }, data: { name: name.trim().slice(0, 60) } }); await this.broadcast(team.sessionId, teamId); return updated; } async remove(teamId: string) { const team = await this.getTeamOrThrow(teamId); await this.prisma.team.delete({ where: { id: teamId } }); this.realtime.emitStateUpdate(team.sessionId, { teamRemoved: teamId }); return { ok: true }; } async grant(teamId: string, resource: string, amount: number) { const team = await this.getTeamOrThrow(teamId); const session = await this.prisma.gameSession.findUnique({ where: { id: team.sessionId } }); const config = session?.config as unknown as GameConfig; if (!config?.resources.some((r) => r.key === resource)) throw new BadRequestException('Неизвестный ресурс'); if (!Number.isFinite(amount) || amount === 0) throw new BadRequestException('Некорректное количество'); // Под row-lock команды: без него — обычный read-modify-write JSON-поля resources, тот // же lost update, что чинит team-lock.ts у сбора/трансфера/кражи/обмена. Ведущий может // выдать ресурс живой командой во время игры, и это может совпасть по времени с // действием самих игроков — без лока одно из двух изменений тихо терялось бы. await withTeamLock(this.prisma, [teamId], async (tx, teams) => { const resources = teams.get(teamId)!.resources as Record<string, number>; const newAmount = Math.max(0, (resources[resource] ?? 0) + amount); await tx.team.update({ where: { id: teamId }, data: { resources: { ...resources, [resource]: newAmount } }, }); }); await this.events.logAndEmit({ sessionId: team.sessionId, type: 'admin:grant', targetTeamId: teamId, payload: { resource, amount }, }); this.realtime.emitTeamNotification(teamId, { kind: amount > 0 ? 'admin_grant' : 'admin_penalty', resource, amount, }); await this.broadcast(team.sessionId, teamId); return { ok: true }; } async applyEffect(teamId: string, multiplier: number, durationSec: number) { if (!Number.isFinite(multiplier) || multiplier <= 0) throw new BadRequestException('Некорректный множитель'); if (!Number.isFinite(durationSec) || durationSec <= 0) throw new BadRequestException('Некорректная длительность'); const team = await this.getTeamOrThrow(teamId); const cooldownEffectUntil = new Date(Date.now() + durationSec * 1000); await this.prisma.team.update({ where: { id: teamId }, data: { cooldownMultiplier: multiplier, cooldownEffectUntil }, }); await this.events.logAndEmit({ sessionId: team.sessionId, type: 'admin:effect', targetTeamId: teamId, payload: { multiplier, durationSec }, }); this.realtime.emitTeamNotification(teamId, { kind: multiplier < 1 ? 'admin_boost' : 'admin_slowdown', multiplier, durationSec, }); await this.broadcast(team.sessionId, teamId); return { ok: true }; } private async broadcast(sessionId: string, teamId: string) { const team = await this.prisma.team.findUnique({ where: { id: teamId }, include: { branches: { orderBy: { createdAt: 'asc' } } }, }); this.realtime.emitStateUpdate(sessionId, { teamUpdated: team }); } }