feat: 赛事管理操作日志与审计页体验优化
- API 为赛事/联赛/盘口/赔率/冠军盘写操作写入 CATALOG 审计日志,并新增 catalog-audit-logs 接口 - 管理端将赛事日志独立至赛事管理子页,通用操作日志排除 CATALOG 模块 - 统一赛事子页 list-chrome 布局与面包屑;修复操作日志页表格滚动与分页不可见问题 - 补充中英文/马来语文案及 UAT 验收项 SEC013/SEC014 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -35,6 +35,12 @@ import { SettlementService } from '../../domains/settlement/settlement.service';
|
||||
import { CashbackService } from '../../domains/operations/cashback/cashback.service';
|
||||
import { I18nService } from '../../domains/operations/i18n/i18n.service';
|
||||
import { AuditService } from '../../domains/operations/audit/audit.service';
|
||||
import {
|
||||
CatalogAuditAction,
|
||||
CATALOG_AUDIT_MODULE,
|
||||
logCatalogAudit,
|
||||
summarizeOddsUpdates,
|
||||
} from '../../domains/operations/audit/catalog-audit';
|
||||
import { BetsService } from '../../domains/betting/bets.service';
|
||||
import { BettingLimitsService } from '../../domains/betting/betting-limits.service';
|
||||
import { PrismaService } from '../../shared/prisma/prisma.service';
|
||||
@@ -1825,11 +1831,13 @@ export class AdminController {
|
||||
@Post('leagues')
|
||||
@RequirePermissions(P.matches)
|
||||
async createLeague(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Body() dto: CreatePlatformLeagueDto | { code: string; translations: Record<string, string> },
|
||||
) {
|
||||
let league;
|
||||
if ('leagueZh' in dto || 'leagueEn' in dto) {
|
||||
const body = dto as CreatePlatformLeagueDto;
|
||||
const league = await this.matches.createPlatformLeague({
|
||||
league = await this.matches.createPlatformLeague({
|
||||
leagueEn: body.leagueEn,
|
||||
leagueZh: body.leagueZh,
|
||||
leagueMs: body.leagueMs,
|
||||
@@ -1837,16 +1845,26 @@ export class AdminController {
|
||||
displayOrder: body.displayOrder,
|
||||
isActive: body.isActive,
|
||||
});
|
||||
return jsonResponse(league);
|
||||
} else {
|
||||
const legacy = dto as { code: string; translations: Record<string, string> };
|
||||
league = await this.matches.createLeague(legacy.code, legacy.translations);
|
||||
}
|
||||
const legacy = dto as { code: string; translations: Record<string, string> };
|
||||
const league = await this.matches.createLeague(legacy.code, legacy.translations);
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.CREATE_LEAGUE,
|
||||
targetId: league.id,
|
||||
afterData:
|
||||
'leagueZh' in dto || 'leagueEn' in dto
|
||||
? { leagueEn: (dto as CreatePlatformLeagueDto).leagueEn, leagueZh: (dto as CreatePlatformLeagueDto).leagueZh }
|
||||
: { code: (dto as { code: string }).code },
|
||||
});
|
||||
return jsonResponse(league);
|
||||
}
|
||||
|
||||
@Put('leagues/:leagueId')
|
||||
@RequirePermissions(P.matches)
|
||||
async updateLeague(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('leagueId') leagueId: string,
|
||||
@Body() dto: CreatePlatformLeagueDto,
|
||||
) {
|
||||
@@ -1858,6 +1876,12 @@ export class AdminController {
|
||||
displayOrder: dto.displayOrder,
|
||||
isActive: dto.isActive,
|
||||
});
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.UPDATE_LEAGUE,
|
||||
targetId: leagueId,
|
||||
afterData: { leagueEn: dto.leagueEn, leagueZh: dto.leagueZh, isActive: dto.isActive },
|
||||
});
|
||||
return jsonResponse(league);
|
||||
}
|
||||
|
||||
@@ -1870,8 +1894,17 @@ export class AdminController {
|
||||
|
||||
@Post('leagues/:leagueId/archive')
|
||||
@RequirePermissions(P.matches)
|
||||
async archiveLeague(@Param('leagueId') leagueId: string) {
|
||||
async archiveLeague(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('leagueId') leagueId: string,
|
||||
) {
|
||||
const result = await this.catalogArchive.archiveLeague(BigInt(leagueId));
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.ARCHIVE_LEAGUE,
|
||||
targetId: leagueId,
|
||||
afterData: result,
|
||||
});
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@@ -1925,8 +1958,17 @@ export class AdminController {
|
||||
|
||||
@Post('teams')
|
||||
@RequirePermissions(P.matches)
|
||||
async createTeam(@Body() dto: { code: string; translations: Record<string, string> }) {
|
||||
async createTeam(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Body() dto: { code: string; translations: Record<string, string> },
|
||||
) {
|
||||
const team = await this.matches.createTeam(dto.code, dto.translations);
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.CREATE_TEAM,
|
||||
targetId: team.id,
|
||||
afterData: { code: dto.code },
|
||||
});
|
||||
return jsonResponse(team);
|
||||
}
|
||||
|
||||
@@ -1999,13 +2041,29 @@ export class AdminController {
|
||||
updatedBy: operatorId,
|
||||
});
|
||||
await this.outright.syncOutrightTeamsForLeagueIfExists(match.leagueId);
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.UPDATE_MATCH,
|
||||
targetId: id,
|
||||
afterData: {
|
||||
status: match.status,
|
||||
startTime: match.startTime,
|
||||
isHot: match.isHot,
|
||||
matchName: match.matchName,
|
||||
},
|
||||
});
|
||||
return jsonResponse(match);
|
||||
}
|
||||
|
||||
@Delete('matches/:id')
|
||||
@RequirePermissions(P.matches)
|
||||
async deleteMatch(@Param('id') id: string) {
|
||||
async deleteMatch(@CurrentUser('id') operatorId: bigint, @Param('id') id: string) {
|
||||
await this.matches.deleteMatch(BigInt(id));
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.DELETE_MATCH,
|
||||
targetId: id,
|
||||
});
|
||||
return jsonResponse({ deleted: true });
|
||||
}
|
||||
|
||||
@@ -2018,7 +2076,11 @@ export class AdminController {
|
||||
|
||||
@Post('matches/:id/archive')
|
||||
@RequirePermissions(P.matches)
|
||||
async archiveMatch(@Param('id') id: string, @Body() dto: ArchiveMatchDto) {
|
||||
async archiveMatch(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
@Body() dto: ArchiveMatchDto,
|
||||
) {
|
||||
const matchId = BigInt(id);
|
||||
const result = await this.catalogArchive.archiveMatch(matchId, {
|
||||
force: dto.force === true,
|
||||
@@ -2028,6 +2090,12 @@ export class AdminController {
|
||||
const voided = await this.settlement.voidMatchBets(matchId);
|
||||
voidedCount = voided.voidedCount;
|
||||
}
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.ARCHIVE_MATCH,
|
||||
targetId: id,
|
||||
afterData: { ...result, force: dto.force === true, voidedCount },
|
||||
});
|
||||
return jsonResponse({ ...result, voidedCount });
|
||||
}
|
||||
|
||||
@@ -2059,6 +2127,17 @@ export class AdminController {
|
||||
createdBy: operatorId,
|
||||
});
|
||||
await this.outright.syncOutrightTeamsForLeagueIfExists(match.leagueId);
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.CREATE_MATCH,
|
||||
targetId: match.id,
|
||||
afterData: {
|
||||
status: match.status,
|
||||
startTime: match.startTime,
|
||||
leagueId: match.leagueId?.toString(),
|
||||
matchName: match.matchName,
|
||||
},
|
||||
});
|
||||
return jsonResponse(match);
|
||||
}
|
||||
|
||||
@@ -2069,42 +2148,86 @@ export class AdminController {
|
||||
throw appBadRequest('IMPORT_MATCHES_REQUIRED');
|
||||
}
|
||||
const result = await this.matches.importZhiboMatchesBundle(dto, operatorId);
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.IMPORT_MATCHES,
|
||||
afterData: {
|
||||
total: result.total,
|
||||
imported: result.imported,
|
||||
skipped: result.skipped,
|
||||
failed: result.failed,
|
||||
},
|
||||
});
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@Post('matches/:id/publish')
|
||||
@RequirePermissions(P.matches)
|
||||
async publishMatch(@Param('id') id: string) {
|
||||
async publishMatch(@CurrentUser('id') operatorId: bigint, @Param('id') id: string) {
|
||||
const match = await this.matches.publishMatch(BigInt(id));
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.PUBLISH_MATCH,
|
||||
targetId: id,
|
||||
afterData: { status: match.status, publishTime: match.publishTime },
|
||||
});
|
||||
return jsonResponse(match);
|
||||
}
|
||||
|
||||
@Post('matches/:id/unpublish')
|
||||
@RequirePermissions(P.matches)
|
||||
async unpublishMatch(@Param('id') id: string) {
|
||||
async unpublishMatch(@CurrentUser('id') operatorId: bigint, @Param('id') id: string) {
|
||||
const match = await this.matches.unpublishMatch(BigInt(id));
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.UNPUBLISH_MATCH,
|
||||
targetId: id,
|
||||
afterData: { status: match.status },
|
||||
});
|
||||
return jsonResponse(match);
|
||||
}
|
||||
|
||||
@Post('matches/:id/close')
|
||||
@RequirePermissions(P.matches)
|
||||
async closeMatch(@Param('id') id: string) {
|
||||
async closeMatch(@CurrentUser('id') operatorId: bigint, @Param('id') id: string) {
|
||||
const match = await this.matches.closeMatch(BigInt(id));
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.CLOSE_MATCH,
|
||||
targetId: id,
|
||||
afterData: { status: match.status, closeTime: match.closeTime },
|
||||
});
|
||||
return jsonResponse(match);
|
||||
}
|
||||
|
||||
@Post('matches/:id/reopen')
|
||||
@RequirePermissions(P.matches)
|
||||
async reopenMatch(@Param('id') id: string, @Body() dto: ReopenMatchDto) {
|
||||
async reopenMatch(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
@Body() dto: ReopenMatchDto,
|
||||
) {
|
||||
const startTime = dto.startTime ? parseMatchStartTime(dto.startTime) : undefined;
|
||||
const match = await this.matches.reopenMatch(BigInt(id), startTime);
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.REOPEN_MATCH,
|
||||
targetId: id,
|
||||
afterData: { status: match.status, startTime: match.startTime },
|
||||
});
|
||||
return jsonResponse(match);
|
||||
}
|
||||
|
||||
@Post('matches/:id/cancel')
|
||||
@RequirePermissions(P.matches)
|
||||
async cancelMatch(@Param('id') id: string) {
|
||||
async cancelMatch(@CurrentUser('id') operatorId: bigint, @Param('id') id: string) {
|
||||
const voided = await this.settlement.cancelMatchAndVoidBets(BigInt(id));
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.CANCEL_MATCH,
|
||||
targetId: id,
|
||||
afterData: voided,
|
||||
});
|
||||
return jsonResponse(voided);
|
||||
}
|
||||
|
||||
@@ -2123,8 +2246,17 @@ export class AdminController {
|
||||
|
||||
@Post('market-templates')
|
||||
@RequirePermissions(P.matches)
|
||||
async createMarketTemplate(@Body() dto: MarketTemplateSaveDto) {
|
||||
async createMarketTemplate(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Body() dto: MarketTemplateSaveDto,
|
||||
) {
|
||||
const template = await this.markets.createTemplate(dto);
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.CREATE_MARKET_TEMPLATE,
|
||||
targetId: template.id,
|
||||
afterData: { name: template.name, sportType: template.sportType },
|
||||
});
|
||||
return jsonResponse(template);
|
||||
}
|
||||
|
||||
@@ -2137,39 +2269,87 @@ export class AdminController {
|
||||
|
||||
@Put('market-templates/:id')
|
||||
@RequirePermissions(P.matches)
|
||||
async updateMarketTemplate(@Param('id') id: string, @Body() dto: MarketTemplateSaveDto) {
|
||||
async updateMarketTemplate(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
@Body() dto: MarketTemplateSaveDto,
|
||||
) {
|
||||
const template = await this.markets.updateTemplate(BigInt(id), dto);
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.UPDATE_MARKET_TEMPLATE,
|
||||
targetId: id,
|
||||
afterData: { name: template.name, sportType: template.sportType },
|
||||
});
|
||||
return jsonResponse(template);
|
||||
}
|
||||
|
||||
@Post('market-templates/:id/duplicate')
|
||||
@RequirePermissions(P.matches)
|
||||
async duplicateMarketTemplate(@Param('id') id: string) {
|
||||
async duplicateMarketTemplate(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
const template = await this.markets.duplicateTemplate(BigInt(id));
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.DUPLICATE_MARKET_TEMPLATE,
|
||||
targetId: template.id,
|
||||
afterData: { sourceTemplateId: id, name: template.name },
|
||||
});
|
||||
return jsonResponse(template);
|
||||
}
|
||||
|
||||
@Post('market-templates/:id/set-default')
|
||||
@RequirePermissions(P.matches)
|
||||
async setDefaultMarketTemplate(@Param('id') id: string) {
|
||||
async setDefaultMarketTemplate(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
const template = await this.markets.setDefaultTemplate(BigInt(id));
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.SET_DEFAULT_MARKET_TEMPLATE,
|
||||
targetId: id,
|
||||
afterData: { name: template.name },
|
||||
});
|
||||
return jsonResponse(template);
|
||||
}
|
||||
|
||||
@Post('matches/:id/markets/templates')
|
||||
@RequirePermissions(P.matches)
|
||||
async generateTemplates(@Param('id') id: string, @Body() dto: MarketTemplatesDto) {
|
||||
async generateTemplates(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
@Body() dto: MarketTemplatesDto,
|
||||
) {
|
||||
const markets = await this.markets.generateTemplates(BigInt(id), dto.marketTypes);
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.GENERATE_MATCH_MARKETS,
|
||||
targetId: id,
|
||||
afterData: { marketTypes: dto.marketTypes, ...markets },
|
||||
});
|
||||
return jsonResponse(markets);
|
||||
}
|
||||
|
||||
@Post('matches/:id/markets/apply-template')
|
||||
@RequirePermissions(P.matches)
|
||||
async applyMarketTemplate(@Param('id') id: string, @Body() dto: ApplyMarketTemplateDto) {
|
||||
async applyMarketTemplate(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
@Body() dto: ApplyMarketTemplateDto,
|
||||
) {
|
||||
const result = await this.markets.applyTemplateToMatch(
|
||||
BigInt(id),
|
||||
dto.templateId ? BigInt(dto.templateId) : null,
|
||||
);
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.APPLY_MARKET_TEMPLATE,
|
||||
targetId: id,
|
||||
afterData: { requestedTemplateId: dto.templateId, ...result },
|
||||
});
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@@ -2181,6 +2361,12 @@ export class AdminController {
|
||||
@Body() dto: BulkMatchMarketsDto,
|
||||
) {
|
||||
const result = await this.markets.saveMatchMarkets(BigInt(id), dto.markets, operatorId);
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.BULK_SAVE_MATCH_MARKETS,
|
||||
targetId: id,
|
||||
afterData: { ...result, marketCount: dto.markets.length },
|
||||
});
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@@ -2196,12 +2382,22 @@ export class AdminController {
|
||||
odds: u.odds,
|
||||
}));
|
||||
const results = await this.markets.batchUpdateOdds(updates, operatorId);
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.UPDATE_MATCH_ODDS,
|
||||
targetId: id,
|
||||
afterData: summarizeOddsUpdates(updates.map((u) => u.selectionId)),
|
||||
});
|
||||
return jsonResponse({ matchId: id, updated: results.length });
|
||||
}
|
||||
|
||||
@Patch('markets/:id')
|
||||
@RequirePermissions(P.matches)
|
||||
async updateMarket(@Param('id') id: string, @Body() dto: UpdateMarketDto) {
|
||||
async updateMarket(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('id') id: string,
|
||||
@Body() dto: UpdateMarketDto,
|
||||
) {
|
||||
const market = await this.markets.updateMarket(BigInt(id), {
|
||||
promoLabel: dto.promoLabel,
|
||||
promoLabelI18n: dto.promoLabelI18n,
|
||||
@@ -2210,6 +2406,17 @@ export class AdminController {
|
||||
lineValue: dto.lineValue,
|
||||
showOnPlayer: dto.showOnPlayer,
|
||||
});
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.UPDATE_MARKET,
|
||||
targetId: id,
|
||||
afterData: {
|
||||
matchId: market.matchId.toString(),
|
||||
status: market.status,
|
||||
lineValue: market.lineValue,
|
||||
showOnPlayer: market.showOnPlayer,
|
||||
},
|
||||
});
|
||||
return jsonResponse(market);
|
||||
}
|
||||
|
||||
@@ -2230,6 +2437,20 @@ export class AdminController {
|
||||
},
|
||||
operatorId,
|
||||
);
|
||||
const market = await this.prisma.market.findUnique({
|
||||
where: { id: selection.marketId },
|
||||
select: { matchId: true },
|
||||
});
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.UPDATE_SELECTION,
|
||||
targetId: id,
|
||||
afterData: {
|
||||
matchId: market?.matchId?.toString(),
|
||||
odds: dto.odds,
|
||||
status: dto.status,
|
||||
},
|
||||
});
|
||||
return jsonResponse(selection);
|
||||
}
|
||||
|
||||
@@ -2241,6 +2462,16 @@ export class AdminController {
|
||||
@Body() dto: UpdateOddsDto,
|
||||
) {
|
||||
const selection = await this.markets.updateOdds(BigInt(id), dto.odds, operatorId);
|
||||
const market = await this.prisma.market.findUnique({
|
||||
where: { id: selection.marketId },
|
||||
select: { matchId: true },
|
||||
});
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.UPDATE_MATCH_ODDS,
|
||||
targetId: market?.matchId ?? id,
|
||||
afterData: { selectionId: id, odds: dto.odds },
|
||||
});
|
||||
return jsonResponse(selection);
|
||||
}
|
||||
|
||||
@@ -2260,7 +2491,10 @@ export class AdminController {
|
||||
|
||||
@Post('outrights')
|
||||
@RequirePermissions(P.matches)
|
||||
async createOutright(@Body() dto: CreateOutrightDto) {
|
||||
async createOutright(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Body() dto: CreateOutrightDto,
|
||||
) {
|
||||
const data = await this.outright.createForAdmin({
|
||||
leagueId: BigInt(dto.leagueId),
|
||||
titleZh: dto.titleZh,
|
||||
@@ -2268,13 +2502,24 @@ export class AdminController {
|
||||
titleMs: dto.titleMs,
|
||||
status: dto.status,
|
||||
});
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.CREATE_OUTRIGHT,
|
||||
targetId: data.id,
|
||||
afterData: { leagueId: dto.leagueId, status: dto.status, titleZh: dto.titleZh },
|
||||
});
|
||||
return jsonResponse(data);
|
||||
}
|
||||
|
||||
@Post('outrights/import/wc2026')
|
||||
@RequirePermissions(P.matches)
|
||||
async importWc2026Outright() {
|
||||
async importWc2026Outright(@CurrentUser('id') operatorId: bigint) {
|
||||
const data = await this.outright.importWc2026Canonical();
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.IMPORT_WC2026_OUTRIGHT,
|
||||
afterData: data,
|
||||
});
|
||||
return jsonResponse(data);
|
||||
}
|
||||
|
||||
@@ -2298,16 +2543,27 @@ export class AdminController {
|
||||
const list = await this.outright.listForAdmin();
|
||||
const wc = list.find((e) => e.leagueCode === 'WC2026');
|
||||
if (!wc) throw appBadRequest('WC_OUTRIGHT_NOT_FOUND');
|
||||
return jsonResponse(
|
||||
await this.outright.batchUpdateOdds(BigInt(wc.id), dto.updates, operatorId),
|
||||
);
|
||||
const data = await this.outright.batchUpdateOdds(BigInt(wc.id), dto.updates, operatorId);
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.UPDATE_OUTRIGHT_ODDS,
|
||||
targetId: wc.id,
|
||||
afterData: summarizeOddsUpdates(dto.updates.map((u) => u.selectionId)),
|
||||
});
|
||||
return jsonResponse(data);
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
@Post('outrights/wc2026/apply-canonical')
|
||||
@RequirePermissions(P.matches)
|
||||
async applyWc2026CanonicalLegacy() {
|
||||
return jsonResponse(await this.outright.importWc2026Canonical());
|
||||
async applyWc2026CanonicalLegacy(@CurrentUser('id') operatorId: bigint) {
|
||||
const data = await this.outright.importWc2026Canonical();
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.IMPORT_WC2026_OUTRIGHT,
|
||||
afterData: data,
|
||||
});
|
||||
return jsonResponse(data);
|
||||
}
|
||||
|
||||
@Get('outrights/:matchId')
|
||||
@@ -2320,10 +2576,17 @@ export class AdminController {
|
||||
@Put('outrights/:matchId')
|
||||
@RequirePermissions(P.matches)
|
||||
async updateOutright(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('matchId') matchId: string,
|
||||
@Body() dto: UpdateOutrightDto,
|
||||
) {
|
||||
const data = await this.outright.updateForAdmin(BigInt(matchId), dto);
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.UPDATE_OUTRIGHT,
|
||||
targetId: matchId,
|
||||
afterData: { status: dto.status, matchName: dto.matchName, isHot: dto.isHot },
|
||||
});
|
||||
return jsonResponse(data);
|
||||
}
|
||||
|
||||
@@ -2339,22 +2602,36 @@ export class AdminController {
|
||||
dto.updates,
|
||||
operatorId,
|
||||
);
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.UPDATE_OUTRIGHT_ODDS,
|
||||
targetId: matchId,
|
||||
afterData: summarizeOddsUpdates(dto.updates.map((u) => u.selectionId)),
|
||||
});
|
||||
return jsonResponse(data);
|
||||
}
|
||||
|
||||
@Post('outrights/:matchId/selections')
|
||||
@RequirePermissions(P.matches)
|
||||
async addOutrightSelection(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('matchId') matchId: string,
|
||||
@Body() dto: AddOutrightSelectionDto,
|
||||
) {
|
||||
const data = await this.outright.addSelection(BigInt(matchId), dto);
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.ADD_OUTRIGHT_SELECTION,
|
||||
targetId: matchId,
|
||||
afterData: dto,
|
||||
});
|
||||
return jsonResponse(data);
|
||||
}
|
||||
|
||||
@Post('outrights/:matchId/selections/batch')
|
||||
@RequirePermissions(P.matches)
|
||||
async addOutrightSelectionsBatch(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('matchId') matchId: string,
|
||||
@Body() dto: AddOutrightSelectionsBatchDto,
|
||||
) {
|
||||
@@ -2365,12 +2642,19 @@ export class AdminController {
|
||||
BigInt(matchId),
|
||||
dto.items,
|
||||
);
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.BATCH_ADD_OUTRIGHT_SELECTIONS,
|
||||
targetId: matchId,
|
||||
afterData: { count: dto.items.length },
|
||||
});
|
||||
return jsonResponse(data);
|
||||
}
|
||||
|
||||
@Patch('outrights/:matchId/selections/:selectionId')
|
||||
@RequirePermissions(P.matches)
|
||||
async updateOutrightSelectionTeam(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('matchId') matchId: string,
|
||||
@Param('selectionId') selectionId: string,
|
||||
@Body() dto: UpdateOutrightSelectionTeamDto,
|
||||
@@ -2380,12 +2664,19 @@ export class AdminController {
|
||||
BigInt(selectionId),
|
||||
dto,
|
||||
);
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.UPDATE_OUTRIGHT_SELECTION,
|
||||
targetId: selectionId,
|
||||
afterData: { matchId, ...dto },
|
||||
});
|
||||
return jsonResponse(data);
|
||||
}
|
||||
|
||||
@Delete('outrights/:matchId/selections/:selectionId')
|
||||
@RequirePermissions(P.matches)
|
||||
async removeOutrightSelection(
|
||||
@CurrentUser('id') operatorId: bigint,
|
||||
@Param('matchId') matchId: string,
|
||||
@Param('selectionId') selectionId: string,
|
||||
) {
|
||||
@@ -2393,6 +2684,12 @@ export class AdminController {
|
||||
BigInt(matchId),
|
||||
BigInt(selectionId),
|
||||
);
|
||||
await logCatalogAudit(this.audit, {
|
||||
operatorId,
|
||||
action: CatalogAuditAction.REMOVE_OUTRIGHT_SELECTION,
|
||||
targetId: selectionId,
|
||||
afterData: { matchId },
|
||||
});
|
||||
return jsonResponse(data);
|
||||
}
|
||||
|
||||
@@ -2927,7 +3224,28 @@ export class AdminController {
|
||||
const result = await this.audit.list(
|
||||
page ? parseInt(page, 10) : 1,
|
||||
pageSize ? parseInt(pageSize, 10) : 10,
|
||||
module || undefined,
|
||||
{
|
||||
module: module || undefined,
|
||||
excludeModule: CATALOG_AUDIT_MODULE,
|
||||
},
|
||||
{ viewerId, viewerRole, viewerUserType },
|
||||
);
|
||||
return jsonResponse(result);
|
||||
}
|
||||
|
||||
@Get('catalog-audit-logs')
|
||||
@RequirePermissions(P.matches)
|
||||
async catalogAuditLogs(
|
||||
@CurrentUser('id') viewerId: bigint,
|
||||
@CurrentUser('role') viewerRole: string | undefined,
|
||||
@CurrentUser('userType') viewerUserType: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
) {
|
||||
const result = await this.audit.list(
|
||||
page ? parseInt(page, 10) : 1,
|
||||
pageSize ? parseInt(pageSize, 10) : 10,
|
||||
{ module: CATALOG_AUDIT_MODULE },
|
||||
{ viewerId, viewerRole, viewerUserType },
|
||||
);
|
||||
return jsonResponse(result);
|
||||
|
||||
Reference in New Issue
Block a user