Skip to content

Commit

Permalink
[BE#374] polling 처리 및 시간 슬라이싱 저장
Browse files Browse the repository at this point in the history
  • Loading branch information
yeongbinim authored Dec 7, 2023
1 parent 5f1ea47 commit 707ddfa
Show file tree
Hide file tree
Showing 10 changed files with 135 additions and 24 deletions.
2 changes: 2 additions & 0 deletions BE/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { LoggingMiddleware } from './common/middleware/logging.middleware';
import { typeormConfig } from './common/config/typeorm.config';
import { staticConfig } from './common/config/static.config';
import { AdminModule } from './admin/admin.module';
import { HeartbeatModule } from './heartbeat/heartbeat.module';

@Module({
imports: [
Expand All @@ -32,6 +33,7 @@ import { AdminModule } from './admin/admin.module';
PassportModule,
AuthModule,
AdminModule,
HeartbeatModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
18 changes: 13 additions & 5 deletions BE/src/common/redis.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,23 @@ export class RedisService {
this.client = createClient();
this.client.connect();
}
async set(key: string, value: string) {
await this.client.set(key, value);
async hset(key: string, field: string, value: string) {
await this.client.hSet(key, field, value);
}

get(key: string): Promise<string | null> {
return this.client.get(key);
hget(key: string, field: string): Promise<string | null> {
return this.client.hGet(key, field);
}

async del(key: string): Promise<void> {
async hdel(key: string, field: string): Promise<void> {
await this.client.hDel(key, field);
}

async del(key: string) {
await this.client.del(key);
}

getKeys() {
return this.client.keys('*');
}
}
16 changes: 16 additions & 0 deletions BE/src/heartbeat/heartbeat.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Controller, Get, UseGuards } from '@nestjs/common';
import { HeartbeatService } from './heartbeat.service';
import { User } from 'src/users/decorator/user.decorator';
import { AccessTokenGuard } from 'src/auth/guard/bearer-token.guard';

@Controller()
export class HeartbeatController {
constructor(private readonly heartbeatsService: HeartbeatService) {}

@UseGuards(AccessTokenGuard)
@Get('/heartbeat')
heartbeat(@User('id') userId: number) {
this.heartbeatsService.recordHeartbeat(userId);
return { statusCode: 200, message: 'OK' };
}
}
13 changes: 13 additions & 0 deletions BE/src/heartbeat/heartbeat.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { HeartbeatService } from './heartbeat.service';
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';

@Module({
imports: [AuthModule, UsersModule],
controllers: [HeartbeatController],
providers: [HeartbeatService, RedisService],
})
export class HeartbeatModule {}
27 changes: 27 additions & 0 deletions BE/src/heartbeat/heartbeat.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Injectable } from '@nestjs/common';
import { RedisService } from 'src/common/redis.service';

@Injectable()
export class HeartbeatService {
constructor(private redisService: RedisService) {}

recordHeartbeat(userId: number) {
this.redisService.hset(`${userId}`, 'received_at', `${Date.now()}`);
}

async startCheckingHeartbeats() {
setInterval(async () => {
const now = Date.now();
const clients = await this.redisService.getKeys();
for (const clientId of clients) {
const received_at = await this.redisService.hget(
`${clientId}`,
'received_at',
);
if (now - +received_at > 30000) {
await this.redisService.del(`${clientId}`);
}
}
}, 10000);
}
}
4 changes: 4 additions & 0 deletions BE/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { LoggingInterceptor } from './common/interceptor/logging.interceptor';
import { HttpExceptionFilter } from './common/exception-filter/http-exception-filter';
import { swaggerConfig } from './common/config/swagger.config';
import { ENV } from './common/const/env-keys.const';
import { HeartbeatService } from './heartbeat/heartbeat.service';

async function bootstrap() {
const configService = new ConfigService();
Expand All @@ -35,6 +36,9 @@ async function bootstrap() {
app.useGlobalInterceptors(new LoggingInterceptor());
app.useGlobalFilters(new HttpExceptionFilter());

const heartbeatService = app.get(HeartbeatService);
heartbeatService.startCheckingHeartbeats();

await app.listen(configService.get<number>(ENV.PORT) || 3000);
}
bootstrap();
7 changes: 5 additions & 2 deletions BE/src/mates/mates.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ export class MatesService {
);
return Promise.all(
studyTimeByFollowing.map(async (record) => {
const started_at = await this.redisService.get(`${record.id}`);
const started_at = await this.redisService.hget(
`${record.id}`,
'started_at',
);
return {
...record,
image_url: getImageUrl(
Expand All @@ -89,7 +92,7 @@ export class MatesService {
const userIds = result.map((following) => following.following_id.id);
return Promise.all(
userIds.map(async (id) => {
const started_at = await this.redisService.get(`${id}`);
const started_at = await this.redisService.hget(`${id}`, 'started_at');
return { id, started_at };
}),
);
Expand Down
4 changes: 2 additions & 2 deletions BE/src/study-logs/dto/request/create-study-logs.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ export class StudyLogsCreateDto {
example: '2023-11-23',
description: '학습을 시작한 날짜',
})
date: Date;
date: string;

@ApiProperty({
type: 'date',
example: '2023-11-23 11:00:12',
description: '학습을 시작/종료 시점의 시간',
})
created_at: Date;
created_at: string;

@ApiProperty({
type: 'enum',
Expand Down
6 changes: 1 addition & 5 deletions BE/src/study-logs/study-logs.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,11 @@ export class StudyLogsController {
@User('id') userId: number,
@Body() studyLogsData: StudyLogsCreateDto,
): Promise<ResponseDto> {
const { created_at, learning_time, type } = studyLogsData;
const { type } = studyLogsData;
if (type === 'start') {
await this.studyLogsService.createStartLog(studyLogsData, userId);
return new ResponseDto(200, 'OK');
}
studyLogsData.date = this.studyLogsService.calculateStartDay(
new Date(created_at),
learning_time,
);
await this.studyLogsService.createFinishLog(studyLogsData, userId);
return new ResponseDto(200, 'OK');
}
Expand Down
62 changes: 52 additions & 10 deletions BE/src/study-logs/study-logs.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,35 @@ export class StudyLogsService {
user_id: number,
): Promise<void> {
const { created_at } = studyLogsData;
await this.redisService.set(`${user_id}`, `${created_at}`);
await this.redisService.hset(`${user_id}`, 'started_at', `${created_at}`);
await this.redisService.hset(`${user_id}`, 'received_at', `${Date.now()}`);
}

async createFinishLog(
studyLogsData: StudyLogsCreateDto,
user_id: number,
): Promise<StudyLogsDto> {
const { category_id, ...data } = studyLogsData;
): Promise<void> {
const { category_id } = studyLogsData;
const user = { id: user_id } as UsersModel;
const category = { id: category_id ?? null } as Categories;

const studyLog = this.studyLogsRepository.create({
...data,
user_id: user,
category_id: category,
});
const savedStudyLog = await this.studyLogsRepository.save(studyLog);
const learningTimes = this.calculateLearningTimes(
studyLogsData.created_at,
studyLogsData.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: user,
category_id: category,
});
await this.studyLogsRepository.save(studyLog);
}
await this.redisService.del(`${user_id}`);
return this.entityToDto(savedStudyLog);
}

async findAll(): Promise<StudyLogs[]> {
Expand All @@ -59,6 +69,38 @@ export class StudyLogsService {
return started_at;
}

calculateLearningTimes(
created_at: string,
learning_time: number,
): { started_at: string; date: string; learning_time: number }[] {
const finishedAt = moment(new Date(created_at));
const startedAt = finishedAt.clone().subtract(learning_time, 's');
if (startedAt.get('date') !== finishedAt.get('date')) {
return [
{
started_at: startedAt.toISOString(),
date: startedAt.format('YYYY-MM-DD'),
learning_time:
startedAt.clone().endOf('day').diff(startedAt, 's') + 1,
},
{
started_at: finishedAt.clone().startOf('day').toISOString(),
date: finishedAt.format('YYYY-MM-DD'),
learning_time: finishedAt
.clone()
.diff(finishedAt.clone().startOf('day'), 's'),
},
];
}
return [
{
started_at: startedAt.toISOString(),
date: startedAt.format('YYYY-MM-DD'),
learning_time: learning_time,
},
];
}

async calculateTotalTimes(
user_id: number,
start_date: string,
Expand Down

0 comments on commit 707ddfa

Please sign in to comment.