diff --git a/app/src/page/myInfo/models/myInfo.model.ts b/app/src/page/myInfo/models/myInfo.model.ts index d250e214..719368f3 100644 --- a/app/src/page/myInfo/models/myInfo.model.ts +++ b/app/src/page/myInfo/models/myInfo.model.ts @@ -1,20 +1,25 @@ -import { Field, ObjectType } from '@nestjs/graphql'; +import { Field, Float, ObjectType } from '@nestjs/graphql'; import { UserPreview } from 'src/common/models/common.user.model'; import { UserTeam } from 'src/page/personal/general/models/personal.general.model'; -export type MyInfoRoot = { +type SeoulStudentInfo = { userPreview: UserPreview; displayname: string; + level: number; + beginAt: Date; }; -@ObjectType() -export class MyInfo { - @Field() +type SeoulUserInfo = { userPreview: UserPreview; - - @Field() displayname: string; + level?: number; + beginAt?: Date; +}; + +export type MyInfoRoot = SeoulStudentInfo | SeoulUserInfo; +@ObjectType() +export class MyRecentActivity { @Field() isNewMember: boolean; @@ -33,3 +38,56 @@ export class MyInfo { @Field({ nullable: true }) evalCountRank?: number; } + +@ObjectType() +export class MyInfo { + @Field() + userPreview: UserPreview; + + @Field() + displayname: string; + + @Field((_type) => Float, { nullable: true }) + level?: number; + + @Field({ nullable: true }) + beginAt?: Date; + + @Field({ nullable: true }) + myRecentActivity?: MyRecentActivity; + + @Field({ + deprecationReason: 'deprecated at v0.9.0, recentActivity 를 사용하세요', + }) + isNewMember: boolean; + + @Field({ + nullable: true, + deprecationReason: 'deprecated at v0.9.0, recentActivity 를 사용하세요', + }) + lastValidatedTeam?: UserTeam; + + @Field({ + nullable: true, + deprecationReason: 'deprecated at v0.9.0, recentActivity 를 사용하세요', + }) + blackholedAt?: Date; + + @Field({ + nullable: true, + deprecationReason: 'deprecated at v0.9.0, recentActivity 를 사용하세요', + }) + experienceRank?: number; + + @Field({ + nullable: true, + deprecationReason: 'deprecated at v0.9.0, recentActivity 를 사용하세요', + }) + scoreRank?: number; + + @Field({ + nullable: true, + deprecationReason: 'deprecated at v0.9.0, recentActivity 를 사용하세요', + }) + evalCountRank?: number; +} diff --git a/app/src/page/myInfo/myInfo.module.ts b/app/src/page/myInfo/myInfo.module.ts index da8d8ab8..605292e2 100644 --- a/app/src/page/myInfo/myInfo.module.ts +++ b/app/src/page/myInfo/myInfo.module.ts @@ -6,7 +6,6 @@ import { ScaleTeamModule } from 'src/api/scaleTeam/scaleTeam.module'; import { ScoreModule } from 'src/api/score/score.module'; import { TeamModule } from 'src/api/team/team.module'; import { UserModule } from 'src/api/user/user.module'; -import { DateRangeModule } from 'src/dateRange/dateRange.module'; import { MyInfoResolver } from './myInfo.resolver'; import { MyInfoService } from './myInfo.service'; @@ -19,7 +18,6 @@ import { MyInfoService } from './myInfo.service'; ScaleTeamModule, QuestsUserModule, UserModule, - DateRangeModule, ], providers: [MyInfoResolver, MyInfoService], }) diff --git a/app/src/page/myInfo/myInfo.resolver.ts b/app/src/page/myInfo/myInfo.resolver.ts index b459d2ff..e3a347a6 100644 --- a/app/src/page/myInfo/myInfo.resolver.ts +++ b/app/src/page/myInfo/myInfo.resolver.ts @@ -1,10 +1,10 @@ import { UseFilters, UseGuards } from '@nestjs/common'; -import { Int, Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { Int, Query, ResolveField, Resolver, Root } from '@nestjs/graphql'; import { MyUserId } from 'src/auth/myContext'; import { StatAuthGuard } from 'src/auth/statAuthGuard'; import { HttpExceptionFilter } from 'src/http-exception.filter'; import { UserTeam } from '../personal/general/models/personal.general.model'; -import { MyInfo, MyInfoRoot } from './models/myInfo.model'; +import { MyInfo, MyInfoRoot, MyRecentActivity } from './models/myInfo.model'; import { MyInfoService } from './myInfo.service'; @UseFilters(HttpExceptionFilter) @@ -18,6 +18,13 @@ export class MyInfoResolver { return await this.myInfoService.myInfoRoot(myUserId); } + @ResolveField((_returns) => MyRecentActivity, { nullable: true }) + async myRecentActivity( + @Root() myInfoRoot?: MyInfoRoot, + ): Promise { + return await this.myInfoService.myRecentActivity(myInfoRoot); + } + @ResolveField((_returns) => Date, { nullable: true }) async blackholedAt(@MyUserId() myUserId: number): Promise { return await this.myInfoService.blackholedAt(myUserId); @@ -25,7 +32,7 @@ export class MyInfoResolver { @ResolveField((_returns) => Boolean) async isNewMember(@MyUserId() myUserId: number): Promise { - return await this.myInfoService.isNewMember(myUserId); + return (await this.myInfoService.isNewMember(myUserId)) ?? false; } @ResolveField((_returns) => UserTeam, { nullable: true }) diff --git a/app/src/page/myInfo/myInfo.service.ts b/app/src/page/myInfo/myInfo.service.ts index 3345f4b6..f1e1e92a 100644 --- a/app/src/page/myInfo/myInfo.service.ts +++ b/app/src/page/myInfo/myInfo.service.ts @@ -1,31 +1,22 @@ -import { Injectable, NotFoundException } from '@nestjs/common'; -import type { FilterQuery } from 'mongoose'; +import { Injectable } from '@nestjs/common'; import { CursusUserCacheService } from 'src/api/cursusUser/cursusUser.cache.service'; import { CursusUserService } from 'src/api/cursusUser/cursusUser.service'; import type { cursus_user } from 'src/api/cursusUser/db/cursusUser.database.schema'; -import type { experience_user } from 'src/api/experienceUser/db/experienceUser.database.schema'; import { ExperienceUserCacheService } from 'src/api/experienceUser/experienceUser.cache.service'; -import { ExperienceUserService } from 'src/api/experienceUser/experienceUser.service'; import type { quests_user } from 'src/api/questsUser/db/questsUser.database.schema'; import { COMMON_CORE_QUEST_ID, QuestsUserService, } from 'src/api/questsUser/questsUser.service'; -import type { scale_team } from 'src/api/scaleTeam/db/scaleTeam.database.schema'; import { ScaleTeamCacheService } from 'src/api/scaleTeam/scaleTeam.cache.service'; -import { ScaleTeamService } from 'src/api/scaleTeam/scaleTeam.service'; -import { scoreDateRangeFilter } from 'src/api/score/db/score.database.aggregate'; import { ScoreCacheService } from 'src/api/score/score.cache.service'; -import { ScoreService } from 'src/api/score/score.service'; import { TeamService } from 'src/api/team/team.service'; import { UserService } from 'src/api/user/user.service'; import { CacheOnReturn } from 'src/cache/decrators/onReturn/cache.decorator.onReturn.symbol'; -import { findUserRank } from 'src/common/findUserRank'; -import { DateRangeService } from 'src/dateRange/dateRange.service'; import { DateTemplate } from 'src/dateRange/dtos/dateRange.dto'; import { DateWrapper } from 'src/dateWrapper/dateWrapper'; import type { UserTeam } from '../personal/general/models/personal.general.model'; -import type { MyInfoRoot } from './models/myInfo.model'; +import type { MyInfoRoot, MyRecentActivity } from './models/myInfo.model'; @Injectable() export class MyInfoService { @@ -34,14 +25,10 @@ export class MyInfoService { private readonly cursusUserCacheService: CursusUserCacheService, private readonly questsUserService: QuestsUserService, private readonly teamService: TeamService, - private readonly experienceUserService: ExperienceUserService, private readonly experienceUserCacheService: ExperienceUserCacheService, - private readonly scoreService: ScoreService, private readonly scoreCacheService: ScoreCacheService, - private readonly scaleTeamService: ScaleTeamService, private readonly scaleTeamCacheService: ScaleTeamCacheService, private readonly userService: UserService, - private readonly dateRangeService: DateRangeService, ) {} @CacheOnReturn() @@ -57,6 +44,8 @@ export class MyInfoService { imgUrl: cachedUserFullProfile.cursusUser.user.image.link, }, displayname: cachedUserFullProfile.cursusUser.user.displayname, + beginAt: cachedUserFullProfile.cursusUser.beginAt, + level: cachedUserFullProfile.cursusUser.level, }; } @@ -89,6 +78,40 @@ export class MyInfoService { return null; } + @CacheOnReturn() + async myRecentActivity( + myInfoRoot?: MyInfoRoot, + ): Promise { + // todo: isStudent 같은 field 를 제공하는 것도 방법일듯 + if (!myInfoRoot || !myInfoRoot.beginAt) { + return null; + } + + const userId = myInfoRoot.userPreview.id; + + const cachedUserFullProfile = + await this.cursusUserCacheService.getUserFullProfile(userId); + + if (!cachedUserFullProfile) { + return null; + } + + const isNewMember = await this.isNewMember(userId); + if (isNewMember === null) { + return null; + } + + return { + isNewMember, + lastValidatedTeam: (await this.lastValidatedTeam(userId)) ?? undefined, + blackholedAt: cachedUserFullProfile.cursusUser.blackholedAt, + experienceRank: await this.experienceRank(userId), + scoreRank: await this.scoreRank(userId), + evalCountRank: await this.evalCountRank(userId), + }; + } + + // todo: deprecate 이후 안쓰는 함수 삭제 및 private method 로 변경 @CacheOnReturn() async blackholedAt(userId: number): Promise { const cachedUserFullProfile = @@ -105,7 +128,7 @@ export class MyInfoService { } @CacheOnReturn() - async isNewMember(userId: number): Promise { + async isNewMember(userId: number): Promise { const questsUser: Pick | null = await this.questsUserService.findOneAndLean({ filter: { @@ -116,7 +139,7 @@ export class MyInfoService { }); if (!questsUser) { - throw new NotFoundException(); + return null; } if (!questsUser.validatedAt) { @@ -152,22 +175,7 @@ export class MyInfoService { userId, ); - if (cachedRank) { - return cachedRank.rank; - } - - const dateRange = this.dateRangeService.dateRangeFromTemplate( - DateTemplate.CURR_WEEK, - ); - - const dateFilter: FilterQuery = { - createdAt: this.dateRangeService.aggrFilterFromDateRange(dateRange), - }; - - const expIncreamentRanking = - await this.experienceUserService.increamentRanking(dateFilter); - - return findUserRank(expIncreamentRanking, userId)?.rank; + return cachedRank?.rank; } async scoreRank(userId: number): Promise { @@ -176,19 +184,7 @@ export class MyInfoService { userId, ); - if (cachedRank) { - return cachedRank.rank; - } - - const dateRange = this.dateRangeService.dateRangeFromTemplate( - DateTemplate.CURR_WEEK, - ); - - const ranking = await this.scoreService.scoreRanking({ - filter: scoreDateRangeFilter(dateRange), - }); - - return findUserRank(ranking, userId)?.rank; + return cachedRank?.rank; } async evalCountRank(userId: number): Promise { @@ -197,22 +193,6 @@ export class MyInfoService { userId, ); - if (cachedRanking) { - return cachedRanking.rank; - } - - const dateRange = this.dateRangeService.dateRangeFromTemplate( - DateTemplate.CURR_WEEK, - ); - - const dateFilter: FilterQuery = { - beginAt: this.dateRangeService.aggrFilterFromDateRange(dateRange), - }; - - const evalCountRanking = await this.scaleTeamService.evalCountRanking( - dateFilter, - ); - - return findUserRank(evalCountRanking, userId)?.rank; + return cachedRanking?.rank; } } diff --git a/app/src/schema.gql b/app/src/schema.gql index 2b2f7b95..08480350 100644 --- a/app/src/schema.gql +++ b/app/src/schema.gql @@ -401,9 +401,7 @@ type PersonalGeneral { character: Character } -type MyInfo { - userPreview: UserPreview! - displayname: String! +type MyRecentActivity { isNewMember: Boolean! lastValidatedTeam: UserTeam blackholedAt: DateTime @@ -412,6 +410,20 @@ type MyInfo { evalCountRank: Int } +type MyInfo { + userPreview: UserPreview! + displayname: String! + level: Float + beginAt: DateTime + myRecentActivity: MyRecentActivity + isNewMember: Boolean! @deprecated(reason: "deprecated at v0.9.0, recentActivity 를 사용하세요") + lastValidatedTeam: UserTeam @deprecated(reason: "deprecated at v0.9.0, recentActivity 를 사용하세요") + blackholedAt: DateTime @deprecated(reason: "deprecated at v0.9.0, recentActivity 를 사용하세요") + experienceRank: Int @deprecated(reason: "deprecated at v0.9.0, recentActivity 를 사용하세요") + scoreRank: Int @deprecated(reason: "deprecated at v0.9.0, recentActivity 를 사용하세요") + evalCountRank: Int @deprecated(reason: "deprecated at v0.9.0, recentActivity 를 사용하세요") +} + type PersonalEval { userProfile: UserProfile! correctionPoint: Int!