feat(challenges): 支持公开访问挑战列表与详情接口

- 在 GET /challenges、GET /challenges/:id、GET /challenges/:id/rankings 添加 @Public() 装饰器,允许未登录用户访问
- 将 userId 改为可选参数,未登录时仍可返回基础数据
- 列表接口过滤掉 UPCOMING 状态挑战,仅展示进行中/已结束
- 返回 DTO 新增 unit 字段,用于前端展示进度单位
- 鉴权守卫优化:公开接口若携带 token 仍尝试解析并注入 user,方便后续业务逻辑
This commit is contained in:
richarjiang
2025-09-30 16:43:46 +08:00
parent 87c3cbfac9
commit 999fc7f793
6 changed files with 64 additions and 33 deletions

View File

@@ -28,7 +28,7 @@ export class ChallengesService {
private readonly progressReportModel: typeof ChallengeProgressReport,
) { }
async getChallengesForUser(userId: string): Promise<ChallengeListItemDto[]> {
async getChallengesForUser(userId?: string): Promise<ChallengeListItemDto[]> {
const challenges = await this.challengeModel.findAll({
order: [['startAt', 'ASC']],
});
@@ -50,6 +50,7 @@ export class ChallengesService {
challenge,
status: this.computeStatus(challenge.startAt, challenge.endAt),
}))
.filter(({ status }) => status !== ChallengeStatus.UPCOMING)
.sort((a, b) => {
const priorityDiff = statusPriority[a.status] - statusPriority[b.status];
if (priorityDiff !== 0) {
@@ -81,19 +82,22 @@ export class ChallengesService {
}
}
const userParticipations = await this.participantModel.findAll({
where: {
challengeId: challengeIds,
userId,
status: {
[Op.ne]: ChallengeParticipantStatus.LEFT,
},
},
});
const participationMap = new Map<string, ChallengeParticipant>();
for (const participation of userParticipations) {
participationMap.set(participation.challengeId, participation);
if (userId) {
const userParticipations = await this.participantModel.findAll({
where: {
challengeId: challengeIds,
userId,
status: {
[Op.ne]: ChallengeParticipantStatus.LEFT,
},
},
});
for (const participation of userParticipations) {
participationMap.set(participation.challengeId, participation);
}
}
return challengesWithStatus.map(({ challenge, status }) => {
@@ -111,6 +115,7 @@ export class ChallengesService {
durationLabel: challenge.durationLabel,
requirementLabel: challenge.requirementLabel,
status,
unit: challenge.progressUnit,
startAt: challenge.startAt,
endAt: challenge.endAt,
participantsCount: participantsCountMap.get(challenge.id) ?? 0,
@@ -126,7 +131,7 @@ export class ChallengesService {
});
}
async getChallengeDetail(userId: string, challengeId: string): Promise<ChallengeDetailDto> {
async getChallengeDetail(challengeId: string, userId?: string,): Promise<ChallengeDetailDto> {
const challenge = await this.challengeModel.findByPk(challengeId);
if (!challenge) {
@@ -147,15 +152,17 @@ export class ChallengesService {
status: ChallengeParticipantStatus.ACTIVE,
},
}),
this.participantModel.findOne({
where: {
challengeId,
userId,
status: {
[Op.ne]: ChallengeParticipantStatus.LEFT,
userId
? this.participantModel.findOne({
where: {
challengeId,
userId,
status: {
[Op.ne]: ChallengeParticipantStatus.LEFT,
},
},
},
}),
})
: null,
]);
this.winstonLogger.info('end get detail', {
@@ -205,6 +212,7 @@ export class ChallengesService {
progress,
rankings,
userRank,
unit: challenge.progressUnit,
type: challenge.type,
};
}