Commit c9131dce authored by nanahira's avatar nanahira

logic left

parent eed3242a
......@@ -5,6 +5,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { TournamentModule } from './tournament/tournament.module';
import { MycardAuthModule } from 'nestjs-mycard';
import { ParticipantModule } from './participant/participant.module';
import { MatchModule } from './match/match.module';
@Module({
imports: [
......@@ -36,6 +37,7 @@ import { ParticipantModule } from './participant/participant.module';
}),
TournamentModule,
ParticipantModule,
MatchModule,
],
})
export class AppModule {}
import { Entity, ManyToOne } from 'typeorm';
import { EnumColumn, IdBase, IntColumn, NotChangeable, NotColumn } from 'nicot';
import { TournamentIdColumn } from '../../utility/decorators/tournament-id-column';
import { Tournament } from '../../tournament/entities/Tournament.entity';
import { Participant } from '../../participant/entities/participant.entity';
enum MatchStatus {
Pending = 'Pending',
Running = 'Running',
Finished = 'Finished',
}
@Entity()
export class Match extends IdBase() {
@NotChangeable()
@TournamentIdColumn(true)
tournamentId: number;
@NotColumn()
@ManyToOne(() => Tournament, (tournament) => tournament.matches)
tournament: Tournament;
@NotChangeable()
@IntColumn('smallint', {
unsigned: true,
required: true,
description: '比赛轮次。',
})
round: number;
@EnumColumn(MatchStatus, {
default: MatchStatus.Pending,
description: '比赛状态',
})
@NotChangeable()
status: MatchStatus;
@NotChangeable()
@IntColumn('bigint', {
unsigned: true,
description: '玩家 1 ID',
})
player1Id: number;
@IntColumn('mediumint', { description: '玩家 1 分数', required: false })
player1Score: number;
@NotColumn()
@ManyToOne(() => Participant, (participant) => participant.matches1)
player1: Participant;
@NotChangeable()
@IntColumn('bigint', {
unsigned: true,
description: '玩家 2 ID',
})
player2Id: number;
@IntColumn('mediumint', { description: '玩家 2 分数', required: false })
player2Score: number;
@NotColumn()
@ManyToOne(() => Participant, (participant) => participant.matches2)
player2: Participant;
@IntColumn('bigint', {
unsigned: true,
description: '胜者 ID',
})
winnerId: number;
@NotColumn()
@ManyToOne(() => Participant, (participant) => participant.wonMatches)
winner: Participant;
@NotChangeable()
@IntColumn('bigint', {
unsigned: true,
description: '晋级通往的比赛 ID',
required: false,
})
childMatchId: number;
@NotColumn()
@ManyToOne(() => Match, (match) => match.parentMatches)
childMatch: Match;
@NotColumn()
@ManyToOne(() => Match, (match) => match.childMatch)
parentMatches: Match[];
}
import { Test, TestingModule } from '@nestjs/testing';
import { MatchController } from './match.controller';
import { MatchService } from './match.service';
describe('MatchController', () => {
let controller: MatchController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [MatchController],
providers: [MatchService],
}).compile();
controller = module.get<MatchController>(MatchController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});
import { Controller } from '@nestjs/common';
import { MatchService } from './match.service';
import { RestfulFactory } from 'nicot';
import { Match } from './entities/match.entity';
import { ApiHeader, ApiTags } from '@nestjs/swagger';
import { MycardUser, PutMycardUser } from 'nestjs-mycard';
const factory = new RestfulFactory(Match);
class FindMatchDto extends factory.findAllDto {}
class UpdateMatchDto extends factory.updateDto {}
@Controller('match')
@ApiHeader({ name: 'Authorization', required: false, description: 'MC Token' })
@ApiTags('match')
export class MatchController {
constructor(private readonly matchService: MatchService) {}
@factory.findOne()
findOne(@factory.idParam() id: number, @PutMycardUser() user: MycardUser) {
return this.matchService.getMatch(id, user);
}
@factory.findAll()
findAll(
@factory.findAllParam() dto: FindMatchDto,
@PutMycardUser() user: MycardUser,
) {
return this.matchService.getMatches(dto, user);
}
}
import { Module } from '@nestjs/common';
import { MatchService } from './match.service';
import { MatchController } from './match.controller';
import { TournamentModule } from '../tournament/tournament.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Match } from './entities/match.entity';
@Module({
controllers: [MatchController],
providers: [MatchService],
imports: [TypeOrmModule.forFeature([Match]), TournamentModule],
})
export class MatchModule {}
import { Test, TestingModule } from '@nestjs/testing';
import { MatchService } from './match.service';
describe('MatchService', () => {
let service: MatchService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [MatchService],
}).compile();
service = module.get<MatchService>(MatchService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});
import { Injectable } from '@nestjs/common';
import { CrudService, Inner } from 'nicot';
import { Match } from './entities/match.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { MycardUser } from 'nestjs-mycard';
import { Tournament } from '../tournament/entities/Tournament.entity';
import { Participant } from '../participant/entities/participant.entity';
@Injectable()
export class MatchService extends CrudService(Match, {
relations: [Inner('tournament'), 'player1', 'player2', 'winner'],
}) {
constructor(@InjectRepository(Match) repo) {
super(repo);
}
getMatch(id: number, user: MycardUser) {
return this.findOne(id, (qb) =>
Tournament.extraQueryForUser(user, qb, 'tournament'),
);
}
getMatches(dto: Partial<Participant>, user: MycardUser) {
return this.findAll(dto, (qb) =>
Tournament.extraQueryForUser(user, qb, 'tournament'),
);
}
}
import { Entity, ManyToOne, SelectQueryBuilder } from 'typeorm';
import { Entity, ManyToOne, OneToMany, SelectQueryBuilder } from 'typeorm';
import { NamedBase } from '../../utility/NamedBase.entity';
import {
applyQueryProperty,
......@@ -12,6 +12,8 @@ import {
TournamentVisibility,
} from '../../tournament/entities/Tournament.entity';
import { MycardUser } from 'nestjs-mycard';
import { Match } from '../../match/entities/match.entity';
import { ApiProperty } from '@nestjs/swagger';
@Entity()
export class Participant extends NamedBase {
......@@ -26,6 +28,31 @@ export class Participant extends NamedBase {
@ManyToOne(() => Tournament, (tournament) => tournament.participants)
tournament: Tournament;
@NotColumn()
@OneToMany(() => Match, (match) => match.player1)
matches1: Match[];
@NotColumn()
@OneToMany(() => Match, (match) => match.player2)
matches2: Match[];
@NotColumn()
@OneToMany(() => Match, (match) => match.winner)
wonMatches: Match[];
@NotColumn()
@ApiProperty({ type: [Match], description: '参与的比赛。' })
matches: Match[];
getMatches() {
return this.matches1.concat(this.matches2);
}
async afterGet() {
await super.afterGet();
this.matches = this.getMatches();
}
checkPermission(user: MycardUser) {
return this.tournament?.checkPermission(user);
}
......
......@@ -13,7 +13,8 @@ import { MycardUser } from 'nestjs-mycard';
import { DescBase } from '../../utility/NamedBase.entity';
import { Participant } from '../../participant/entities/participant.entity';
import { IsArray, IsInt, IsPositive } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { ApiProperty, PartialType } from '@nestjs/swagger';
import { Match } from '../../match/entities/match.entity';
export enum TournamentRule {
SingleElimination = 'SingleElimination',
......@@ -32,6 +33,23 @@ export enum TournamentStatus {
Finished = 'Finished',
}
export class RuleSettings {
@ApiProperty({ description: '瑞士轮局数。' })
rounds: number;
@ApiProperty({ description: '瑞士轮胜利分。' })
winScore: number;
@ApiProperty({ description: '瑞士轮平局分。' })
drawScore: number;
@ApiProperty({ description: '瑞士轮轮空分。' })
byeScore: number;
@ApiProperty({ description: '淘汰赛中是否有季军塞' })
hasThirdPlaceMatch: boolean;
}
@Entity()
export class Tournament extends DescBase {
@NotChangeable()
......@@ -41,6 +59,11 @@ export class Tournament extends DescBase {
})
rule: TournamentRule;
@NotChangeable()
@Column('jsonb', { comment: '规则设定', default: {} })
@ApiProperty({ type: PartialType(RuleSettings), required: false })
ruleSettings: RuleSettings;
@EnumColumn(TournamentVisibility, {
default: TournamentVisibility.Public,
description: '可见性',
......@@ -80,6 +103,10 @@ export class Tournament extends DescBase {
@OneToMany(() => Participant, (participant) => participant.tournament)
participants: Participant[];
@NotColumn()
@OneToMany(() => Match, (match) => match.tournament)
matches: Match[];
async beforeCreate() {
this.createdAt = new Date();
}
......
......@@ -3,11 +3,12 @@ import { TournamentService } from './tournament.service';
import { TournamentController } from './tournament.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Tournament } from './entities/Tournament.entity';
import { Match } from '../match/entities/match.entity';
@Module({
controllers: [TournamentController],
providers: [TournamentService],
imports: [TypeOrmModule.forFeature([Tournament])],
imports: [TypeOrmModule.forFeature([Tournament, Match])],
exports: [TournamentService],
})
export class TournamentModule {}
......@@ -3,12 +3,23 @@ import { BlankReturnMessageDto, CrudService } from 'nicot';
import { Tournament, TournamentStatus } from './entities/Tournament.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { MycardUser } from 'nestjs-mycard';
import { Match } from '../match/entities/match.entity';
import { Repository } from 'typeorm';
@Injectable()
export class TournamentService extends CrudService(Tournament, {
relations: ['participants'],
relations: [
'participants',
'matches',
'matches.player1',
'matches.player2',
'matches.winner',
],
}) {
constructor(@InjectRepository(Tournament) repo) {
constructor(
@InjectRepository(Tournament) repo,
@InjectRepository(Match) private readonly matchRepo: Repository<Match>,
) {
super(repo);
}
......@@ -43,10 +54,14 @@ export class TournamentService extends CrudService(Tournament, {
return this.delete(id);
}
async checkPermissionOfTournament(id: number, user: MycardUser) {
async checkPermissionOfTournament(
id: number,
user: MycardUser,
extraGets: (keyof Tournament)[] = [],
) {
const tournament = await this.repo.findOne({
where: { id },
select: ['id', 'creator', 'collaborators'],
select: ['id', 'creator', 'collaborators', ...extraGets],
});
if (!tournament) {
throw new BlankReturnMessageDto(404, '未找到该比赛。').toException();
......@@ -64,4 +79,49 @@ export class TournamentService extends CrudService(Tournament, {
}
return tournamnt;
}
async resetTournament(id: number, user: MycardUser) {
const tournament = await this.checkPermissionOfTournament(id, user, [
'status',
]);
if (tournament.status === TournamentStatus.Ready) {
throw new BlankReturnMessageDto(
403,
'比赛还未开始,不能重置。',
).toException();
}
await this.repo.update(id, { status: TournamentStatus.Ready });
await this.matchRepo.softDelete({ tournamentId: id });
return new BlankReturnMessageDto(200, 'success');
}
async startTournament(id: number, user: MycardUser) {
const tournament = await this.checkPermissionOfTournament(id, user, [
'status',
]);
if (tournament.status !== TournamentStatus.Ready) {
throw new BlankReturnMessageDto(
403,
'比赛已经开始,不能重复开始。',
).toException();
}
await this.repo.update(id, { status: TournamentStatus.Running });
// TODO: generate matches
return new BlankReturnMessageDto(200, 'success');
}
async endTournament(id: number, user: MycardUser) {
const tournament = await this.checkPermissionOfTournament(id, user, [
'status',
]);
if (tournament.status !== TournamentStatus.Running) {
throw new BlankReturnMessageDto(
403,
'比赛还未开始,不能结束。',
).toException();
}
await this.repo.update(id, { status: TournamentStatus.Finished });
// TODO: calculate results
return new BlankReturnMessageDto(200, 'success');
}
}
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