From 2dc017273dfd7d2fdf5f84db28ffa973d0a60aec Mon Sep 17 00:00:00 2001 From: lsh23 <shseoul14@gmail.com> Date: Fri, 8 Dec 2023 01:16:45 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[BE]=20feat:=20avatar=20holder=EB=A5=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/auth/application/avatar.holder.spec.ts | 23 +++++++++++++++++++ BE/src/auth/application/avatar.holder.ts | 20 ++++++++++++++++ BE/src/auth/auth.module.ts | 2 ++ 3 files changed, 45 insertions(+) create mode 100644 BE/src/auth/application/avatar.holder.spec.ts create mode 100644 BE/src/auth/application/avatar.holder.ts diff --git a/BE/src/auth/application/avatar.holder.spec.ts b/BE/src/auth/application/avatar.holder.spec.ts new file mode 100644 index 00000000..e1d90202 --- /dev/null +++ b/BE/src/auth/application/avatar.holder.spec.ts @@ -0,0 +1,23 @@ +import { ConfigService } from '@nestjs/config'; +import { AvatarHolder } from './avatar.holder'; + +describe('AvatarHolder Test', () => { + test('기본 avatarUrl을 환경변수로 부터 가져온다.', () => { + //given + //when + const configService = new ConfigService({ + USER_AVATAR_URLS: 'url1,url2,url3,url4,url5', + }); + const avatarHolder: AvatarHolder = new AvatarHolder(configService); + + //then + expect(avatarHolder.getUrls()).toEqual([ + 'url1', + 'url2', + 'url3', + 'url4', + 'url5', + ]); + expect(avatarHolder.getUrls()).toContain(avatarHolder.getUrl()); + }); +}); diff --git a/BE/src/auth/application/avatar.holder.ts b/BE/src/auth/application/avatar.holder.ts new file mode 100644 index 00000000..005340d2 --- /dev/null +++ b/BE/src/auth/application/avatar.holder.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; + +@Injectable() +export class AvatarHolder { + private readonly defaultAvatarUrls: string[]; + constructor(configService: ConfigService) { + const rawAvatarUrls = configService.get<string>('USER_AVATAR_URLS'); + this.defaultAvatarUrls = rawAvatarUrls.split(','); + } + + getUrls() { + return this.defaultAvatarUrls; + } + + getUrl() { + const idx = Math.floor(Math.random() * this.defaultAvatarUrls.length); + return this.defaultAvatarUrls[idx]; + } +} diff --git a/BE/src/auth/auth.module.ts b/BE/src/auth/auth.module.ts index 77c4865d..2551092d 100644 --- a/BE/src/auth/auth.module.ts +++ b/BE/src/auth/auth.module.ts @@ -14,6 +14,7 @@ import { AccessTokenGuard } from './guard/access-token.guard'; import { CacheModule } from '@nestjs/cache-manager'; import { redisModuleOptions } from '../config/redis'; import type { RedisClientOptions } from 'redis'; +import { AvatarHolder } from './application/avatar.holder'; @Global() @Module({ @@ -32,6 +33,7 @@ import type { RedisClientOptions } from 'redis'; JwtUtils, UserCodeGenerator, AccessTokenGuard, + AvatarHolder, ], exports: [JwtUtils, AccessTokenGuard], }) From 84d9526220bf6fbdb5e9b0eac4f385d66cb4926b Mon Sep 17 00:00:00 2001 From: lsh23 <shseoul14@gmail.com> Date: Fri, 8 Dec 2023 01:18:40 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[BE]=20feat:=20=ED=9A=8C=EC=9B=90=EC=9D=B4?= =?UTF-8?q?=20=EB=93=B1=EB=A1=9D=20=EB=90=98=EB=8A=94=20=EC=8B=9C=EC=A0=90?= =?UTF-8?q?=EC=97=90=20avatar=EC=9D=B4=20=EC=9E=90=EB=8F=99=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=A7=80=EC=A0=95=EB=90=9C=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/auth/application/auth.service.spec.ts | 4 ++++ BE/src/auth/application/auth.service.ts | 3 +++ BE/src/config/config/index.ts | 1 + BE/src/users/domain/user.domain.ts | 4 ++++ 4 files changed, 12 insertions(+) diff --git a/BE/src/auth/application/auth.service.spec.ts b/BE/src/auth/application/auth.service.spec.ts index 80f39dca..7b50d9a0 100644 --- a/BE/src/auth/application/auth.service.spec.ts +++ b/BE/src/auth/application/auth.service.spec.ts @@ -20,6 +20,7 @@ import { Cache } from 'cache-manager'; import { RefreshTokenNotFoundException } from '../exception/refresh-token-not-found.exception'; import { AuthTestModule } from '../../../test/auth/auth-test.module'; import { anyString, instance, mock, when } from 'ts-mockito'; +import { AvatarHolder } from './avatar.holder'; describe('AuthService', () => { let authService: AuthService; @@ -41,6 +42,7 @@ describe('AuthService', () => { ], providers: [ AuthService, + AvatarHolder, OauthHandler, OauthRequester, JwtUtils, @@ -76,6 +78,8 @@ describe('AuthService', () => { response.user.userCode, ); expect(response.user).toBeDefined(); + expect(response.user.userCode).toBeDefined(); + expect(response.user.avatarUrl).toBeDefined(); expect(response.accessToken).toBeDefined(); expect(response.refreshToken).toBeDefined(); expect(response.refreshToken).toBeDefined(); diff --git a/BE/src/auth/application/auth.service.ts b/BE/src/auth/application/auth.service.ts index d00796d9..ee2d8955 100644 --- a/BE/src/auth/application/auth.service.ts +++ b/BE/src/auth/application/auth.service.ts @@ -14,6 +14,7 @@ import { RefreshAuthResponseDto } from '../dto/refresh-auth-response.dto'; import { RefreshTokenNotFoundException } from '../exception/refresh-token-not-found.exception'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import { Cache } from 'cache-manager'; +import { AvatarHolder } from './avatar.holder'; @Injectable() export class AuthService { @@ -23,6 +24,7 @@ export class AuthService { private readonly oauthHandler: OauthHandler, private readonly userCodeGenerator: UserCodeGenerator, private readonly jwtUtils: JwtUtils, + private readonly avatarHolder: AvatarHolder, ) {} @Transactional() @@ -56,6 +58,7 @@ export class AuthService { const newUser = User.from(userIdentifier); const userCode = await this.userCodeGenerator.generate(); newUser.assignUserCode(userCode); + newUser.assignAvatar(this.avatarHolder.getUrl()); return await this.usersRepository.saveUser(newUser); } diff --git a/BE/src/config/config/index.ts b/BE/src/config/config/index.ts index e3784680..336e483b 100644 --- a/BE/src/config/config/index.ts +++ b/BE/src/config/config/index.ts @@ -50,6 +50,7 @@ export const configServiceModuleOptions = { }), GROUP_AVATAR_URLS: Joi.string().required(), + USER_AVATAR_URLS: Joi.string().required(), REDIS_HOST: Joi.number().when('NODE_ENV', { is: 'production', diff --git a/BE/src/users/domain/user.domain.ts b/BE/src/users/domain/user.domain.ts index 5ac751de..027f25d7 100644 --- a/BE/src/users/domain/user.domain.ts +++ b/BE/src/users/domain/user.domain.ts @@ -20,4 +20,8 @@ export class User { assignUserCode(userCode: string) { this.userCode = userCode; } + + assignAvatar(avatarUrl: string) { + this.avatarUrl = avatarUrl; + } } From 7bc4d7e457c28cb4a1137d62c1998bb821e04344 Mon Sep 17 00:00:00 2001 From: lsh23 <shseoul14@gmail.com> Date: Fri, 8 Dec 2023 01:19:19 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[BE]=20feat:=20=EA=B7=B8=EB=A3=B9=20?= =?UTF-8?q?=EB=8B=AC=EC=84=B1=20=EA=B8=B0=EB=A1=9D=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=9D=98=20=EC=9D=91=EB=8B=B5=EC=97=90=20=EC=9C=A0?= =?UTF-8?q?=EC=A0=80=EC=97=90=20=EB=8C=80=ED=95=9C=20avatar=20url=EC=9D=84?= =?UTF-8?q?=20=ED=8F=AC=ED=95=A8=ED=95=9C=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/group-achievement.service.ts | 4 +-- .../group-achievement.controller.spec.ts | 30 +++++++++++++++---- .../dto/group-achievement-response.ts | 11 +++---- .../group-achievement.repository.spec.ts | 3 +- .../entities/group-achievement.repository.ts | 2 +- BE/src/group/achievement/index.ts | 10 +++++++ 6 files changed, 44 insertions(+), 16 deletions(-) diff --git a/BE/src/group/achievement/application/group-achievement.service.ts b/BE/src/group/achievement/application/group-achievement.service.ts index 3ec5e11f..4763bacc 100644 --- a/BE/src/group/achievement/application/group-achievement.service.ts +++ b/BE/src/group/achievement/application/group-achievement.service.ts @@ -95,9 +95,7 @@ export class GroupAchievementService { ); return new PaginateGroupAchievementResponse( paginateGroupAchievementRequest, - achievements.map((achievement) => - GroupAchievementResponse.from(achievement), - ), + achievements, ); } diff --git a/BE/src/group/achievement/controller/group-achievement.controller.spec.ts b/BE/src/group/achievement/controller/group-achievement.controller.spec.ts index 640dfe5f..1ae335d6 100644 --- a/BE/src/group/achievement/controller/group-achievement.controller.spec.ts +++ b/BE/src/group/achievement/controller/group-achievement.controller.spec.ts @@ -349,21 +349,30 @@ describe('GroupAchievementController', () => { { id: 6, title: 'test6', - userCode: 'ABCDEFG', + user: { + userCode: 'ABCDEF3', + avatarUrl: 'avatarUrl1', + }, categoryId: 1, thumbnailUrl: 'thumbnail_url6', }, { id: 3, title: 'test3', - userCode: 'ABCDEFG', + user: { + userCode: 'ABCDEF2', + avatarUrl: 'avatarUrl2', + }, categoryId: 1, thumbnailUrl: 'thumbnail_url3', }, { id: 2, title: 'test2', - userCode: 'ABCDEFG', + user: { + userCode: 'ABCDEF1', + avatarUrl: 'avatarUrl3', + }, categoryId: 1, thumbnailUrl: 'thumbnail_url2', }, @@ -394,21 +403,30 @@ describe('GroupAchievementController', () => { id: 6, thumbnailUrl: 'thumbnail_url6', title: 'test6', - userCode: 'ABCDEFG', + user: { + avatarUrl: 'avatarUrl1', + userCode: 'ABCDEF3', + }, }, { categoryId: 1, id: 3, thumbnailUrl: 'thumbnail_url3', title: 'test3', - userCode: 'ABCDEFG', + user: { + avatarUrl: 'avatarUrl2', + userCode: 'ABCDEF2', + }, }, { categoryId: 1, id: 2, thumbnailUrl: 'thumbnail_url2', title: 'test2', - userCode: 'ABCDEFG', + user: { + avatarUrl: 'avatarUrl3', + userCode: 'ABCDEF1', + }, }, ], next: { diff --git a/BE/src/group/achievement/dto/group-achievement-response.ts b/BE/src/group/achievement/dto/group-achievement-response.ts index f1fe0e78..975c122c 100644 --- a/BE/src/group/achievement/dto/group-achievement-response.ts +++ b/BE/src/group/achievement/dto/group-achievement-response.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IGroupAchievementListDetail } from '../index'; +import { IGroupAchievementListDetail, UserInfo } from '../index'; export class GroupAchievementResponse { @ApiProperty({ description: 'id' }) @@ -10,20 +10,20 @@ export class GroupAchievementResponse { title: string; @ApiProperty({ description: 'categoryId' }) categoryId: number; - @ApiProperty({ description: 'userCode' }) - userCode: string; - + @ApiProperty({ description: 'user', type: UserInfo }) + user: UserInfo; constructor( id: number, thumbnailUrl: string, title: string, userCode: string, + avatarUrl: string, categoryId: number | null, ) { this.id = id; this.thumbnailUrl = thumbnailUrl; this.title = title; - this.userCode = userCode; + this.user = new UserInfo(userCode, avatarUrl); this.categoryId = categoryId ? categoryId : -1; } @@ -33,6 +33,7 @@ export class GroupAchievementResponse { groupAchievementListDetail.thumbnailUrl, groupAchievementListDetail.title, groupAchievementListDetail.userCode, + groupAchievementListDetail.avatarUrl, groupAchievementListDetail.categoryId, ); } diff --git a/BE/src/group/achievement/entities/group-achievement.repository.spec.ts b/BE/src/group/achievement/entities/group-achievement.repository.spec.ts index 71f5b0f0..270654d1 100644 --- a/BE/src/group/achievement/entities/group-achievement.repository.spec.ts +++ b/BE/src/group/achievement/entities/group-achievement.repository.spec.ts @@ -470,7 +470,8 @@ describe('GroupRepository Test', () => { // then expect(findAll.length).toEqual(30); expect(findAll[0].id).toEqual(last.id); - expect(findAll[0].userCode).toEqual(last.user.userCode); + expect(findAll[0].user.userCode).toEqual(last.user.userCode); + expect(findAll[0].user.avatarUrl).toEqual(last.user.avatarUrl); expect(findAll[0].title).toEqual(last.title); expect(findAll[0].categoryId).toEqual(last.groupCategory.id); expect(findAll[0].thumbnailUrl).toEqual(last.image.thumbnailUrl); diff --git a/BE/src/group/achievement/entities/group-achievement.repository.ts b/BE/src/group/achievement/entities/group-achievement.repository.ts index 5a2737a2..c261ece8 100644 --- a/BE/src/group/achievement/entities/group-achievement.repository.ts +++ b/BE/src/group/achievement/entities/group-achievement.repository.ts @@ -125,7 +125,7 @@ export class GroupAchievementRepository extends TransactionalRepository<GroupAch 'user', 'group_achievement.user_id = user.id', ) - .addSelect(['user.user_code as userCode']) + .addSelect(['user.user_code as userCode', 'user.avatar_url as avatarUrl']) .leftJoin('group_achievement.groupCategory', 'category') .addSelect('category.id as categoryId') diff --git a/BE/src/group/achievement/index.ts b/BE/src/group/achievement/index.ts index 494894fe..2ec9499b 100644 --- a/BE/src/group/achievement/index.ts +++ b/BE/src/group/achievement/index.ts @@ -11,6 +11,7 @@ export interface IGroupAchievementListDetail { title: string; thumbnailUrl: string; userCode: string; + avatarUrl: string; categoryId: number; } @@ -22,3 +23,12 @@ export interface GroupAchievementUpdate { imageUrl?: string; thumbnailUrl?: string; } + +export class UserInfo { + userCode: string; + avatarUrl: string; + constructor(userCode: string, avatarUrl: string) { + this.userCode = userCode; + this.avatarUrl = avatarUrl; + } +} From 48efb52060b146c0fbb1f54cffda67ffacd1161d Mon Sep 17 00:00:00 2001 From: lsh23 <shseoul14@gmail.com> Date: Fri, 8 Dec 2023 01:19:47 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[BE]=20fix:=20REDIS=20HOST=20=ED=99=98?= =?UTF-8?q?=EA=B2=BD=EB=B3=80=EC=88=98=EC=97=90=20=EB=8C=80=ED=95=9C=20?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BE/src/config/config/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BE/src/config/config/index.ts b/BE/src/config/config/index.ts index 336e483b..4aa52875 100644 --- a/BE/src/config/config/index.ts +++ b/BE/src/config/config/index.ts @@ -52,7 +52,7 @@ export const configServiceModuleOptions = { GROUP_AVATAR_URLS: Joi.string().required(), USER_AVATAR_URLS: Joi.string().required(), - REDIS_HOST: Joi.number().when('NODE_ENV', { + REDIS_HOST: Joi.string().when('NODE_ENV', { is: 'production', then: Joi.required(), otherwise: Joi.optional(), From 54771f2b13285a12a174cf4531165d943f621aa6 Mon Sep 17 00:00:00 2001 From: lsh23 <shseoul14@gmail.com> Date: Fri, 8 Dec 2023 01:26:56 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[BE]=20fix:=20CI=20&=20CD=EC=97=90=20USER?= =?UTF-8?q?=5FAVATAR=5FURLS=20=EB=B3=80=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/be.cd.yml | 1 + .github/workflows/be.ci.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/be.cd.yml b/.github/workflows/be.cd.yml index c858641f..0d4159df 100644 --- a/.github/workflows/be.cd.yml +++ b/.github/workflows/be.cd.yml @@ -66,6 +66,7 @@ jobs: FILESTORE_IMAGE_PREFIX: ${{ secrets.FILESTORE_IMAGE_PREFIX }} FILESTORE_THUMBNAIL_PREFIX: ${{ secrets.FILESTORE_THUMBNAIL_PREFIX }} GROUP_AVATAR_URLS: ${{ secrets.GROUP_AVATAR_URLS }} + USER_AVATAR_URLS: ${{ secrets.USER_AVATAR_URLS }} EOF - name: Install dependencies diff --git a/.github/workflows/be.ci.yml b/.github/workflows/be.ci.yml index b8909499..229eec03 100644 --- a/.github/workflows/be.ci.yml +++ b/.github/workflows/be.ci.yml @@ -70,6 +70,7 @@ jobs: FILESTORE_IMAGE_PREFIX: ${{ secrets.FILESTORE_IMAGE_PREFIX }} FILESTORE_THUMBNAIL_PREFIX: ${{ secrets.FILESTORE_THUMBNAIL_PREFIX }} GROUP_AVATAR_URLS: ${{ secrets.GROUP_AVATAR_URLS }} + USER_AVATAR_URLS: ${{ secrets.USER_AVATAR_URLS }} EOF - name: Install dependencies run: npm ci