diff --git a/src/common/constants/errorMessage.enum.ts b/src/common/constants/errorMessage.enum.ts index c6b9366..7324499 100644 --- a/src/common/constants/errorMessage.enum.ts +++ b/src/common/constants/errorMessage.enum.ts @@ -3,9 +3,9 @@ export enum ErrorMessage { USER_NOT_FOUND = '해당 유저를 찾을 수 없습니다.', USER_EXIST = '이미 존재하는 이메일입니다.', USER_OAUTH_EXIST = '이미 존재하는 소셜 로그인 유저입니다.', - USER_NICKNAME_EXIST = '이미 존재하는 닉네임입니다.', + USER_NICKNAME_EXIST = '이미 존재하는 닉네임입니다. 다시 시도해 주세요.', USER_UNAUTHORIZED_ID = '존재하지 않는 email 입니다.', - USER_BAD_REQUEST_PW = '비밀번호가 일치하지 않습니다.', + USER_BAD_REQUEST_PW = '비밀번호가 일치하지 않습니다. 다시 시도해 주세요.', USER_UNAUTHORIZED_TOKEN = '토큰의 유효기간이 만료되었습니다. 로그인이 필요합니다', USER_FORBIDDEN_NOT_OWNER = 'Plan을 등록한 본인만 수정, 삭제할 수 있습니다. 권한을 확인해주세요.', USER_FORBIDDEN_NOT_MAKER = 'Maker 역할을 가진 사용자만 이 리소스에 접근할 수 있습니다. 권한을 확인해 주세요.', diff --git a/src/modules/notification/notification.controller.ts b/src/modules/notification/notification.controller.ts index 2dba60a..fe1d378 100644 --- a/src/modules/notification/notification.controller.ts +++ b/src/modules/notification/notification.controller.ts @@ -1,8 +1,8 @@ import { Controller, Get, Param, Patch, Post, Sse } from '@nestjs/common'; import NotificationService from './notification.service'; import { UserId } from 'src/common/decorators/user.decorator'; -import { map, Observable } from 'rxjs'; -import { Public } from 'src/common/decorators/public.decorator'; +import { interval, merge, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; import { NotificationEventName, NotificationProperties } from './types/notification.types'; @Controller('notifications') @@ -41,10 +41,16 @@ export default class NotificationController { @Sse('stream') stream(@UserId() userId: string): Observable<{ data: string }> { console.log(`SSE connection for userId: ${userId}`); - return this.service.stream(userId).pipe( + + // 40초마다 Heartbeat 전송하여 연결 유지 + const heartbeat$ = interval(40000).pipe(map(() => ({ data: 'ping' }))); + const notification$ = this.service.stream(userId).pipe( map((content: string) => { return { data: content }; }) ); + + // Heartbeat와 알림 stream 병합 + return merge(heartbeat$, notification$); } } diff --git a/src/modules/user/domain/user.domain.ts b/src/modules/user/domain/user.domain.ts index 36b7acb..7d5c6d0 100644 --- a/src/modules/user/domain/user.domain.ts +++ b/src/modules/user/domain/user.domain.ts @@ -159,6 +159,10 @@ export default class User implements IUser { return this.role ?? null; } + getNickName(): string { + return this.nickName; + } + isFollowed(dreamerId: string): boolean { return this.followers?.some((follower) => follower.dreamerId === dreamerId); } diff --git a/src/modules/user/domain/user.interface.ts b/src/modules/user/domain/user.interface.ts index 1b803c2..3d55e7f 100644 --- a/src/modules/user/domain/user.interface.ts +++ b/src/modules/user/domain/user.interface.ts @@ -21,6 +21,7 @@ export interface IUser { OAuthData(): OAuthProperties; getId(): string; getRole(): Role | null; + getNickName(): string; isFollowed(dreamerId: string): boolean; getWithMakerProfile(withDetails?: boolean): Partial; getStats(): UserStatsToClientProperties; diff --git a/src/modules/user/user.service.ts b/src/modules/user/user.service.ts index b6f0b7d..7947270 100644 --- a/src/modules/user/user.service.ts +++ b/src/modules/user/user.service.ts @@ -58,8 +58,8 @@ export default class UserService { throw new BadRequestError(ErrorMessage.USER_NOT_FOUND); } - if (data.nickName) { - const existNickName = this.repository.findByNickName(data.nickName); + if (data.nickName && data.nickName !== user.getNickName()) { + const existNickName = await this.repository.findByNickName(data.nickName); if (existNickName) { throw new BadRequestError(ErrorMessage.USER_NICKNAME_EXIST); } diff --git a/test.json b/test.json new file mode 100644 index 0000000..3be50e6 --- /dev/null +++ b/test.json @@ -0,0 +1,13 @@ +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": "arn:aws:iam::YOUR_EC2_ACCOUNT_ID:role/YOUR_IAM_ROLE_NAME" + }, + "Action": ["s3:GetObject", "s3:PutObject"], + "Resource": "arn:aws:s3:::TARGET_BUCKET_NAME/*" + } + ] +}