feat(challenges): 在挑战列表和详情中添加勋章信息展示
为睡眠挑战类型添加勋章信息支持,在挑战列表和详情接口中返回 sleepChallengeMonth 勋章配置数据。 - 在 ChallengesModule 中注册 BadgeConfig 模型 - 在 ChallengesService 中注入 BadgeConfig 仓库 - 查询列表时,若存在睡眠挑战则预加载勋章配置 - 查询详情时,若为睡眠挑战则附加勋章信息 - 在 DTO 中新增 BadgeInfoDto 接口定义勋章数据结构 - 仅对激活状态的 sleepChallengeMonth 勋章进行查询和展示
This commit is contained in:
@@ -7,10 +7,11 @@ import { ChallengeParticipant } from './models/challenge-participant.model';
|
|||||||
import { ChallengeProgressReport } from './models/challenge-progress-report.model';
|
import { ChallengeProgressReport } from './models/challenge-progress-report.model';
|
||||||
import { UsersModule } from '../users/users.module';
|
import { UsersModule } from '../users/users.module';
|
||||||
import { User } from '../users/models/user.model';
|
import { User } from '../users/models/user.model';
|
||||||
|
import { BadgeConfig } from '../users/models/badge-config.model';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
SequelizeModule.forFeature([Challenge, ChallengeParticipant, ChallengeProgressReport, User]),
|
SequelizeModule.forFeature([Challenge, ChallengeParticipant, ChallengeProgressReport, User, BadgeConfig]),
|
||||||
UsersModule,
|
UsersModule,
|
||||||
],
|
],
|
||||||
controllers: [ChallengesController],
|
controllers: [ChallengesController],
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
|
|||||||
import { Logger as WinstonLogger } from 'winston';
|
import { Logger as WinstonLogger } from 'winston';
|
||||||
import { BadgeService } from '../users/services/badge.service';
|
import { BadgeService } from '../users/services/badge.service';
|
||||||
import { BadgeSource } from '../users/models/user-badge.model';
|
import { BadgeSource } from '../users/models/user-badge.model';
|
||||||
|
import { BadgeConfig } from '../users/models/badge-config.model';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ChallengesService {
|
export class ChallengesService {
|
||||||
@@ -28,6 +29,8 @@ export class ChallengesService {
|
|||||||
private readonly participantModel: typeof ChallengeParticipant,
|
private readonly participantModel: typeof ChallengeParticipant,
|
||||||
@InjectModel(ChallengeProgressReport)
|
@InjectModel(ChallengeProgressReport)
|
||||||
private readonly progressReportModel: typeof ChallengeProgressReport,
|
private readonly progressReportModel: typeof ChallengeProgressReport,
|
||||||
|
@InjectModel(BadgeConfig)
|
||||||
|
private readonly badgeConfigModel: typeof BadgeConfig,
|
||||||
private readonly badgeService: BadgeService,
|
private readonly badgeService: BadgeService,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
@@ -103,6 +106,25 @@ export class ChallengesService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🎖️ 查询 sleepChallengeMonth 勋章信息(仅在有睡眠挑战时查询)
|
||||||
|
const hasSleepChallenge = challengesWithStatus.some(({ challenge }) => challenge.type === ChallengeType.SLEEP);
|
||||||
|
let sleepBadge: ChallengeListItemDto['badge'] = undefined;
|
||||||
|
if (hasSleepChallenge) {
|
||||||
|
const badgeConfig = await this.badgeConfigModel.findOne({
|
||||||
|
where: { code: 'sleepChallengeMonth', isActive: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (badgeConfig) {
|
||||||
|
sleepBadge = {
|
||||||
|
code: badgeConfig.code,
|
||||||
|
name: badgeConfig.name,
|
||||||
|
description: badgeConfig.description,
|
||||||
|
imageUrl: badgeConfig.imageUrl,
|
||||||
|
category: badgeConfig.category,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return challengesWithStatus.map(({ challenge, status }) => {
|
return challengesWithStatus.map(({ challenge, status }) => {
|
||||||
const completionTarget = challenge.minimumCheckInDays
|
const completionTarget = challenge.minimumCheckInDays
|
||||||
const participation = participationMap.get(challenge.id);
|
const participation = participationMap.get(challenge.id);
|
||||||
@@ -130,6 +152,7 @@ export class ChallengesService {
|
|||||||
progress,
|
progress,
|
||||||
isJoined: Boolean(participation),
|
isJoined: Boolean(participation),
|
||||||
type: challenge.type,
|
type: challenge.type,
|
||||||
|
badge: challenge.type === ChallengeType.SLEEP ? sleepBadge : undefined,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -198,6 +221,24 @@ export class ChallengesService {
|
|||||||
|
|
||||||
const userRank = participation ? await this.calculateUserRank(challengeId, participation) : undefined;
|
const userRank = participation ? await this.calculateUserRank(challengeId, participation) : undefined;
|
||||||
|
|
||||||
|
// 🎖️ 如果是睡眠挑战,获取 sleepChallengeMonth 勋章信息
|
||||||
|
let badge: ChallengeDetailDto['badge'] = undefined;
|
||||||
|
if (challenge.type === ChallengeType.SLEEP) {
|
||||||
|
const badgeConfig = await this.badgeConfigModel.findOne({
|
||||||
|
where: { code: 'sleepChallengeMonth', isActive: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (badgeConfig) {
|
||||||
|
badge = {
|
||||||
|
code: badgeConfig.code,
|
||||||
|
name: badgeConfig.name,
|
||||||
|
description: badgeConfig.description,
|
||||||
|
imageUrl: badgeConfig.imageUrl,
|
||||||
|
category: badgeConfig.category,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: challenge.id,
|
id: challenge.id,
|
||||||
title: challenge.title,
|
title: challenge.title,
|
||||||
@@ -217,6 +258,7 @@ export class ChallengesService {
|
|||||||
userRank,
|
userRank,
|
||||||
unit: challenge.progressUnit,
|
unit: challenge.progressUnit,
|
||||||
type: challenge.type,
|
type: challenge.type,
|
||||||
|
badge,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
import { ChallengeProgressDto, RankingItemDto } from './challenge-progress.dto';
|
import { ChallengeProgressDto, RankingItemDto } from './challenge-progress.dto';
|
||||||
import { ChallengeType } from '../models/challenge.model';
|
import { ChallengeType } from '../models/challenge.model';
|
||||||
|
|
||||||
|
export interface BadgeInfoDto {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
imageUrl: string;
|
||||||
|
category: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ChallengeDetailDto {
|
export interface ChallengeDetailDto {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -20,4 +28,5 @@ export interface ChallengeDetailDto {
|
|||||||
userRank?: number;
|
userRank?: number;
|
||||||
type: ChallengeType;
|
type: ChallengeType;
|
||||||
unit: string;
|
unit: string;
|
||||||
|
badge?: BadgeInfoDto;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
import { ChallengeStatus, ChallengeType } from '../models/challenge.model';
|
import { ChallengeStatus, ChallengeType } from '../models/challenge.model';
|
||||||
import { ChallengeProgressDto } from './challenge-progress.dto';
|
import { ChallengeProgressDto } from './challenge-progress.dto';
|
||||||
|
|
||||||
|
export interface BadgeInfoDto {
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
imageUrl: string;
|
||||||
|
category: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ChallengeListItemDto {
|
export interface ChallengeListItemDto {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -21,6 +29,7 @@ export interface ChallengeListItemDto {
|
|||||||
isJoined: boolean;
|
isJoined: boolean;
|
||||||
type: ChallengeType;
|
type: ChallengeType;
|
||||||
unit: string;
|
unit: string;
|
||||||
|
badge?: BadgeInfoDto;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChallengeListResponseDto {
|
export interface ChallengeListResponseDto {
|
||||||
|
|||||||
Reference in New Issue
Block a user