import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { ShareParticipant } from '../entities/share-participant.entity'; type ShareParticipantCountRow = { shareConfigId: string; participantCount: string; }; export type ShareParticipantRankingRow = { shareConfigId: string; participantId: string; correctCount: number; totalTimeSpent: number; submittedAt: Date; }; @Injectable() export class ShareParticipantRepository { constructor( @InjectRepository(ShareParticipant) private readonly repository: Repository, ) {} /** 添加参与者(已存在则忽略) */ async addParticipant( shareConfigId: string, participantId: string, ): Promise { await this.repository .createQueryBuilder() .insert() .into(ShareParticipant) .values({ shareConfigId, participantId }) .orIgnore() .execute(); } async countByShareConfigId(shareConfigId: string): Promise { return this.repository.count({ where: { shareConfigId } }); } async countByShareConfigIds( shareConfigIds: string[], ): Promise> { if (shareConfigIds.length === 0) { return new Map(); } const rows = await this.repository .createQueryBuilder('participant') .select('participant.shareConfigId', 'shareConfigId') .addSelect('COUNT(*)', 'participantCount') .where('participant.shareConfigId IN (:...shareConfigIds)', { shareConfigIds, }) .groupBy('participant.shareConfigId') .getRawMany(); return new Map( rows.map((row) => [row.shareConfigId, Number(row.participantCount)]), ); } async upsertSubmissionSummary(data: { shareConfigId: string; participantId: string; correctCount: number; totalTimeSpent: number; submittedAt: Date; }): Promise { await this.repository .createQueryBuilder() .insert() .into(ShareParticipant) .values(data) .orUpdate( ['correctCount', 'totalTimeSpent', 'submittedAt'], ['shareConfigId', 'participantId'], ) .execute(); } async countSubmittedByShareConfigId(shareConfigId: string): Promise { return this.repository .createQueryBuilder('participant') .where('participant.shareConfigId = :shareConfigId', { shareConfigId }) .andWhere('participant.submittedAt IS NOT NULL') .getCount(); } async countSubmittedByShareConfigIds( shareConfigIds: string[], ): Promise> { if (shareConfigIds.length === 0) { return new Map(); } const rows = await this.repository .createQueryBuilder('participant') .select('participant.shareConfigId', 'shareConfigId') .addSelect('COUNT(*)', 'participantCount') .where('participant.shareConfigId IN (:...shareConfigIds)', { shareConfigIds, }) .andWhere('participant.submittedAt IS NOT NULL') .groupBy('participant.shareConfigId') .getRawMany(); return new Map( rows.map((row) => [row.shareConfigId, Number(row.participantCount)]), ); } async findSubmittedRankingsByShareConfigId( shareConfigId: string, ): Promise { const rows = await this.repository .createQueryBuilder('participant') .where('participant.shareConfigId = :shareConfigId', { shareConfigId }) .andWhere('participant.submittedAt IS NOT NULL') .orderBy('participant.correctCount', 'DESC') .addOrderBy('participant.totalTimeSpent', 'ASC') .addOrderBy('participant.submittedAt', 'ASC') .addOrderBy('participant.participantId', 'ASC') .getMany(); return rows.map((row) => ({ shareConfigId: row.shareConfigId, participantId: row.participantId, correctCount: row.correctCount, totalTimeSpent: row.totalTimeSpent, submittedAt: row.submittedAt!, })); } async findSubmittedRankingsByShareConfigIds( shareConfigIds: string[], ): Promise { if (shareConfigIds.length === 0) { return []; } const rows = await this.repository .createQueryBuilder('participant') .where('participant.shareConfigId IN (:...shareConfigIds)', { shareConfigIds, }) .andWhere('participant.submittedAt IS NOT NULL') .orderBy('participant.shareConfigId', 'ASC') .addOrderBy('participant.correctCount', 'DESC') .addOrderBy('participant.totalTimeSpent', 'ASC') .addOrderBy('participant.submittedAt', 'ASC') .addOrderBy('participant.participantId', 'ASC') .getMany(); return rows.map((row) => ({ shareConfigId: row.shareConfigId, participantId: row.participantId, correctCount: row.correctCount, totalTimeSpent: row.totalTimeSpent, submittedAt: row.submittedAt!, })); } async existsByShareConfigAndParticipant( shareConfigId: string, participantId: string, ): Promise { const count = await this.repository.count({ where: { shareConfigId, participantId }, }); return count > 0; } }