Compare commits

...

2 Commits

4 changed files with 62 additions and 42 deletions

View File

@@ -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);

View File

@@ -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();
}

View File

@@ -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)

View File

@@ -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或自定义动作列表');
}
}
}
/**
@@ -127,53 +125,67 @@ export class WorkoutsService {
const transaction = await this.workoutSessionModel.sequelize?.transaction();
if (!transaction) throw new Error('Failed to start transaction');
this.winstonLogger.info(`创建自定义训练会话`, {
context: 'WorkoutsService',
userId,
dto,
});
try {
// 1. 创建训练会话
const workoutSession = await this.workoutSessionModel.create({
userId,
trainingPlanId: null, // 自定义训练不关联训练计划
name: dto.name,
scheduledDate: dto.scheduledDate ? new Date(dto.scheduledDate) : new Date(),
status: 'planned',
}, { 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();
this.winstonLogger.info(`创建自定义训练会话 ${workoutSession.id}`, {
context: 'WorkoutsService',
userId,
workoutSessionId: workoutSession.id,
exerciseCount: dto.customExercises!.length,
});
exerciseCount: dto.customExercises?.length,
});
return this.getWorkoutSessionDetail(userId, workoutSession.id);
await transaction.commit();
return workoutSession.toJSON();
} catch (error) {
this.winstonLogger.error(`创建自定义训练会话失败`, {
context: 'WorkoutsService',
userId,
dto,
error,
});
await transaction.rollback();
throw error;
}
@@ -262,6 +274,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,17 +318,16 @@ export class WorkoutsService {
include: [
{
model: TrainingPlan,
required: false,
attributes: ['id', 'name', 'goal']
}
],
order: [['scheduledDate', 'DESC']],
order: [['createdAt', 'DESC']],
limit,
offset,
});
return {
sessions: sessions.map(s => s.toJSON()),
sessions,
pagination: {
page,
limit,