From f855879e46e926a50b9c6e787a5ee434afb28673 Mon Sep 17 00:00:00 2001 From: jaham Date: Wed, 15 Nov 2023 00:08:23 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20:sparkles:=20blackholed=20user=20record?= =?UTF-8?q?=20last=20=EC=A6=9D=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 방식대로 처리해도 크게 무리 없는 수준이라 판단했습니다. - #392 --- app/src/page/home/user/home.user.resolver.ts | 37 +++++++++++++--- app/src/page/home/user/home.user.service.ts | 42 +++++++------------ .../page/home/user/models/home.user.model.ts | 11 ++++- 3 files changed, 55 insertions(+), 35 deletions(-) diff --git a/app/src/page/home/user/home.user.resolver.ts b/app/src/page/home/user/home.user.resolver.ts index cc751040..b833f0f1 100644 --- a/app/src/page/home/user/home.user.resolver.ts +++ b/app/src/page/home/user/home.user.resolver.ts @@ -1,12 +1,15 @@ import { UseFilters, UseGuards } from '@nestjs/common'; import { Args, Query, ResolveField, Resolver } from '@nestjs/graphql'; import { StatAuthGuard } from 'src/auth/statAuthGuard'; +import { CacheUtilService } from 'src/cache/cache.util.service'; import { Rate } from 'src/common/models/common.rate.model'; import { UserRank } from 'src/common/models/common.user.model'; import { IntRecord } from 'src/common/models/common.valueRecord.model'; +import { DateWrapper } from 'src/dateWrapper/dateWrapper'; import { HttpExceptionFilter } from 'src/http-exception.filter'; import { HomeUserService } from './home.user.service'; import { + GetHomeUserBlackholedCountRecordsArgs, HomeUser, IntPerCircle, UserCountPerLevel, @@ -16,7 +19,10 @@ import { @UseGuards(StatAuthGuard) @Resolver((_of: unknown) => HomeUser) export class HomeUserResolver { - constructor(private readonly homeUserService: HomeUserService) {} + constructor( + private readonly homeUserService: HomeUserService, + private readonly cacheUtilService: CacheUtilService, + ) {} @Query((_of) => HomeUser) async getHomeUser() { @@ -43,13 +49,32 @@ export class HomeUserResolver { return await this.homeUserService.blackholedRate(); } - @ResolveField((_returns) => [IntRecord], { description: '1 ~ 24 개월' }) + @ResolveField((_returns) => [IntRecord], { + description: '1 ~ 120 개월', + }) async blackholedCountRecords( - @Args('last') last: number, + @Args() { last }: GetHomeUserBlackholedCountRecordsArgs, ): Promise { - return await this.homeUserService.blackholedCountRecords( - Math.max(1, Math.min(last, 24)), - ); + const nextMonth = DateWrapper.nextMonth().toDate(); + const start = DateWrapper.currMonth() + .moveMonth(1 - last) + .toDate(); + + const cacheKey = `homeUserBlackholedCountRecords:${start.getTime()}:${nextMonth.getTime()}`; + + const cached = await this.cacheUtilService.get(cacheKey); + if (cached) { + return cached; + } + + const result = await this.homeUserService.blackholedCountRecords({ + start, + end: nextMonth, + }); + + await this.cacheUtilService.set(cacheKey, result); + + return result; } @ResolveField((_returns) => [IntPerCircle]) diff --git a/app/src/page/home/user/home.user.service.ts b/app/src/page/home/user/home.user.service.ts index 0f9be23c..d5061d0f 100644 --- a/app/src/page/home/user/home.user.service.ts +++ b/app/src/page/home/user/home.user.service.ts @@ -16,7 +16,6 @@ import { assertExist } from 'src/common/assertExist'; import type { Rate } from 'src/common/models/common.rate.model'; import type { UserRank } from 'src/common/models/common.user.model'; import type { IntRecord } from 'src/common/models/common.valueRecord.model'; -import { DateRangeService } from 'src/dateRange/dateRange.service'; import type { DateRange } from 'src/dateRange/dtos/dateRange.dto'; import { DateWrapper } from 'src/dateWrapper/dateWrapper'; import type { IntPerCircle, UserCountPerLevel } from './models/home.user.model'; @@ -27,7 +26,6 @@ export class HomeUserService { private readonly cursusUserService: CursusUserService, private readonly cursusUserCacheService: CursusUserCacheService, private readonly questsUserService: QuestsUserService, - private readonly dateRangeService: DateRangeService, ) {} @CacheOnReturn() @@ -90,46 +88,34 @@ export class HomeUserService { }; } - @CacheOnReturn() - async blackholedCountRecords(last: number): Promise { - const startDate = new DateWrapper() - .startOfMonth() - .moveMonth(1 - last) - .toDate(); - - const blackholeds: { blackholedAt?: Date }[] = + async blackholedCountRecords({ + start, + end, + }: DateRange): Promise { + const blackholed: Pick[] = await this.cursusUserService.findAllAndLean({ - filter: blackholedUserFilterByDateRange({ - start: startDate, - end: new Date(), - }), + filter: blackholedUserFilterByDateRange({ start, end }), select: { blackholedAt: 1 }, }); - const res = blackholeds.reduce((acc, { blackholedAt }) => { + const groupedRecord = blackholed.reduce((acc, { blackholedAt }) => { assertExist(blackholedAt); - const date = new DateWrapper(blackholedAt) + const timestamp = new DateWrapper(blackholedAt) .startOfMonth() .toDate() .getTime(); - const prev = acc.get(date); + const prev = acc.get(timestamp) ?? 0; - acc.set(date, (prev ?? 0) + 1); + acc.set(timestamp, prev + 1); return acc; - }, new Map() as Map); - - const records: IntRecord[] = []; - - for (let i = 0; i < last; i++) { - const currDate = new DateWrapper(startDate).moveMonth(i).toDate(); - - records.push({ at: currDate, value: res.get(currDate.getTime()) ?? 0 }); - } + }, new Map()); - return records; + return [...groupedRecord.entries()] + .sort(([a], [b]) => a - b) + .map(([timestamp, value]) => ({ at: new Date(timestamp), value })); } @CacheOnReturn() diff --git a/app/src/page/home/user/models/home.user.model.ts b/app/src/page/home/user/models/home.user.model.ts index 3e34c901..b26e60d8 100644 --- a/app/src/page/home/user/models/home.user.model.ts +++ b/app/src/page/home/user/models/home.user.model.ts @@ -1,4 +1,5 @@ -import { Field, ObjectType } from '@nestjs/graphql'; +import { ArgsType, Field, ObjectType } from '@nestjs/graphql'; +import { Max, Min } from 'class-validator'; import { Rate } from 'src/common/models/common.rate.model'; import { UserRank } from 'src/common/models/common.user.model'; import { IntRecord } from 'src/common/models/common.valueRecord.model'; @@ -50,3 +51,11 @@ export class HomeUser { @Field((_type) => [IntPerCircle]) averageDurationPerCircle: IntPerCircle[]; } + +@ArgsType() +export class GetHomeUserBlackholedCountRecordsArgs { + @Min(1) + @Max(750) + @Field() + last: number; +}