feat(ai-coach,checkins): 实现软删除功能

在AI教练和打卡模块中添加deleted字段,将物理删除改为软删除:
- 在AiConversation和AiMessage模型中添加deleted布尔字段
- 在Checkin模型中添加deleted字段
- 更新所有查询条件添加deleted: false过滤
- 修改删除操作为标记deleted: true而非物理删除
- 在打卡服务中添加重复记录检查逻辑
This commit is contained in:
richarjiang
2025-08-14 19:16:57 +08:00
parent 9ad65b19fd
commit 812ac5c21e
5 changed files with 58 additions and 10 deletions

View File

@@ -54,7 +54,7 @@ export class AiCoachService {
buildChatHistory = async (userId: string, conversationId: string) => { buildChatHistory = async (userId: string, conversationId: string) => {
const history = await AiMessage.findAll({ const history = await AiMessage.findAll({
where: { userId, conversationId }, where: { userId, conversationId, deleted: false },
order: [['created_at', 'ASC']], order: [['created_at', 'ASC']],
}); });
@@ -128,7 +128,7 @@ export class AiCoachService {
const pageSize = Math.min(50, Math.max(1, params.pageSize || 20)); const pageSize = Math.min(50, Math.max(1, params.pageSize || 20));
const offset = (page - 1) * pageSize; const offset = (page - 1) * pageSize;
const { rows, count } = await AiConversation.findAndCountAll({ const { rows, count } = await AiConversation.findAndCountAll({
where: { userId }, where: { userId, deleted: false },
order: [['last_message_at', 'DESC']], order: [['last_message_at', 'DESC']],
offset, offset,
limit: pageSize, limit: pageSize,
@@ -147,10 +147,10 @@ export class AiCoachService {
} }
async getConversationDetail(userId: string, conversationId: string) { async getConversationDetail(userId: string, conversationId: string) {
const conv = await AiConversation.findOne({ where: { id: conversationId, userId } }); const conv = await AiConversation.findOne({ where: { id: conversationId, userId, deleted: false } });
if (!conv) return null; if (!conv) return null;
const messages = await AiMessage.findAll({ const messages = await AiMessage.findAll({
where: { userId, conversationId }, where: { userId, conversationId, deleted: false },
order: [['created_at', 'ASC']], order: [['created_at', 'ASC']],
}); });
return { return {
@@ -163,10 +163,10 @@ export class AiCoachService {
} }
async deleteConversation(userId: string, conversationId: string): Promise<boolean> { async deleteConversation(userId: string, conversationId: string): Promise<boolean> {
const conv = await AiConversation.findOne({ where: { id: conversationId, userId } }); const conv = await AiConversation.findOne({ where: { id: conversationId, userId, deleted: false } });
if (!conv) return false; if (!conv) return false;
await AiMessage.destroy({ where: { userId, conversationId } }); await AiMessage.update({ deleted: true }, { where: { userId, conversationId } });
await AiConversation.destroy({ where: { id: conversationId, userId } }); await AiConversation.update({ deleted: true }, { where: { id: conversationId, userId } });
return true; return true;
} }

View File

@@ -46,6 +46,13 @@ export class AiConversation extends Model {
}) })
declare updatedAt: Date; declare updatedAt: Date;
@Column({
type: DataType.BOOLEAN,
allowNull: false,
defaultValue: false,
})
declare deleted: boolean;
@HasMany(() => AiMessage) @HasMany(() => AiMessage)
declare messages?: AiMessage[]; declare messages?: AiMessage[];
} }

View File

@@ -65,6 +65,13 @@ export class AiMessage extends Model {
}) })
declare updatedAt: Date; declare updatedAt: Date;
@Column({
type: DataType.BOOLEAN,
allowNull: false,
defaultValue: false,
})
declare deleted: boolean;
@BelongsTo(() => AiConversation) @BelongsTo(() => AiConversation)
declare conversation?: AiConversation; declare conversation?: AiConversation;
} }

View File

@@ -19,6 +19,21 @@ export class CheckinsService {
) { } ) { }
async create(dto: CreateCheckinDto): Promise<CheckinResponseDto> { async create(dto: CreateCheckinDto): Promise<CheckinResponseDto> {
// 检查是否已存在未删除的记录
const existingRecord = await this.checkinModel.findOne({
where: {
userId: dto.userId,
workoutId: dto.workoutId || null,
planId: dto.planId || null,
checkinDate: dto.checkinDate || null,
deleted: false,
},
});
if (existingRecord) {
return { code: ResponseCode.SUCCESS, message: 'success', data: existingRecord.toJSON() };
}
const record = await this.checkinModel.create({ const record = await this.checkinModel.create({
userId: dto.userId, userId: dto.userId,
workoutId: dto.workoutId || null, workoutId: dto.workoutId || null,
@@ -43,7 +58,12 @@ export class CheckinsService {
} }
async update(dto: UpdateCheckinDto, userId: string): Promise<CheckinResponseDto> { async update(dto: UpdateCheckinDto, userId: string): Promise<CheckinResponseDto> {
const record = await this.checkinModel.findByPk(dto.id); const record = await this.checkinModel.findOne({
where: {
id: dto.id,
deleted: false,
},
});
if (!record) { if (!record) {
throw new NotFoundException('打卡记录不存在'); throw new NotFoundException('打卡记录不存在');
} }
@@ -75,7 +95,12 @@ export class CheckinsService {
} }
async complete(dto: CompleteCheckinDto, userId: string): Promise<CheckinResponseDto> { async complete(dto: CompleteCheckinDto, userId: string): Promise<CheckinResponseDto> {
const record = await this.checkinModel.findByPk(dto.id); const record = await this.checkinModel.findOne({
where: {
id: dto.id,
deleted: false,
},
});
if (!record) { if (!record) {
throw new NotFoundException('打卡记录不存在'); throw new NotFoundException('打卡记录不存在');
} }
@@ -114,7 +139,8 @@ export class CheckinsService {
if (record.userId !== userId) { if (record.userId !== userId) {
throw new ForbiddenException('无权操作该打卡记录'); throw new ForbiddenException('无权操作该打卡记录');
} }
await record.destroy(); record.deleted = true;
await record.save();
await this.activityLogsService.record({ await this.activityLogsService.record({
userId: record.userId, userId: record.userId,
entityType: ActivityEntityType.CHECKIN, entityType: ActivityEntityType.CHECKIN,
@@ -139,6 +165,7 @@ export class CheckinsService {
const rows = await this.checkinModel.findAll({ const rows = await this.checkinModel.findAll({
where: { where: {
userId, userId,
deleted: false,
[Op.or]: [ [Op.or]: [
{ checkinDate: target.format('YYYY-MM-DD') as any }, { checkinDate: target.format('YYYY-MM-DD') as any },
{ {
@@ -170,6 +197,7 @@ export class CheckinsService {
const rows = await this.checkinModel.findAll({ const rows = await this.checkinModel.findAll({
where: { where: {
userId, userId,
deleted: false,
[Op.or]: [ [Op.or]: [
{ {
checkinDate: { checkinDate: {

View File

@@ -111,6 +111,12 @@ export class Checkin extends Model {
defaultValue: DataType.NOW, defaultValue: DataType.NOW,
}) })
declare updatedAt: Date; declare updatedAt: Date;
@Column({
type: DataType.BOOLEAN,
defaultValue: false,
})
declare deleted: boolean;
} }