Skip to content

Commit

Permalink
feat: ✨ dailyEvalCount 추가를 통해 eval count record last 지원 범위 증가
Browse files Browse the repository at this point in the history
  • Loading branch information
jpham005 committed Nov 13, 2023
1 parent 1d303ec commit 2299260
Show file tree
Hide file tree
Showing 14 changed files with 385 additions and 59 deletions.
13 changes: 13 additions & 0 deletions app/src/dailyEvalCount/dailyEvalCount.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export type FindEvalCountByDateInput = {
start: Date;
end: Date;
};

export type FindUserEvalCountByDateInput = FindEvalCountByDateInput & {
userId: number;
};

export type FindEvalCountByDateOutput = {
date: Date;
count: number;
};
23 changes: 23 additions & 0 deletions app/src/dailyEvalCount/dailyEvalCount.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { DailyEvalCountService } from './dailyEvalCount.service';
import { DailyEvalCountDaoImpl } from './db/dailyEvalCount.database.dao';
import {
dailyUserEvalCountSchema,
mv_daily_user_scale_team_counts,
} from './db/dailyEvalCount.database.schema';

@Module({
imports: [
MongooseModule.forFeature([
{
name: mv_daily_user_scale_team_counts.name,
schema: dailyUserEvalCountSchema,
},
]),
],
providers: [DailyEvalCountService, DailyEvalCountDaoImpl],
exports: [DailyEvalCountService],
})
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class DailyEvalCountModule {}
91 changes: 91 additions & 0 deletions app/src/dailyEvalCount/dailyEvalCount.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Test } from '@nestjs/testing';
import type { FindEvalCountByDateOutput } from './dailyEvalCount.dto';
import { DailyEvalCountService } from './dailyEvalCount.service';
import {
DailyEvalCountDaoImpl,
type DailyEvalCountDao,
} from './db/dailyEvalCount.database.dao';

describe(DailyEvalCountService.name, () => {
let dailyEvalCountService: DailyEvalCountService;

beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [DailyEvalCountService],
})
.useMocker((token) => {
if (token === DailyEvalCountDaoImpl) {
return {
findEvalCountsByDate: async ({
start,
end,
}): Promise<FindEvalCountByDateOutput[]> => {
return [
{
date: new Date('2022-12-31T15:00:00.000Z'),
count: 1,
},
{
date: new Date('2023-01-31T15:00:00.000Z'),
count: 2,
},
{
date: new Date('2023-06-31T15:00:00.000Z'),
count: 3,
},
{
date: new Date('2024-05-31T15:00:00.000Z'),
count: 4,
},
].filter(({ date }) => date >= start && date <= end);
},
findUserEvalCountsByDatePerMonth: async ({
start,
end,
}): Promise<FindEvalCountByDateOutput[]> => {
return [
{
date: new Date('2022-12-31T15:00:00.000Z'),
count: 1,
},
{
date: new Date('2023-01-31T15:00:00.000Z'),
count: 2,
},
{
date: new Date('2023-06-31T15:00:00.000Z'),
count: 3,
},
{
date: new Date('2024-05-31T15:00:00.000Z'),
count: 4,
},
].filter(({ date }) => date >= start && date <= end);
},
} satisfies DailyEvalCountDao;
}
})
.compile();

dailyEvalCountService = moduleRef.get<DailyEvalCountService>(
DailyEvalCountService,
);
});

describe('evalCountRecordsByDate', () => {
const testDate = {
start: new Date('2022-12-31T15:00:00.000Z'),
end: new Date('2023-12-31T15:00:00.000Z'),
};

it('should return eval count records', async () => {
const evalCountRecords =
await dailyEvalCountService.evalCountRecordsByDate(testDate);

evalCountRecords.forEach(({ at, value }) => {
expect(isNaN(new Date(at).getTime())).toBe(false);
expect(typeof value).toBe('number');
});
});
});
});
38 changes: 38 additions & 0 deletions app/src/dailyEvalCount/dailyEvalCount.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Injectable } from '@nestjs/common';
import { DailyEvalCountDaoImpl } from './db/dailyEvalCount.database.dao';
import type { DateRange } from 'src/dateRange/dtos/dateRange.dto';
import type { IntRecord } from 'src/common/models/common.valueRecord.model';

@Injectable()
export class DailyEvalCountService {
constructor(private readonly dailyEvalCountDao: DailyEvalCountDaoImpl) {}

async evalCountRecordsByDate({
start,
end,
}: DateRange): Promise<IntRecord[]> {
const evalCounts = await this.dailyEvalCountDao.findEvalCountsByDate({
start,
end,
});

return evalCounts.map(({ date, count }) => ({ at: date, value: count }));
}

async userEvalCountRecordsByDatePerMonth(
userId: number,
{ start, end }: DateRange,
): Promise<IntRecord[]> {
const userEvalCounts =
await this.dailyEvalCountDao.findUserEvalCountsByDatePerMonth({
userId,
start,
end,
});

return userEvalCounts.map(({ date, count }) => ({
at: date,
value: count,
}));
}
}
101 changes: 101 additions & 0 deletions app/src/dailyEvalCount/db/dailyEvalCount.database.dao.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { Inject, Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import type { Model } from 'mongoose';
import type {
FindEvalCountByDateInput,
FindEvalCountByDateOutput,
FindUserEvalCountByDateInput,
} from '../dailyEvalCount.dto';
import { mv_daily_user_scale_team_counts } from './dailyEvalCount.database.schema';
import { RUNTIME_CONFIG } from 'src/config/runtime';
import type { ConfigType } from '@nestjs/config';

export type DailyEvalCountDao = {
findEvalCountsByDate: (
args: FindEvalCountByDateInput,
) => Promise<FindEvalCountByDateOutput[]>;
findUserEvalCountsByDatePerMonth: (
args: FindUserEvalCountByDateInput,
) => Promise<FindEvalCountByDateOutput[]>;
};

@Injectable()
export class DailyEvalCountDaoImpl implements DailyEvalCountDao {
constructor(
@InjectModel(mv_daily_user_scale_team_counts.name)
private readonly dailyUserEvalCountModel: Model<mv_daily_user_scale_team_counts>,
@Inject(RUNTIME_CONFIG.KEY)
private readonly runtimeConfig: ConfigType<typeof RUNTIME_CONFIG>,
) {}

async findEvalCountsByDate({
start,
end,
}: FindEvalCountByDateInput): Promise<FindEvalCountByDateOutput[]> {
return await this.dailyUserEvalCountModel
.aggregate<FindEvalCountByDateOutput>()
.match({
date: {
$gte: start,
$lt: end,
},
})
.group({
// timezone 달라지면 dateFromParts 등으로 변환할것
_id: '$date',
count: {
$sum: '$count',
},
})
.sort({ _id: 1 })
.project({
_id: 0,
date: '$_id',
count: 1,
});
}

async findUserEvalCountsByDatePerMonth({
userId,
start,
end,
}: FindUserEvalCountByDateInput): Promise<FindEvalCountByDateOutput[]> {
return await this.dailyUserEvalCountModel
.aggregate<FindEvalCountByDateOutput>()
.match({
userId,
date: {
$gte: start,
$lt: end,
},
})
.group({
_id: {
$dateFromParts: {
year: {
$year: {
date: '$date',
timezone: this.runtimeConfig.TIMEZONE,
},
},
month: {
$month: {
date: '$date',
timezone: this.runtimeConfig.TIMEZONE,
},
},
timezone: this.runtimeConfig.TIMEZONE,
},
},
count: {
$sum: '$count',
},
})
.sort({ _id: 1 })
.project({
_id: 0,
date: '$_id',
count: 1,
});
}
}
21 changes: 21 additions & 0 deletions app/src/dailyEvalCount/db/dailyEvalCount.database.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { HydratedDocument } from 'mongoose';

export type DailyUserEvalCountDocument =
HydratedDocument<mv_daily_user_scale_team_counts>;

@Schema({ collection: 'mv_daily_user_scale_team_counts' })
export class mv_daily_user_scale_team_counts {
@Prop({ required: true })
count: number;

@Prop({ required: true })
date: Date;

@Prop({ required: true })
userId: number;
}

export const dailyUserEvalCountSchema = SchemaFactory.createForClass(
mv_daily_user_scale_team_counts,
);
6 changes: 3 additions & 3 deletions app/src/page/home/eval/home.eval.module.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Module } from '@nestjs/common';
import { CursusUserModule } from 'src/api/cursusUser/cursusUser.module';
import { ScaleTeamModule } from 'src/api/scaleTeam/scaleTeam.module';
import { DateRangeModule } from 'src/dateRange/dateRange.module';
import { CacheUtilModule } from 'src/cache/cache.util.module';
import { DailyEvalCountModule } from 'src/dailyEvalCount/dailyEvalCount.module';
import { HomeEvalResolver } from './home.eval.resolver';
import { HomeEvalService } from './home.eval.service';

@Module({
imports: [ScaleTeamModule, CursusUserModule, DateRangeModule],
imports: [ScaleTeamModule, DailyEvalCountModule, CacheUtilModule],
providers: [HomeEvalResolver, HomeEvalService],
})
// eslint-disable-next-line
Expand Down
41 changes: 34 additions & 7 deletions app/src/page/home/eval/home.eval.resolver.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import { UseFilters, UseGuards } from '@nestjs/common';
import { Args, Int, Query, ResolveField, Resolver } from '@nestjs/graphql';
import { StatAuthGuard } from 'src/auth/statAuthGuard';
import { CacheUtilService } from 'src/cache/cache.util.service';
import { IntRecord } from 'src/common/models/common.valueRecord.model';
import { DailyEvalCountService } from 'src/dailyEvalCount/dailyEvalCount.service';
import { DateWrapper } from 'src/dateWrapper/dateWrapper';
import { HttpExceptionFilter } from 'src/http-exception.filter';
import { HomeEvalService } from './home.eval.service';
import { HomeEval } from './models/home.eval.model';
import { GetEvalCountRecordsArgs, HomeEval } from './models/home.eval.model';

@UseFilters(HttpExceptionFilter)
@UseGuards(StatAuthGuard)
@Resolver((_of: unknown) => HomeEval)
export class HomeEvalResolver {
constructor(private readonly homeEvalService: HomeEvalService) {}
constructor(
private readonly homeEvalService: HomeEvalService,
private readonly dailyEvalCountService: DailyEvalCountService,
private readonly cacheUtilService: CacheUtilService,
) {}

@Query((_of) => HomeEval)
async getHomeEval() {
Expand All @@ -22,11 +29,31 @@ export class HomeEvalResolver {
return await this.homeEvalService.totalEvalCount();
}

@ResolveField((_returns) => [IntRecord], { description: '1 ~ 60 일' })
async evalCountRecords(@Args('last') last: number): Promise<IntRecord[]> {
return await this.homeEvalService.evalCountRecords(
Math.max(Math.min(last, 60), 1),
);
@ResolveField((_returns) => [IntRecord], { description: '1 ~ 730 일' })
async evalCountRecords(
@Args() { last }: GetEvalCountRecordsArgs,
): Promise<IntRecord[]> {
const nextDay = new DateWrapper().startOfDate().moveDate(1).toDate();
const start = new DateWrapper()
.startOfDate()
.moveDate(1 - last)
.toDate();

const cacheKey = `evalCountRecords:${start.getTime()}:${nextDay.getTime()}`;

const cached = await this.cacheUtilService.get<IntRecord[]>(cacheKey);
if (cached) {
return cached;
}

const result = await this.dailyEvalCountService.evalCountRecordsByDate({
start: start,
end: nextDay,
});

await this.cacheUtilService.set(cacheKey, result, DateWrapper.MIN);

return result;
}

@ResolveField((_returns) => Int)
Expand Down
Loading

0 comments on commit 2299260

Please sign in to comment.