From f01f2f931db6c0949b8297e7e8fe60dcdcc875ea Mon Sep 17 00:00:00 2001 From: jaham Date: Mon, 13 Nov 2023 21:52:39 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20:sparkles:=20daily=20coalition=20score?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=EB=A5=BC=20=ED=86=B5=ED=95=9C=20scoreReco?= =?UTF-8?q?rdsPerCoalition=20last=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #392 --- .../dailyCoalitionScore.dto.ts | 12 +++ .../dailyCoalitionScore.module.ts | 22 +++++ .../dailyCoalitionScore.service.ts | 29 ++++++ .../db/dailyCoalitionScore.database.dao.ts | 91 +++++++++++++++++++ .../db/dailyCoalitionScore.database.schema.ts | 21 +++++ app/src/lambda/lambda.service.ts | 25 +---- .../home/coalition/home.coalition.module.ts | 9 +- .../home/coalition/home.coalition.resolver.ts | 39 +++++++- .../home/coalition/home.coalition.service.ts | 50 +--------- .../coalition/models/home.coalition.model.ts | 12 ++- app/src/schema.gql | 2 +- 11 files changed, 233 insertions(+), 79 deletions(-) create mode 100644 app/src/dailyCoalitionScore/dailyCoalitionScore.dto.ts create mode 100644 app/src/dailyCoalitionScore/dailyCoalitionScore.module.ts create mode 100644 app/src/dailyCoalitionScore/dailyCoalitionScore.service.ts create mode 100644 app/src/dailyCoalitionScore/db/dailyCoalitionScore.database.dao.ts create mode 100644 app/src/dailyCoalitionScore/db/dailyCoalitionScore.database.schema.ts diff --git a/app/src/dailyCoalitionScore/dailyCoalitionScore.dto.ts b/app/src/dailyCoalitionScore/dailyCoalitionScore.dto.ts new file mode 100644 index 00000000..89f02ff1 --- /dev/null +++ b/app/src/dailyCoalitionScore/dailyCoalitionScore.dto.ts @@ -0,0 +1,12 @@ +import type { coalition } from 'src/api/coalition/db/coalition.database.schema'; +import type { DateRange } from 'src/dateRange/dtos/dateRange.dto'; + +export type FindScorePerCoalitionByDateInput = DateRange; + +export type FindScorePerCoalitionByDateOutput = { + coalition: coalition; + scores: { + date: Date; + value: number; + }[]; +}; diff --git a/app/src/dailyCoalitionScore/dailyCoalitionScore.module.ts b/app/src/dailyCoalitionScore/dailyCoalitionScore.module.ts new file mode 100644 index 00000000..5e389aa1 --- /dev/null +++ b/app/src/dailyCoalitionScore/dailyCoalitionScore.module.ts @@ -0,0 +1,22 @@ +import { Module } from '@nestjs/common'; +import { MongooseModule } from '@nestjs/mongoose'; +import { CoalitionModule } from 'src/api/coalition/coalition.module'; +import { DailyCoalitionScoreService } from './dailyCoalitionScore.service'; +import { DailyCoalitionScoreDaoImpl } from './db/dailyCoalitionScore.database.dao'; +import { + dailyCoalitionScoreSchema, + mv_daily_score_values, +} from './db/dailyCoalitionScore.database.schema'; + +@Module({ + imports: [ + MongooseModule.forFeature([ + { name: mv_daily_score_values.name, schema: dailyCoalitionScoreSchema }, + ]), + CoalitionModule, + ], + providers: [DailyCoalitionScoreService, DailyCoalitionScoreDaoImpl], + exports: [DailyCoalitionScoreService], +}) +// eslint-disable-next-line @typescript-eslint/no-extraneous-class +export class DailyCoalitionScoreModule {} diff --git a/app/src/dailyCoalitionScore/dailyCoalitionScore.service.ts b/app/src/dailyCoalitionScore/dailyCoalitionScore.service.ts new file mode 100644 index 00000000..01c50ddc --- /dev/null +++ b/app/src/dailyCoalitionScore/dailyCoalitionScore.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import { DailyCoalitionScoreDaoImpl } from './db/dailyCoalitionScore.database.dao'; +import { ScoreRecordPerCoalition } from 'src/page/home/coalition/models/home.coalition.model'; +import type { DateRange } from 'src/dateRange/dtos/dateRange.dto'; +import { CoalitionService } from 'src/api/coalition/coalition.service'; + +@Injectable() +export class DailyCoalitionScoreService { + constructor( + private readonly dailyCoalitionScoreDao: DailyCoalitionScoreDaoImpl, + private readonly coalitionService: CoalitionService, + ) {} + + async scoreRecordsPerCoalitions({ + start, + end, + }: DateRange): Promise { + const scoresPerCoalition = + await this.dailyCoalitionScoreDao.findScoresPerCoalitionByDate({ + start, + end, + }); + + return scoresPerCoalition.map(({ coalition, scores }) => ({ + coalition: this.coalitionService.daoToDto(coalition), + records: scores.map(({ date, value }) => ({ at: date, value })), + })); + } +} diff --git a/app/src/dailyCoalitionScore/db/dailyCoalitionScore.database.dao.ts b/app/src/dailyCoalitionScore/db/dailyCoalitionScore.database.dao.ts new file mode 100644 index 00000000..275ed427 --- /dev/null +++ b/app/src/dailyCoalitionScore/db/dailyCoalitionScore.database.dao.ts @@ -0,0 +1,91 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { InjectModel } from '@nestjs/mongoose'; +import type { Model } from 'mongoose'; +import { mv_daily_score_values } from './dailyCoalitionScore.database.schema'; +import type { + FindScorePerCoalitionByDateInput, + FindScorePerCoalitionByDateOutput, +} from '../dailyCoalitionScore.dto'; +import { RUNTIME_CONFIG } from 'src/config/runtime'; +import type { ConfigType } from '@nestjs/config'; +import { lookupCoalition } from 'src/api/coalition/db/coalition.database.aggregate'; +import { coalition } from 'src/api/coalition/db/coalition.database.schema'; + +export type DailyCoalitionScoreDao = { + findScoresPerCoalitionByDate: ( + args: FindScorePerCoalitionByDateInput, + ) => Promise; +}; + +@Injectable() +export class DailyCoalitionScoreDaoImpl implements DailyCoalitionScoreDao { + constructor( + @InjectModel(mv_daily_score_values.name) + private readonly dailyCoalitionScore: Model, + @Inject(RUNTIME_CONFIG.KEY) + private readonly runtimeConfig: ConfigType, + ) {} + + async findScoresPerCoalitionByDate({ + start, + end, + }: FindScorePerCoalitionByDateInput): Promise< + FindScorePerCoalitionByDateOutput[] + > { + return await this.dailyCoalitionScore + .aggregate() + .match({ + date: { + $gte: start, + $lt: end, + }, + }) + .group({ + _id: { + date: { + $dateFromParts: { + year: { + $year: { + date: '$date', + timezone: this.runtimeConfig.TIMEZONE, + }, + }, + month: { + $month: { + date: '$date', + timezone: this.runtimeConfig.TIMEZONE, + }, + }, + timezone: this.runtimeConfig.TIMEZONE, + }, + }, + coalitionId: '$coalitionId', + }, + value: { + $sum: '$value', + }, + }) + .sort({ + '_id.date': 1, + '_id.coalitionId': 1, + }) + .group({ + _id: '$_id.coalitionId', + scores: { + $push: { + date: '$_id.date', + value: '$value', + }, + }, + }) + .append(lookupCoalition('_id', 'id')) + .sort({ _id: 1 }) + .project({ + _id: 0, + coalition: { + $first: `$${coalition.name}s`, + }, + scores: 1, + }); + } +} diff --git a/app/src/dailyCoalitionScore/db/dailyCoalitionScore.database.schema.ts b/app/src/dailyCoalitionScore/db/dailyCoalitionScore.database.schema.ts new file mode 100644 index 00000000..7e6eaa26 --- /dev/null +++ b/app/src/dailyCoalitionScore/db/dailyCoalitionScore.database.schema.ts @@ -0,0 +1,21 @@ +import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; +import type { HydratedDocument } from 'mongoose'; + +export type DailyCoalitionScoreDocument = + HydratedDocument; + +@Schema({ collection: 'mv_daily_score_values' }) +export class mv_daily_score_values { + @Prop({ required: true }) + date: Date; + + @Prop({ required: true }) + coalitionId: number; + + @Prop({ required: true }) + value: number; +} + +export const dailyCoalitionScoreSchema = SchemaFactory.createForClass( + mv_daily_score_values, +); diff --git a/app/src/lambda/lambda.service.ts b/app/src/lambda/lambda.service.ts index a7a1dbba..1c0a8537 100644 --- a/app/src/lambda/lambda.service.ts +++ b/app/src/lambda/lambda.service.ts @@ -24,7 +24,6 @@ import { ScaleTeamService } from 'src/api/scaleTeam/scaleTeam.service'; import { scoreDateRangeFilter } from 'src/api/score/db/score.database.aggregate'; import { SCORE_RANKING, - SCORE_RECORDS, TOTAL_SCORES_BY_COALITION, WIN_COUNT_PER_COALITION, } from 'src/api/score/score.cache.service'; @@ -32,8 +31,7 @@ import { ScoreService } from 'src/api/score/score.service'; import { CacheUtilRankingService } from 'src/cache/cache.util.ranking.service'; import { CacheUtilService } from 'src/cache/cache.util.service'; import { DateRangeService } from 'src/dateRange/dateRange.service'; -import { DateTemplate, type DateRange } from 'src/dateRange/dtos/dateRange.dto'; -import { DateWrapper } from 'src/dateWrapper/dateWrapper'; +import { DateTemplate } from 'src/dateRange/dtos/dateRange.dto'; export const LAMBDA_UPDATED_AT = 'lambdaUpdatedAt'; @@ -177,7 +175,6 @@ export class LambdaService { ); await this.updateTotalScoresPerCoalition(updatedAt); - await this.updateScoreRecords(updatedAt); await this.updateWinCountPerCoalition(updatedAt); const currMonthExp = await this.experienceUserService.increamentRanking( @@ -284,26 +281,6 @@ export class LambdaService { ); } - private async updateScoreRecords(updatedAt: Date): Promise { - const nextMonth = DateWrapper.currMonth().moveMonth(1); - const lastYear = nextMonth.moveYear(-1); - - const dateRange: DateRange = { - start: lastYear.toDate(), - end: nextMonth.toDate(), - }; - - const scoreRecords = await this.scoreService.scoreRecordsPerCoalition({ - filter: scoreDateRangeFilter(dateRange), - }); - - await this.cacheUtilService.setWithDate( - SCORE_RECORDS, - scoreRecords, - updatedAt, - ); - } - private async updateWinCountPerCoalition(updatedAt: Date): Promise { const winCountPerCoalition = await this.scoreService.winCountPerCoalition(); diff --git a/app/src/page/home/coalition/home.coalition.module.ts b/app/src/page/home/coalition/home.coalition.module.ts index 758a2d04..241d94f2 100644 --- a/app/src/page/home/coalition/home.coalition.module.ts +++ b/app/src/page/home/coalition/home.coalition.module.ts @@ -1,11 +1,18 @@ import { Module } from '@nestjs/common'; import { ScoreModule } from 'src/api/score/score.module'; +import { CacheUtilModule } from 'src/cache/cache.util.module'; +import { DailyCoalitionScoreModule } from 'src/dailyCoalitionScore/dailyCoalitionScore.module'; import { DateRangeModule } from 'src/dateRange/dateRange.module'; import { HomeCoalitionResolver } from './home.coalition.resolver'; import { HomeCoalitionService } from './home.coalition.service'; @Module({ - imports: [ScoreModule, DateRangeModule], + imports: [ + ScoreModule, + DateRangeModule, + DailyCoalitionScoreModule, + CacheUtilModule, + ], providers: [HomeCoalitionResolver, HomeCoalitionService], }) // eslint-disable-next-line diff --git a/app/src/page/home/coalition/home.coalition.resolver.ts b/app/src/page/home/coalition/home.coalition.resolver.ts index ed19726b..6d911344 100644 --- a/app/src/page/home/coalition/home.coalition.resolver.ts +++ b/app/src/page/home/coalition/home.coalition.resolver.ts @@ -1,10 +1,14 @@ 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 { DailyCoalitionScoreService } from 'src/dailyCoalitionScore/dailyCoalitionScore.service'; import { DateTemplateArgs } from 'src/dateRange/dtos/dateRange.dto'; +import { DateWrapper } from 'src/dateWrapper/dateWrapper'; import { HttpExceptionFilter } from 'src/http-exception.filter'; import { HomeCoalitionService } from './home.coalition.service'; import { + GetScoreRecordsPerCoalitionArgs, HomeCoalition, IntPerCoalition, IntPerCoalitionDateRanged, @@ -15,7 +19,11 @@ import { @UseGuards(StatAuthGuard) @Resolver((_of: unknown) => HomeCoalition) export class HomeCoalitionResolver { - constructor(private readonly homeCoalitionService: HomeCoalitionService) {} + constructor( + private readonly homeCoalitionService: HomeCoalitionService, + private readonly dailyCoalitionScoreService: DailyCoalitionScoreService, + private readonly cacheUtilService: CacheUtilService, + ) {} @Query((_of) => HomeCoalition) async getHomeCoalition() { @@ -28,8 +36,33 @@ export class HomeCoalitionResolver { } @ResolveField((_returns) => [ScoreRecordPerCoalition]) - async scoreRecordsPerCoalition(): Promise { - return await this.homeCoalitionService.scoreRecordsPerCoalition(); + async scoreRecordsPerCoalition( + @Args() { last }: GetScoreRecordsPerCoalitionArgs, + ): Promise { + const nextMonth = DateWrapper.nextMonth().toDate(); + const start = DateWrapper.currMonth() + .moveMonth(1 - last) + .toDate(); + + const cacheKey = `homeCoalitionScoreRecordsPerCoalition:${start.getTime()}:${nextMonth.getTime()}`; + + const cached = await this.cacheUtilService.get( + cacheKey, + ); + + if (cached) { + return cached; + } + + const result = + await this.dailyCoalitionScoreService.scoreRecordsPerCoalitions({ + start, + end: nextMonth, + }); + + await this.cacheUtilService.set(cacheKey, result, DateWrapper.MIN); + + return result; } @ResolveField((_returns) => IntPerCoalitionDateRanged) diff --git a/app/src/page/home/coalition/home.coalition.service.ts b/app/src/page/home/coalition/home.coalition.service.ts index 4c61fe0b..ee44b470 100644 --- a/app/src/page/home/coalition/home.coalition.service.ts +++ b/app/src/page/home/coalition/home.coalition.service.ts @@ -4,14 +4,11 @@ import { ScoreCacheService } from 'src/api/score/score.cache.service'; import { ScoreService } from 'src/api/score/score.service'; import { CacheOnReturn } from 'src/cache/decrators/onReturn/cache.decorator.onReturn.symbol'; import { assertExist } from 'src/common/assertExist'; -import type { IntRecord } from 'src/common/models/common.valueRecord.model'; import { DateRangeService } from 'src/dateRange/dateRange.service'; -import { DateRange, DateTemplate } from 'src/dateRange/dtos/dateRange.dto'; -import { DateWrapper } from 'src/dateWrapper/dateWrapper'; +import { DateTemplate } from 'src/dateRange/dtos/dateRange.dto'; import type { IntPerCoalition, IntPerCoalitionDateRanged, - ScoreRecordPerCoalition, } from './models/home.coalition.model'; @Injectable() @@ -29,51 +26,6 @@ export class HomeCoalitionService { return cachedTotalScores ?? (await this.scoreService.scoresPerCoalition()); } - async scoreRecordsPerCoalition(): Promise { - const cachedScoreRecords = await this.scoreCacheService.getScoreRecords(); - - const nextMonth = new DateWrapper().startOfMonth().moveMonth(1); - const lastYear = nextMonth.moveYear(-1); - - const dateRange: DateRange = { - start: lastYear.toDate(), - end: nextMonth.toDate(), - }; - - const scoreRecords = - cachedScoreRecords ?? - (await this.scoreService.scoreRecordsPerCoalition({ - filter: scoreDateRangeFilter(dateRange), - })); - - const dates: Date[] = []; - - for ( - let currMonth = lastYear; - currMonth.toDate() < nextMonth.toDate(); - currMonth = currMonth.moveMonth(1) - ) { - dates.push(currMonth.toDate()); - } - - return scoreRecords.map(({ coalition, records }) => { - const zeroFilledRecords = dates.reduce((newRecords, currDate) => { - const currValue = records.find( - ({ at }) => currDate.getTime() === at.getTime(), - )?.value; - - newRecords.push({ at: currDate, value: currValue ?? 0 }); - - return newRecords; - }, new Array()); - - return { - coalition, - records: zeroFilledRecords, - }; - }); - } - @CacheOnReturn() async tigCountPerCoalitionByDateTemplate( dateTemplate: DateTemplate, diff --git a/app/src/page/home/coalition/models/home.coalition.model.ts b/app/src/page/home/coalition/models/home.coalition.model.ts index c43fc18f..7ec380d1 100644 --- a/app/src/page/home/coalition/models/home.coalition.model.ts +++ b/app/src/page/home/coalition/models/home.coalition.model.ts @@ -1,7 +1,8 @@ -import { Field, ObjectType } from '@nestjs/graphql'; +import { ArgsType, Field, ObjectType } from '@nestjs/graphql'; import { Coalition } from 'src/page/common/models/coalition.model'; import { IntRecord } from 'src/common/models/common.valueRecord.model'; import { ArrayDateRanged } from 'src/dateRange/models/dateRange.model'; +import { IsOptional, Max, Min } from 'class-validator'; @ObjectType() export class IntPerCoalition { @@ -37,3 +38,12 @@ export class HomeCoalition { @Field() tigCountPerCoalitionByDateTemplate: IntPerCoalitionDateRanged; } + +@ArgsType() +export class GetScoreRecordsPerCoalitionArgs { + @Min(1) + @Max(120) + @IsOptional() + @Field({ defaultValue: 12 }) + last: number; +} diff --git a/app/src/schema.gql b/app/src/schema.gql index 2f801645..9e807e50 100644 --- a/app/src/schema.gql +++ b/app/src/schema.gql @@ -163,7 +163,7 @@ type ScoreRecordPerCoalition { type HomeCoalition { totalScoresPerCoalition: [IntPerCoalition!]! - scoreRecordsPerCoalition: [ScoreRecordPerCoalition!]! + scoreRecordsPerCoalition(last: Int! = 12): [ScoreRecordPerCoalition!]! tigCountPerCoalitionByDateTemplate(dateTemplate: DateTemplate!): IntPerCoalitionDateRanged! winCountPerCoalition: [IntPerCoalition!]! }