diff --git a/BE/src/heartbeat/heartbeat.module.ts b/BE/src/heartbeat/heartbeat.module.ts index aba62a7..d97973a 100644 --- a/BE/src/heartbeat/heartbeat.module.ts +++ b/BE/src/heartbeat/heartbeat.module.ts @@ -4,9 +4,10 @@ import { HeartbeatController } from './heartbeat.controller'; import { AuthModule } from 'src/auth/auth.module'; import { UsersModule } from 'src/users/users.module'; import { RedisService } from 'src/common/redis.service'; +import { StudyLogsModule } from 'src/study-logs/study-logs.module'; @Module({ - imports: [AuthModule, UsersModule], + imports: [AuthModule, UsersModule, StudyLogsModule], controllers: [HeartbeatController], providers: [HeartbeatService, RedisService], }) diff --git a/BE/src/heartbeat/heartbeat.service.ts b/BE/src/heartbeat/heartbeat.service.ts index a49f267..78784a1 100644 --- a/BE/src/heartbeat/heartbeat.service.ts +++ b/BE/src/heartbeat/heartbeat.service.ts @@ -1,9 +1,23 @@ import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Categories } from 'src/categories/categories.entity'; import { RedisService } from 'src/common/redis.service'; +import { StudyLogs } from 'src/study-logs/study-logs.entity'; +import { StudyLogsService } from 'src/study-logs/study-logs.service'; +import { UsersModel } from 'src/users/entity/users.entity'; +import { Repository } from 'typeorm'; +import moment from 'moment'; @Injectable() export class HeartbeatService { - constructor(private redisService: RedisService) {} + constructor( + private redisService: RedisService, + @InjectRepository(StudyLogs) + private studyLogsRepository: Repository, + @InjectRepository(UsersModel) + private usersRepository: Repository, + private studyLogsService: StudyLogsService, + ) {} recordHeartbeat(userId: number) { this.redisService.hset(`${userId}`, 'received_at', `${Date.now()}`); @@ -19,9 +33,58 @@ export class HeartbeatService { 'received_at', ); if (now - +received_at > 30000) { + await this.removeOldData(clientId); await this.redisService.del(`${clientId}`); + await this.usersRepository.update( + { id: +clientId }, + { is_studying: false }, + ); } } }, 10000); } + + async removeOldData(clientId: string): Promise { + const started_at = await this.redisService.hget( + `${clientId}`, + 'started_at', + ); + const received_at = await this.redisService.hget( + `${clientId}`, + 'received_at', + ); + const category_id = await this.redisService.hget( + `${clientId}`, + 'category_id', + ); + + const learning_time = moment(+received_at).diff(started_at, 's'); + const moment_received_at = moment(+received_at); + const offset = await this.usersRepository.findOne({ + select: ['timezone'], + where: { id: +clientId }, + }); + + const received_at_with_offset = `${moment_received_at.format( + 'YYYY-MM-DD HH:mm:ss', + )}${offset.timezone}`; + + const learningTimes = this.studyLogsService.calculateLearningTimes( + received_at_with_offset, + +learning_time, + ); + + for (const { started_at, date, learning_time } of learningTimes) { + const studyLog = this.studyLogsRepository.create({ + type: 'finish', + date, + learning_time, + created_at: started_at, + user_id: { id: +clientId } as UsersModel, + category_id: { id: +category_id || null } as Categories, + is_finished: false, + }); + await this.studyLogsRepository.save(studyLog); + } + } } diff --git a/BE/src/mates/mates.service.ts b/BE/src/mates/mates.service.ts index 875460a..a342067 100644 --- a/BE/src/mates/mates.service.ts +++ b/BE/src/mates/mates.service.ts @@ -131,8 +131,8 @@ export class MatesService { LEFT JOIN mates m ON m.following_id = u.id LEFT JOIN study_logs s ON s.user_id = u.id AND s.date = DATE(CONVERT_TZ(?, ?, u.timezone)) WHERE m.follower_id = ? - GROUP BY u.id, m.fixation - ORDER BY m.fixation DESC, total_time DESC + GROUP BY u.id, m.is_fixed + ORDER BY m.is_fixed DESC, u.is_studying DESC, total_time DESC `, [followerDate, followerTimezone, followerId], ); diff --git a/BE/src/study-logs/dto/request/create-study-logs.dto.ts b/BE/src/study-logs/dto/request/create-study-logs.dto.ts index aede1a2..47fd605 100644 --- a/BE/src/study-logs/dto/request/create-study-logs.dto.ts +++ b/BE/src/study-logs/dto/request/create-study-logs.dto.ts @@ -9,7 +9,7 @@ export class StudyLogsCreateDto { @ApiProperty({ type: 'date', - example: '2023-11-23 11:00:12', + example: '2023-11-23 11:00:12+09:00', description: '학습을 시작/종료 시점의 시간', }) created_at: string; diff --git a/BE/src/study-logs/study-logs.entity.ts b/BE/src/study-logs/study-logs.entity.ts index 55a5809..2b6f5c9 100644 --- a/BE/src/study-logs/study-logs.entity.ts +++ b/BE/src/study-logs/study-logs.entity.ts @@ -21,18 +21,23 @@ export class StudyLogs { @Column({ type: 'datetime' }) @IsString() - @Matches(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i, {message: '올바른 시간 형식이 아닙니다.'}) + @Matches(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/i, { + message: '올바른 시간 형식이 아닙니다.', + }) created_at: Date; @Column({ type: 'enum', enum: ['start', 'finish'] }) @IsString() - @Matches(/^(start|finish)$/i, {message: '올바른 타입이 아닙니다.'}) + @Matches(/^(start|finish)$/i, { message: '올바른 타입이 아닙니다.' }) type: 'start' | 'finish'; @Column({ type: 'int', default: 0 }) @IsNumber() learning_time: number; + @Column({ type: 'boolean', default: true }) + is_finished: boolean; + @ManyToOne(() => UsersModel, (user) => user.study_logs, { eager: true, onDelete: 'CASCADE', diff --git a/BE/src/study-logs/study-logs.service.ts b/BE/src/study-logs/study-logs.service.ts index 755bd84..34ad599 100644 --- a/BE/src/study-logs/study-logs.service.ts +++ b/BE/src/study-logs/study-logs.service.ts @@ -27,6 +27,12 @@ export class StudyLogsService { const { created_at } = studyLogsData; await this.redisService.hset(`${user_id}`, 'started_at', `${created_at}`); await this.redisService.hset(`${user_id}`, 'received_at', `${Date.now()}`); + await this.redisService.hset( + `${user_id}`, + 'category_id', + `${studyLogsData.category_id ?? null}`, + ); + await this.usersRepository.update({ id: user_id }, { is_studying: true }); } async createFinishLog( @@ -54,6 +60,7 @@ export class StudyLogsService { await this.studyLogsRepository.save(studyLog); } await this.redisService.del(`${user_id}`); + await this.usersRepository.update({ id: user_id }, { is_studying: false }); } async findAll(): Promise { diff --git a/BE/src/users/entity/users.entity.ts b/BE/src/users/entity/users.entity.ts index 3753e0a..fe21eba 100644 --- a/BE/src/users/entity/users.entity.ts +++ b/BE/src/users/entity/users.entity.ts @@ -59,6 +59,12 @@ export class UsersModel { }) timezone: string; + @Column({ + type: 'boolean', + default: false, + }) + is_studying: boolean; + @OneToMany(() => StudyLogs, (studyLog) => studyLog.user_id) study_logs: StudyLogs[];