feat(challenges): 新增挑战功能模块及完整接口实现

- 新增挑战列表、详情、加入/退出、进度上报等 REST 接口
- 定义 Challenge / ChallengeParticipant 数据模型与状态枚举
- 提供排行榜查询与用户排名计算
- 包含接口文档与数据库初始化脚本
This commit is contained in:
richarjiang
2025-09-28 12:02:39 +08:00
parent 8e51994e71
commit 1b7132a325
12 changed files with 1003 additions and 0 deletions

View File

@@ -0,0 +1,123 @@
import { Table, Column, DataType, Model, HasMany } from 'sequelize-typescript';
import { ChallengeParticipant } from './challenge-participant.model';
export enum ChallengeStatus {
UPCOMING = 'upcoming',
ONGOING = 'ongoing',
EXPIRED = 'expired',
}
@Table({
tableName: 't_challenges',
underscored: true,
})
export class Challenge extends Model {
@Column({
type: DataType.CHAR(36),
defaultValue: DataType.UUIDV4,
primaryKey: true,
})
declare id: string;
@Column({
type: DataType.STRING(255),
allowNull: false,
comment: '挑战标题',
})
declare title: string;
@Column({
type: DataType.STRING(512),
allowNull: true,
comment: '挑战封面图',
})
declare image: string;
@Column({
type: DataType.DATE,
allowNull: false,
comment: '挑战开始时间',
})
declare startAt: Date;
@Column({
type: DataType.DATE,
allowNull: false,
comment: '挑战结束时间',
})
declare endAt: Date;
@Column({
type: DataType.STRING(128),
allowNull: true,
comment: '周期标签例如「21天挑战」',
})
declare periodLabel: string | null;
@Column({
type: DataType.STRING(128),
allowNull: false,
comment: '持续时间标签例如「持续21天」',
})
declare durationLabel: string;
@Column({
type: DataType.STRING(255),
allowNull: false,
comment: '挑战要求标签,例如「每日练习 1 次」',
})
declare requirementLabel: string;
@Column({
type: DataType.TEXT,
allowNull: true,
comment: '挑战概要说明',
})
declare summary: string | null;
@Column({
type: DataType.INTEGER,
allowNull: false,
comment: '挑战目标值(例如需要完成的天数)',
})
declare targetValue: number;
@Column({
type: DataType.STRING(64),
allowNull: false,
defaultValue: '天',
comment: '进度单位,用于展示排行榜指标',
})
declare progressUnit: string;
@Column({
type: DataType.STRING(255),
allowNull: true,
comment: '排行榜描述,例如「连续打卡榜」',
})
declare rankingDescription: string | null;
@Column({
type: DataType.STRING(255),
allowNull: false,
comment: '高亮标题',
})
declare highlightTitle: string;
@Column({
type: DataType.STRING(255),
allowNull: false,
comment: '高亮副标题',
})
declare highlightSubtitle: string;
@Column({
type: DataType.STRING(128),
allowNull: false,
comment: 'CTA 按钮文字',
})
declare ctaLabel: string;
@HasMany(() => ChallengeParticipant)
declare participants?: ChallengeParticipant[];
}