import { Injectable, NotFoundException, Logger, ForbiddenException, BadRequestException } from '@nestjs/common'; import { InjectModel } from '@nestjs/sequelize'; import { MoodCheckin, MoodType } from './models/mood-checkin.model'; import { CreateMoodCheckinDto, UpdateMoodCheckinDto, RemoveMoodCheckinDto, MoodCheckinResponseDto, GetMoodHistoryQueryDto, MoodStatistics } from './dto/mood-checkin.dto'; import { ResponseCode } from '../base.dto'; import * as dayjs from 'dayjs'; import { Op } from 'sequelize'; import { ActivityLogsService } from '../activity-logs/activity-logs.service'; import { ActivityActionType, ActivityEntityType } from '../activity-logs/models/activity-log.model'; @Injectable() export class MoodCheckinsService { private readonly logger = new Logger(MoodCheckinsService.name); constructor( @InjectModel(MoodCheckin) private readonly moodCheckinModel: typeof MoodCheckin, private readonly activityLogsService: ActivityLogsService, ) { } async create(dto: CreateMoodCheckinDto, userId: string): Promise { const checkinDate = dto.checkinDate || dayjs().format('YYYY-MM-DD'); // 检查当天是否已有相同心情类型的记录 const existingRecord = await this.moodCheckinModel.findOne({ where: { userId, moodType: dto.moodType, checkinDate, deleted: false, }, }); if (existingRecord) { // 如果存在,则更新现有记录 return this.update({ ...dto, id: existingRecord.id, checkinDate, }, userId); } const record = await this.moodCheckinModel.create({ userId, moodType: dto.moodType, intensity: dto.intensity, description: dto.description || null, checkinDate, metadata: dto.metadata || null, }); await this.activityLogsService.record({ userId, entityType: ActivityEntityType.CHECKIN, action: ActivityActionType.CREATE, entityId: record.id, changes: record.toJSON(), }); return { code: ResponseCode.SUCCESS, message: '心情打卡成功', data: record.toJSON() }; } async update(dto: UpdateMoodCheckinDto, userId: string): Promise { const record = await this.moodCheckinModel.findOne({ where: { id: dto.id, deleted: false, }, }); if (!record) { throw new NotFoundException('心情打卡记录不存在'); } if (record.userId !== userId) { throw new ForbiddenException('无权操作该心情打卡记录'); } const changes: Record = {}; if (dto.moodType !== undefined) { record.moodType = dto.moodType; changes.moodType = dto.moodType; } if (dto.intensity !== undefined) { record.intensity = dto.intensity; changes.intensity = dto.intensity; } if (dto.description !== undefined) { record.description = dto.description; changes.description = dto.description; } if (dto.checkinDate !== undefined) { record.checkinDate = dto.checkinDate; changes.checkinDate = dto.checkinDate; } if (dto.metadata !== undefined) { record.metadata = dto.metadata as any; changes.metadata = dto.metadata; } await record.save(); await this.activityLogsService.record({ userId: record.userId, entityType: ActivityEntityType.CHECKIN, action: ActivityActionType.UPDATE, entityId: record.id, changes, }); return { code: ResponseCode.SUCCESS, message: '心情打卡更新成功', data: record.toJSON() }; } async remove(dto: RemoveMoodCheckinDto, userId: string): Promise { const record = await this.moodCheckinModel.findByPk(dto.id); if (!record) { throw new NotFoundException('心情打卡记录不存在'); } if (record.userId !== userId) { throw new ForbiddenException('无权操作该心情打卡记录'); } record.deleted = true; await record.save(); await this.activityLogsService.record({ userId: record.userId, entityType: ActivityEntityType.CHECKIN, action: ActivityActionType.DELETE, entityId: record.id, changes: null, }); return { code: ResponseCode.SUCCESS, message: '心情打卡删除成功', data: { id: dto.id } }; } async getDaily(userId: string, date?: string): Promise { const targetDate = date || dayjs().format('YYYY-MM-DD'); if (!dayjs(targetDate, 'YYYY-MM-DD').isValid()) { throw new BadRequestException('无效日期格式'); } const records = await this.moodCheckinModel.findAll({ where: { userId, checkinDate: targetDate, deleted: false, }, order: [['createdAt', 'ASC']], }); return { code: ResponseCode.SUCCESS, message: 'success', data: records.map(r => r.toJSON()) }; } async getHistory(userId: string, query: GetMoodHistoryQueryDto): Promise { const start = dayjs(query.startDate, 'YYYY-MM-DD'); const end = dayjs(query.endDate, 'YYYY-MM-DD'); if (!start.isValid() || !end.isValid() || end.isBefore(start)) { throw new BadRequestException('无效日期范围'); } const whereCondition: any = { userId, checkinDate: { [Op.between]: [start.format('YYYY-MM-DD'), end.format('YYYY-MM-DD')], }, deleted: false, }; if (query.moodType) { whereCondition.moodType = query.moodType; } const records = await this.moodCheckinModel.findAll({ where: whereCondition, order: [['checkinDate', 'DESC'], ['createdAt', 'DESC']], }); return { code: ResponseCode.SUCCESS, message: 'success', data: records.map(r => r.toJSON()) }; } async getStatistics(userId: string, startDate: string, endDate: string): Promise> { const start = dayjs(startDate, 'YYYY-MM-DD'); const end = dayjs(endDate, 'YYYY-MM-DD'); if (!start.isValid() || !end.isValid() || end.isBefore(start)) { throw new BadRequestException('无效日期范围'); } const records = await this.moodCheckinModel.findAll({ where: { userId, checkinDate: { [Op.between]: [start.format('YYYY-MM-DD'), end.format('YYYY-MM-DD')], }, deleted: false, }, }); const totalCheckins = records.length; const averageIntensity = totalCheckins > 0 ? records.reduce((sum, record) => sum + record.intensity, 0) / totalCheckins : 0; const moodDistribution: Record = { [MoodType.HAPPY]: 0, [MoodType.EXCITED]: 0, [MoodType.THRILLED]: 0, [MoodType.CALM]: 0, [MoodType.ANXIOUS]: 0, [MoodType.SAD]: 0, [MoodType.LONELY]: 0, [MoodType.WRONGED]: 0, [MoodType.ANGRY]: 0, [MoodType.TIRED]: 0, }; records.forEach(record => { moodDistribution[record.moodType]++; }); const mostFrequentMood = totalCheckins > 0 ? Object.entries(moodDistribution).reduce((a, b) => moodDistribution[a[0] as MoodType] > moodDistribution[b[0] as MoodType] ? a : b )[0] as MoodType : null; const statistics: MoodStatistics = { totalCheckins, averageIntensity: Math.round(averageIntensity * 100) / 100, moodDistribution, mostFrequentMood, }; return { code: ResponseCode.SUCCESS, message: 'success', data: statistics }; } }