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 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/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('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], }) diff --git a/BE/src/config/config/index.ts b/BE/src/config/config/index.ts index e3784680..4aa52875 100644 --- a/BE/src/config/config/index.ts +++ b/BE/src/config/config/index.ts @@ -50,8 +50,9 @@ 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(), 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