Commit 68dde1f4 authored by nanahira's avatar nanahira

update tournament cache clean

parent 8426d009
...@@ -19,9 +19,6 @@ import { SrvproService } from './srvpro/srvpro.service'; ...@@ -19,9 +19,6 @@ import { SrvproService } from './srvpro/srvpro.service';
import { SrvproController } from './srvpro/srvpro.controller'; import { SrvproController } from './srvpro/srvpro.controller';
import { HttpModule } from '@nestjs/axios'; import { HttpModule } from '@nestjs/axios';
import { AragamiModule } from 'nestjs-aragami'; import { AragamiModule } from 'nestjs-aragami';
import { TournamentSubscriberService } from './tournament-subscriber/tournament-subscriber.service';
import { ParticipantSubscriberService } from './participant-subscriber/participant-subscriber.service';
import { MatchSubscriberService } from './match-subscriber/match-subscriber.service';
@Module({ @Module({
imports: [ imports: [
...@@ -80,9 +77,6 @@ import { MatchSubscriberService } from './match-subscriber/match-subscriber.serv ...@@ -80,9 +77,6 @@ import { MatchSubscriberService } from './match-subscriber/match-subscriber.serv
ParticipantService, ParticipantService,
ApiKeyService, ApiKeyService,
SrvproService, SrvproService,
TournamentSubscriberService,
ParticipantSubscriberService,
MatchSubscriberService,
], ],
controllers: [ controllers: [
TournamentController, TournamentController,
......
import { Test, TestingModule } from '@nestjs/testing';
import { MatchSubscriberService } from './match-subscriber.service';
describe('MatchSubscriberService', () => {
let service: MatchSubscriberService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [MatchSubscriberService],
}).compile();
service = module.get<MatchSubscriberService>(MatchSubscriberService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
import { Injectable, OnModuleInit } from '@nestjs/common';
import {
DataSource,
EntitySubscriberInterface,
InsertEvent,
RemoveEvent,
SoftRemoveEvent,
UpdateEvent,
} from 'typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
import { Match } from '../match/entities/match.entity';
import { TournamentService } from '../tournament/tournament.service';
import { getFieldFromTypeormEvent } from '../utility/get-field-from-typeorm-event';
@Injectable()
export class MatchSubscriberService
implements EntitySubscriberInterface<Match>, OnModuleInit
{
constructor(
private tournamentService: TournamentService,
@InjectDataSource() private db: DataSource,
) {}
onModuleInit() {
this.db.subscribers.push(this);
}
listenTo() {
return Match;
}
async clearCacheOnEvent(
event:
| UpdateEvent<Match>
| RemoveEvent<Match>
| SoftRemoveEvent<Match>
| InsertEvent<Match>,
) {
let tournamentId = getFieldFromTypeormEvent(event, 'tournamentId');
if (!tournamentId) {
const id = getFieldFromTypeormEvent(event, 'id');
if (id) {
const match = await this.db
.getRepository(Match)
.findOne({ where: { id }, select: ['tournamentId'] });
tournamentId = match?.tournamentId;
}
}
if (tournamentId) {
await this.tournamentService.clearTournamentCache(tournamentId);
}
}
async afterInsert(event: InsertEvent<Match>) {
await this.clearCacheOnEvent(event);
}
async afterUpdate(event: UpdateEvent<Match>) {
await this.clearCacheOnEvent(event);
}
async afterRemove(event: RemoveEvent<Match>) {
await this.clearCacheOnEvent(event);
}
async afterSoftRemove(event: SoftRemoveEvent<Match>) {
await this.clearCacheOnEvent(event);
}
}
import { Test, TestingModule } from '@nestjs/testing';
import { ParticipantSubscriberService } from './participant-subscriber.service';
describe('ParticipantSubscriberService', () => {
let service: ParticipantSubscriberService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ParticipantSubscriberService],
}).compile();
service = module.get<ParticipantSubscriberService>(ParticipantSubscriberService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
import { Injectable, OnModuleInit } from '@nestjs/common';
import {
DataSource,
EntitySubscriberInterface,
InsertEvent,
RemoveEvent,
SoftRemoveEvent,
UpdateEvent,
} from 'typeorm';
import { InjectDataSource } from '@nestjs/typeorm';
import { Participant } from '../participant/entities/participant.entity';
import { TournamentService } from '../tournament/tournament.service';
import { getFieldFromTypeormEvent } from '../utility/get-field-from-typeorm-event';
@Injectable()
export class ParticipantSubscriberService
implements EntitySubscriberInterface<Participant>, OnModuleInit
{
constructor(
private tournamentService: TournamentService,
@InjectDataSource() private db: DataSource,
) {}
onModuleInit() {
this.db.subscribers.push(this);
}
listenTo() {
return Participant;
}
async clearCacheOnEvent(
event:
| UpdateEvent<Participant>
| RemoveEvent<Participant>
| SoftRemoveEvent<Participant>
| InsertEvent<Participant>,
) {
let tournamentId = getFieldFromTypeormEvent(event, 'tournamentId');
if (!tournamentId) {
const id = getFieldFromTypeormEvent(event, 'id');
if (id) {
const participant = await this.db
.getRepository(Participant)
.findOne({ where: { id }, select: ['tournamentId'] });
tournamentId = participant?.tournamentId;
}
}
if (tournamentId) {
await this.tournamentService.clearTournamentCache(tournamentId);
}
}
// 4 event handlers for different events
async afterUpdate(event: UpdateEvent<Participant>) {
await this.clearCacheOnEvent(event);
}
async afterSoftRemove(event: SoftRemoveEvent<Participant>) {
await this.clearCacheOnEvent(event);
}
async afterRemove(event: RemoveEvent<Participant>) {
await this.clearCacheOnEvent(event);
}
async afterInsert(event: InsertEvent<Participant>) {
await this.clearCacheOnEvent(event);
}
}
...@@ -9,6 +9,7 @@ import { ...@@ -9,6 +9,7 @@ import {
import { MycardUser } from 'nestjs-mycard'; import { MycardUser } from 'nestjs-mycard';
import { TournamentService } from '../tournament/tournament.service'; import { TournamentService } from '../tournament/tournament.service';
import { normalSeq } from '../utility/normal-seq'; import { normalSeq } from '../utility/normal-seq';
import _ from 'lodash';
@Injectable() @Injectable()
export class ParticipantService extends CrudService(Participant, { export class ParticipantService extends CrudService(Participant, {
...@@ -65,12 +66,28 @@ export class ParticipantService extends CrudService(Participant, { ...@@ -65,12 +66,28 @@ export class ParticipantService extends CrudService(Participant, {
user: MycardUser, user: MycardUser,
) { ) {
await this.checkPermissionOfParticipant(id, user, dto.seq === undefined); await this.checkPermissionOfParticipant(id, user, dto.seq === undefined);
return this.update(id, dto); const res = await this.update(id, dto);
const pt = await this.repo.manager.findOne(Participant, {
where: { id },
select: ['tournamentId'],
});
if (pt?.tournamentId) {
await this.tournamentService.clearTournamentCache(pt.tournamentId);
}
return res;
} }
async deleteParticipant(id: number, user: MycardUser) { async deleteParticipant(id: number, user: MycardUser) {
await this.checkPermissionOfParticipant(id, user); await this.checkPermissionOfParticipant(id, user);
return this.delete(id); const pt = await this.repo.manager.findOne(Participant, {
where: { id },
select: ['tournamentId'],
});
const res = await this.delete(id);
if (pt?.tournamentId) {
await this.tournamentService.clearTournamentCache(pt.tournamentId);
}
return res;
} }
async checkPermissionOfParticipant( async checkPermissionOfParticipant(
...@@ -117,7 +134,7 @@ export class ParticipantService extends CrudService(Participant, { ...@@ -117,7 +134,7 @@ export class ParticipantService extends CrudService(Participant, {
user: MycardUser | number, user: MycardUser | number,
) { ) {
normalSeq(participants); normalSeq(participants);
return this.importEntities(participants, async (p) => { const res = await this.importEntities(participants, async (p) => {
try { try {
await this.tournamentService.canModifyParticipants( await this.tournamentService.canModifyParticipants(
p.tournamentId, p.tournamentId,
...@@ -127,5 +144,15 @@ export class ParticipantService extends CrudService(Participant, { ...@@ -127,5 +144,15 @@ export class ParticipantService extends CrudService(Participant, {
return `玩家 ${p.name} 对应的比赛 ${p.tournamentId} 不存在或您没有权限。`; return `玩家 ${p.name} 对应的比赛 ${p.tournamentId} 不存在或您没有权限。`;
} }
}); });
const success = res.data
?.map((r) => r.entry?.tournamentId)
.filter((s) => s);
if (success && success.length > 0) {
await Promise.all(
_.uniq(success).map((tid) =>
this.tournamentService.clearTournamentCache(tid),
),
);
}
} }
} }
import { Test, TestingModule } from '@nestjs/testing';
import { TournamentSubscriberService } from './tournament-subscriber.service';
describe('TournamentSubscriberService', () => {
let service: TournamentSubscriberService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [TournamentSubscriberService],
}).compile();
service = module.get<TournamentSubscriberService>(TournamentSubscriberService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
import { Injectable, OnModuleInit } from '@nestjs/common';
import {
DataSource,
EntitySubscriberInterface,
RemoveEvent,
SoftRemoveEvent,
UpdateEvent,
} from 'typeorm';
import { Tournament } from '../tournament/entities/Tournament.entity';
import { TournamentService } from '../tournament/tournament.service';
import { InjectDataSource } from '@nestjs/typeorm';
import { getFieldFromTypeormEvent } from '../utility/get-field-from-typeorm-event';
@Injectable()
export class TournamentSubscriberService
implements EntitySubscriberInterface<Tournament>, OnModuleInit
{
constructor(
private tournamentService: TournamentService,
@InjectDataSource() private db: DataSource,
) {}
onModuleInit() {
this.db.subscribers.push(this);
}
listenTo() {
return Tournament;
}
async clearCacheOnEvent(
event:
| UpdateEvent<Tournament>
| SoftRemoveEvent<Tournament>
| RemoveEvent<Tournament>,
) {
await this.tournamentService.clearTournamentCache(
getFieldFromTypeormEvent(event, 'id'),
);
}
// three event handlers for different events
async afterUpdate(event: UpdateEvent<Tournament>) {
await this.clearCacheOnEvent(event);
}
async afterSoftRemove(event: SoftRemoveEvent<Tournament>) {
await this.clearCacheOnEvent(event);
}
async afterRemove(event: RemoveEvent<Tournament>) {
await this.clearCacheOnEvent(event);
}
}
...@@ -79,7 +79,6 @@ export class RuleSettings { ...@@ -79,7 +79,6 @@ export class RuleSettings {
hasThirdPlaceMatch: boolean; hasThirdPlaceMatch: boolean;
} }
@CacheTTL(7200000)
@Entity() @Entity()
export class Tournament extends DescBase { export class Tournament extends DescBase {
@QueryEqual() @QueryEqual()
......
...@@ -29,7 +29,25 @@ import { dragRearrange } from '../utility/drag-rearrange'; ...@@ -29,7 +29,25 @@ import { dragRearrange } from '../utility/drag-rearrange';
import { normalSeq } from '../utility/normal-seq'; import { normalSeq } from '../utility/normal-seq';
import { sortAfterSwiss } from '../utility/soft-after-swiss'; import { sortAfterSwiss } from '../utility/soft-after-swiss';
import { InjectAragami } from 'nestjs-aragami'; import { InjectAragami } from 'nestjs-aragami';
import { Aragami } from 'aragami'; import { Aragami, CacheKey, CacheTTL } from 'aragami';
import { Type } from 'class-transformer';
@CacheTTL(7200000)
class TournamentCache {
id: number;
noAnalytics: boolean;
@Type(() => Tournament)
tournament: Tournament;
@Type(() => Participant)
participants: Participant[];
@Type(() => Match)
matches: Match[];
@CacheKey()
cacheKey() {
return `tournament_cache:${this.id}:${this.noAnalytics ? 1 : 0}`;
}
}
@Injectable() @Injectable()
export class TournamentService extends CrudService(Tournament, { export class TournamentService extends CrudService(Tournament, {
...@@ -81,17 +99,26 @@ export class TournamentService extends CrudService(Tournament, { ...@@ -81,17 +99,26 @@ export class TournamentService extends CrudService(Tournament, {
qb.select(`${this.entityAliasName}.id`); qb.select(`${this.entityAliasName}.id`);
}); });
const key = `tournament_cache:${id}:${noAnalytics ? 1 : 0}`; const key = `tournament_cache:${id}:${noAnalytics ? 1 : 0}`;
const res = await this.aragami.cache(Tournament, key, async () => { const res = await this.aragami.cache(TournamentCache, key, async () => {
const result = await this.findOne(id); const result = await this.findOne(id);
if (result.data) { if (result.data) {
if (result.data.status === TournamentStatus.Ready && !noAnalytics) { if (result.data.status === TournamentStatus.Ready && !noAnalytics) {
result.data.analytics(); result.data.analytics();
} }
} }
return result.data; const cache = new TournamentCache();
cache.id = id;
cache.noAnalytics = noAnalytics;
cache.tournament = result.data;
cache.participants = result.data?.participants || [];
cache.matches = result.data?.matches || [];
return cache;
}); });
res.wipeDeckBuf(user); const tournament = res.tournament;
return new GenericReturnMessageDto(200, 'success', res); tournament.participants = res.participants;
tournament.matches = res.matches;
tournament.wipeDeckBuf(user);
return new GenericReturnMessageDto(200, 'success', tournament);
} }
async getTournaments(dto: Partial<Tournament>, user: MycardUser | number) { async getTournaments(dto: Partial<Tournament>, user: MycardUser | number) {
...@@ -128,12 +155,16 @@ export class TournamentService extends CrudService(Tournament, { ...@@ -128,12 +155,16 @@ export class TournamentService extends CrudService(Tournament, {
} }
} }
} }
return this.update(id, dto); const res = await this.update(id, dto);
await this.clearTournamentCache(id);
return res;
} }
async deleteTournament(id: number, user: MycardUser | number) { async deleteTournament(id: number, user: MycardUser | number) {
await this.checkPermissionOfTournament(id, user); await this.checkPermissionOfTournament(id, user);
return this.delete(id); const res = this.delete(id);
await this.clearTournamentCache(id);
return res;
} }
async checkPermissionOfTournament( async checkPermissionOfTournament(
...@@ -180,6 +211,7 @@ export class TournamentService extends CrudService(Tournament, { ...@@ -180,6 +211,7 @@ export class TournamentService extends CrudService(Tournament, {
await tdb.update(Tournament, id, { status: TournamentStatus.Ready }); await tdb.update(Tournament, id, { status: TournamentStatus.Ready });
await tdb.softDelete(Match, { tournamentId: id }); await tdb.softDelete(Match, { tournamentId: id });
}); });
await this.clearTournamentCache(id);
return new BlankReturnMessageDto(200, 'success'); return new BlankReturnMessageDto(200, 'success');
} }
...@@ -206,6 +238,7 @@ export class TournamentService extends CrudService(Tournament, { ...@@ -206,6 +238,7 @@ export class TournamentService extends CrudService(Tournament, {
await tournament.getRuleHandler(edb.getRepository(Match)).initialize(); await tournament.getRuleHandler(edb.getRepository(Match)).initialize();
await edb.update(Tournament, id, { status: TournamentStatus.Running }); await edb.update(Tournament, id, { status: TournamentStatus.Running });
}); });
await this.clearTournamentCache(id);
return new BlankReturnMessageDto(200, 'success'); return new BlankReturnMessageDto(200, 'success');
} }
...@@ -234,6 +267,7 @@ export class TournamentService extends CrudService(Tournament, { ...@@ -234,6 +267,7 @@ export class TournamentService extends CrudService(Tournament, {
).toException(); ).toException();
} }
await this.repo.update(id, { status: TournamentStatus.Finished }); await this.repo.update(id, { status: TournamentStatus.Finished });
await this.clearTournamentCache(id);
return new BlankReturnMessageDto(200, 'success'); return new BlankReturnMessageDto(200, 'success');
} }
...@@ -250,6 +284,7 @@ export class TournamentService extends CrudService(Tournament, { ...@@ -250,6 +284,7 @@ export class TournamentService extends CrudService(Tournament, {
await this.repo.manager.softDelete(Participant, { await this.repo.manager.softDelete(Participant, {
tournamentId: id, tournamentId: id,
}); });
await this.clearTournamentCache(id);
return new BlankReturnMessageDto(200, 'success'); return new BlankReturnMessageDto(200, 'success');
} }
...@@ -280,6 +315,7 @@ export class TournamentService extends CrudService(Tournament, { ...@@ -280,6 +315,7 @@ export class TournamentService extends CrudService(Tournament, {
), ),
); );
}); });
await this.clearTournamentCache(id);
} }
} }
...@@ -445,7 +481,6 @@ export class TournamentService extends CrudService(Tournament, { ...@@ -445,7 +481,6 @@ export class TournamentService extends CrudService(Tournament, {
}); });
} }
} }
return new GenericReturnMessageDto( return new GenericReturnMessageDto(
200, 200,
'success', 'success',
...@@ -476,7 +511,10 @@ export class TournamentService extends CrudService(Tournament, { ...@@ -476,7 +511,10 @@ export class TournamentService extends CrudService(Tournament, {
// shuffle // shuffle
shuffleArray(participants); shuffleArray(participants);
normalSeq(participants); normalSeq(participants);
return this.updateParticipantSeq(participants);
const res = await this.updateParticipantSeq(participants);
await this.clearTournamentCache(id);
return res;
} }
async dragParticipant( async dragParticipant(
...@@ -513,11 +551,15 @@ export class TournamentService extends CrudService(Tournament, { ...@@ -513,11 +551,15 @@ export class TournamentService extends CrudService(Tournament, {
: participants.findIndex((p) => p.id === dto.placeAfterParticipantId); : participants.findIndex((p) => p.id === dto.placeAfterParticipantId);
const { updates } = dragRearrange(participants, draggingIndex, insertAfter); const { updates } = dragRearrange(participants, draggingIndex, insertAfter);
return this.updateParticipantSeq(updates);
const res = await this.updateParticipantSeq(updates);
await this.clearTournamentCache(id);
return res;
} }
async clearTournamentCache(id: number) { async clearTournamentCache(id: number) {
if (!id) return; if (!id) return;
await this.aragami.clear(Tournament, `tournament_cache:${id}:`); this.log.log(`Clearing cache for tournament ${id}`);
await this.aragami.clear(TournamentCache, `tournament_cache:${id}:`);
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment