增强文章控制器的安全性,添加JWT身份验证守卫;优化训练计划服务,简化日志记录逻辑,确保使用计划创建训练会话时的准确性;更新训练会话模型,允许训练计划ID为可空字段。
This commit is contained in:
@@ -6,7 +6,7 @@ import { CreateArticleDto, QueryArticlesDto, CreateArticleResponseDto, QueryArti
|
||||
|
||||
@ApiTags('articles')
|
||||
@Controller('articles')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
|
||||
export class ArticlesController {
|
||||
constructor(private readonly articlesService: ArticlesService) { }
|
||||
|
||||
@@ -15,12 +15,14 @@ export class ArticlesController {
|
||||
@ApiOperation({ summary: '创建文章' })
|
||||
@ApiBody({ type: CreateArticleDto })
|
||||
@ApiResponse({ status: 200 })
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async create(@Body() dto: CreateArticleDto): Promise<CreateArticleResponseDto> {
|
||||
return this.articlesService.create(dto);
|
||||
}
|
||||
|
||||
@Get('list')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({ summary: '查询文章列表(分页)' })
|
||||
async list(@Query() query: QueryArticlesDto): Promise<QueryArticlesResponseDto> {
|
||||
return this.articlesService.query(query);
|
||||
@@ -36,6 +38,7 @@ export class ArticlesController {
|
||||
// 增加阅读数
|
||||
@Post(':id/read-count')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@ApiOperation({ summary: '增加文章阅读数' })
|
||||
async increaseReadCount(@Param('id') id: string): Promise<CreateArticleResponseDto> {
|
||||
return this.articlesService.increaseReadCount(id);
|
||||
|
||||
@@ -17,14 +17,12 @@ export class TrainingPlansService {
|
||||
) { }
|
||||
|
||||
async create(userId: string, dto: CreateTrainingPlanDto) {
|
||||
const id = `plan_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
||||
const createdAt = new Date();
|
||||
|
||||
// 检查用户是否有其他激活的训练计划
|
||||
const activePlans = await this.trainingPlanModel.findAll({ where: { userId, isActive: true, deleted: false } });
|
||||
|
||||
const plan = await this.trainingPlanModel.create({
|
||||
id,
|
||||
userId,
|
||||
name: dto.name ?? '',
|
||||
createdAt,
|
||||
@@ -37,10 +35,10 @@ export class TrainingPlansService {
|
||||
preferredTimeOfDay: dto.preferredTimeOfDay ?? '',
|
||||
isActive: activePlans.length === 0,
|
||||
});
|
||||
this.winstonLogger.info(`create plan ${id} for user ${userId} success`, {
|
||||
this.winstonLogger.info(`create plan ${plan.id} for user ${userId} success`, {
|
||||
context: 'TrainingPlansService',
|
||||
userId,
|
||||
id,
|
||||
planId: plan.id,
|
||||
});
|
||||
await this.activityLogsService.record({
|
||||
userId,
|
||||
@@ -49,10 +47,10 @@ export class TrainingPlansService {
|
||||
entityId: plan.id,
|
||||
changes: plan.toJSON(),
|
||||
});
|
||||
this.winstonLogger.info(`create plan ${id} for user ${userId} success`, {
|
||||
this.winstonLogger.info(`create plan ${plan.id} for user ${userId} success`, {
|
||||
context: 'TrainingPlansService',
|
||||
userId,
|
||||
id,
|
||||
planId: plan.id,
|
||||
});
|
||||
return plan.toJSON();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export class WorkoutSession extends Model {
|
||||
declare userId: string;
|
||||
|
||||
@ForeignKey(() => TrainingPlan)
|
||||
@Column({ type: DataType.UUID, allowNull: false, comment: '关联的训练计划模板' })
|
||||
@Column({ type: DataType.UUID, allowNull: true, comment: '关联的训练计划模板' })
|
||||
declare trainingPlanId: string;
|
||||
|
||||
@BelongsTo(() => TrainingPlan)
|
||||
|
||||
@@ -50,12 +50,10 @@ export class WorkoutsService {
|
||||
dto.scheduledDate ? new Date(dto.scheduledDate) : new Date(),
|
||||
dto.name
|
||||
);
|
||||
} else if (dto.customExercises && dto.customExercises.length > 0) {
|
||||
} else {
|
||||
// 基于自定义动作创建
|
||||
return this.createCustomWorkoutSession(userId, dto);
|
||||
} else {
|
||||
throw new BadRequestException('必须提供训练计划ID或自定义动作列表');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,29 +136,31 @@ export class WorkoutsService {
|
||||
}, { transaction });
|
||||
|
||||
// 2. 创建自定义动作
|
||||
for (const customExercise of dto.customExercises!) {
|
||||
// 如果有exerciseKey,验证动作是否存在
|
||||
if (customExercise.exerciseKey) {
|
||||
const exercise = await this.exerciseModel.findByPk(customExercise.exerciseKey);
|
||||
if (!exercise) {
|
||||
throw new NotFoundException(`动作 "${customExercise.exerciseKey}" 不存在`);
|
||||
if (dto.customExercises && dto.customExercises.length > 0) {
|
||||
for (const customExercise of dto.customExercises!) {
|
||||
// 如果有exerciseKey,验证动作是否存在
|
||||
if (customExercise.exerciseKey) {
|
||||
const exercise = await this.exerciseModel.findByPk(customExercise.exerciseKey);
|
||||
if (!exercise) {
|
||||
throw new NotFoundException(`动作 "${customExercise.exerciseKey}" 不存在`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.workoutExerciseModel.create({
|
||||
workoutSessionId: workoutSession.id,
|
||||
userId,
|
||||
exerciseKey: customExercise.exerciseKey,
|
||||
name: customExercise.name,
|
||||
plannedSets: customExercise.plannedSets,
|
||||
plannedReps: customExercise.plannedReps,
|
||||
plannedDurationSec: customExercise.plannedDurationSec,
|
||||
restSec: customExercise.restSec,
|
||||
note: customExercise.note || '',
|
||||
itemType: customExercise.itemType || 'exercise',
|
||||
status: 'pending',
|
||||
sortOrder: customExercise.sortOrder,
|
||||
}, { transaction });
|
||||
await this.workoutExerciseModel.create({
|
||||
workoutSessionId: workoutSession.id,
|
||||
userId,
|
||||
exerciseKey: customExercise.exerciseKey,
|
||||
name: customExercise.name,
|
||||
plannedSets: customExercise.plannedSets,
|
||||
plannedReps: customExercise.plannedReps,
|
||||
plannedDurationSec: customExercise.plannedDurationSec,
|
||||
restSec: customExercise.restSec,
|
||||
note: customExercise.note || '',
|
||||
itemType: customExercise.itemType || 'exercise',
|
||||
status: 'pending',
|
||||
sortOrder: customExercise.sortOrder,
|
||||
}, { transaction });
|
||||
}
|
||||
}
|
||||
|
||||
await transaction.commit();
|
||||
@@ -169,10 +169,10 @@ export class WorkoutsService {
|
||||
context: 'WorkoutsService',
|
||||
userId,
|
||||
workoutSessionId: workoutSession.id,
|
||||
exerciseCount: dto.customExercises!.length,
|
||||
});
|
||||
exerciseCount: dto.customExercises!.length,
|
||||
});
|
||||
|
||||
return this.getWorkoutSessionDetail(userId, workoutSession.id);
|
||||
return workoutSession.toJSON();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
@@ -262,6 +262,14 @@ export class WorkoutsService {
|
||||
throw new BadRequestException('只能开始计划中的训练会话');
|
||||
}
|
||||
|
||||
// 是否有训练动作,没有的话提示添加
|
||||
const exercises = await this.workoutExerciseModel.findAll({
|
||||
where: { workoutSessionId: sessionId, deleted: false }
|
||||
});
|
||||
if (exercises.length === 0) {
|
||||
throw new BadRequestException('请先添加训练动作');
|
||||
}
|
||||
|
||||
const startTime = dto.startedAt ? new Date(dto.startedAt) : new Date();
|
||||
session.startedAt = startTime;
|
||||
session.status = 'in_progress';
|
||||
@@ -298,7 +306,6 @@ export class WorkoutsService {
|
||||
include: [
|
||||
{
|
||||
model: TrainingPlan,
|
||||
required: false,
|
||||
attributes: ['id', 'name', 'goal']
|
||||
}
|
||||
],
|
||||
@@ -308,7 +315,7 @@ export class WorkoutsService {
|
||||
});
|
||||
|
||||
return {
|
||||
sessions: sessions.map(s => s.toJSON()),
|
||||
sessions,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
|
||||
Reference in New Issue
Block a user