Skip to content

Commit

Permalink
[BE#491] heartbeat가 없는 경우 학습 기록 자동 저장 (#493)
Browse files Browse the repository at this point in the history
* feat: heartbeat 30초동안 없는 경우 자동 저장

* feat: 학습중인 친구 우선적으로 불러오기

* docs: 학습 시작 api 문서 수정

* chore: console.log 삭제
  • Loading branch information
victolee0 authored Jan 16, 2024
1 parent 2915150 commit 0251bc1
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 7 deletions.
3 changes: 2 additions & 1 deletion BE/src/heartbeat/heartbeat.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
})
Expand Down
65 changes: 64 additions & 1 deletion BE/src/heartbeat/heartbeat.service.ts
Original file line number Diff line number Diff line change
@@ -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<StudyLogs>,
@InjectRepository(UsersModel)
private usersRepository: Repository<UsersModel>,
private studyLogsService: StudyLogsService,
) {}

recordHeartbeat(userId: number) {
this.redisService.hset(`${userId}`, 'received_at', `${Date.now()}`);
Expand All @@ -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<void> {
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);
}
}
}
4 changes: 2 additions & 2 deletions BE/src/mates/mates.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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],
);
Expand Down
2 changes: 1 addition & 1 deletion BE/src/study-logs/dto/request/create-study-logs.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
9 changes: 7 additions & 2 deletions BE/src/study-logs/study-logs.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
7 changes: 7 additions & 0 deletions BE/src/study-logs/study-logs.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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<StudyLogs[]> {
Expand Down
6 changes: 6 additions & 0 deletions BE/src/users/entity/users.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];

Expand Down

0 comments on commit 0251bc1

Please sign in to comment.