feat(challenges): 添加用户自定义挑战功能及分享机制
实现完整的自定义挑战系统,支持用户创建、分享和管理个人挑战: - 数据库扩展:添加 source、creator_id、share_code、is_public、max_participants、challenge_state 字段 - 分享机制:自动生成6位唯一分享码,支持公开和私密模式 - API接口:创建挑战、通过分享码加入、获取创建列表、更新归档挑战、重新生成分享码 - 权限控制:创建者专属编辑权限,频率限制防滥用(每日5个) - 业务逻辑:人数限制检查、挑战状态流转、参与者统计 - 文档完善:使用文档和部署指南,包含API示例和回滚方案 兼容现有系统挑战,使用相同的打卡、排行榜和勋章系统
This commit is contained in:
109
src/challenges/dto/create-custom-challenge.dto.ts
Normal file
109
src/challenges/dto/create-custom-challenge.dto.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsString, IsNotEmpty, IsNumber, Min, Max, IsEnum, IsOptional, IsBoolean, MaxLength, MinLength } from 'class-validator';
|
||||
import { ChallengeType } from '../models/challenge.model';
|
||||
|
||||
export class CreateCustomChallengeDto {
|
||||
@ApiProperty({ description: '挑战标题', example: '21天喝水挑战' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(100)
|
||||
title: string;
|
||||
|
||||
@ApiProperty({ description: '挑战类型', enum: ChallengeType, example: ChallengeType.WATER })
|
||||
@IsEnum(ChallengeType)
|
||||
@IsNotEmpty()
|
||||
type: ChallengeType;
|
||||
|
||||
@ApiProperty({ description: '挑战封面图 URL', required: false })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@MaxLength(512)
|
||||
image?: string;
|
||||
|
||||
@ApiProperty({ description: '开始时间戳(毫秒)', example: 1704067200000 })
|
||||
@IsNumber()
|
||||
@Min(Date.now())
|
||||
startAt: number;
|
||||
|
||||
@ApiProperty({ description: '结束时间戳(毫秒)', example: 1705881600000 })
|
||||
@IsNumber()
|
||||
@Min(Date.now() + 86400000) // 至少未来 1 天
|
||||
endAt: number;
|
||||
|
||||
@ApiProperty({ description: '每日目标值(如喝水8杯)', example: 8, minimum: 1, maximum: 1000 })
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
@Max(1000)
|
||||
targetValue: number;
|
||||
|
||||
@ApiProperty({ description: '最少打卡天数', example: 21, minimum: 1, maximum: 365 })
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
@Max(365)
|
||||
minimumCheckInDays: number;
|
||||
|
||||
@ApiProperty({ description: '持续时间标签', example: '持续21天' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(128)
|
||||
durationLabel: string;
|
||||
|
||||
@ApiProperty({ description: '挑战要求标签', example: '每日喝水8杯' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@MaxLength(255)
|
||||
requirementLabel: string;
|
||||
|
||||
@ApiProperty({ description: '挑战概要说明', required: false })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
summary?: string;
|
||||
|
||||
@ApiProperty({ description: '进度单位', example: '天', required: false })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@MaxLength(64)
|
||||
progressUnit?: string;
|
||||
|
||||
@ApiProperty({ description: '周期标签', example: '21天挑战', required: false })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@MaxLength(128)
|
||||
periodLabel?: string;
|
||||
|
||||
@ApiProperty({ description: '排行榜描述', example: '连续打卡榜', required: false })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@MaxLength(255)
|
||||
rankingDescription?: string;
|
||||
|
||||
@ApiProperty({ description: '高亮标题', example: '坚持21天', required: false })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@MaxLength(255)
|
||||
highlightTitle?: string;
|
||||
|
||||
@ApiProperty({ description: '高亮副标题', example: '养成好习惯', required: false })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@MaxLength(255)
|
||||
highlightSubtitle?: string;
|
||||
|
||||
@ApiProperty({ description: 'CTA 按钮文字', example: '立即加入', required: false })
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@MaxLength(128)
|
||||
ctaLabel?: string;
|
||||
|
||||
@ApiProperty({ description: '是否公开(可通过分享码加入)', default: true })
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
isPublic?: boolean;
|
||||
|
||||
@ApiProperty({ description: '最大参与人数限制(null表示无限制)', required: false, minimum: 2, maximum: 10000 })
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
@Min(2)
|
||||
@Max(10000)
|
||||
maxParticipants?: number;
|
||||
}
|
||||
Reference in New Issue
Block a user