Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
d1e4cc4
[feat] 피드 관련 알림 DB 저장 + 이벤트 퍼블리시 를 담당하는 인터페이스 추가 (#296)
seongjunnoh Sep 11, 2025
c926863
[feat] 모임방 관련 알림 DB 저장 + 이벤트 퍼블리시 를 담당하는 인터페이스 추가 (#296)
seongjunnoh Sep 11, 2025
a5f53b2
[feat] 피드 관련 알림 DB 저장 + 이벤트 퍼블리시 를 담당하는 구현체 추가 (#296)
seongjunnoh Sep 12, 2025
1dd9ac3
[feat] 모임방 관련 알림 DB 저장 + 이벤트 퍼블리시 를 담당하는 구현체 추가 (#296)
seongjunnoh Sep 12, 2025
fb61586
[feat] 알림센터에 저장할 title, content 의 생성을 담당하는 template 인터페이스 추가 (#296)
seongjunnoh Sep 12, 2025
4d1d316
[feat] 피드 관련 알림의 title, content 를 생성하는 template enum 객체 추가 (#296)
seongjunnoh Sep 12, 2025
cd10cdb
[feat] 모임방 관련 알림의 title, content 를 생성하는 template enum 객체 추가 (#296)
seongjunnoh Sep 12, 2025
ec18391
[feat] Notification 도메인 엔티티 내부에 정적 팩토리 메서드 추가 (#296)
seongjunnoh Sep 12, 2025
28d1bb0
[feat] notification DB save 관련 코드 추가 (#296)
seongjunnoh Sep 12, 2025
7a62726
[refactor] fcm 푸시알림을 위한 event dto 수정 (#296)
seongjunnoh Sep 12, 2025
47215b3
[refactor] fcm 푸시알림을 위한 이벤트 리스너 하위 코드 수정 (#296)
seongjunnoh Sep 12, 2025
9f28bd6
[refactor] 기존 이벤트 발생 관련 비즈니스 로직을 담당하는 service 코드 수정 (#296)
seongjunnoh Sep 12, 2025
3f0918e
[refactor] 기존 service 통합 테스트 코드에서 EventCommandPort 를 의존하는 코드들 수정 (#296)
seongjunnoh Sep 12, 2025
4b2d4ff
[refactor] 불필요한 teardown 메서드 삭제 (#296)
seongjunnoh Sep 12, 2025
4b34ea3
[refactor] 알림 센터 저장과 관련있는 기존 통합 테스트 코드들의 teardown 메서드 수정 (#296)
seongjunnoh Sep 12, 2025
90b8495
[rename] 기록 생성 api 통합 테스트 클래스 네이밍 수정 (#296)
seongjunnoh Sep 12, 2025
bb8d52b
[move] NotificationCategory enum의 패키지 위치 이동 (#296)
seongjunnoh Sep 12, 2025
597e8cf
[test] 피드 알림 헬퍼 서비스의 단위/통합 테스트 코드 추가 (#296)
seongjunnoh Sep 12, 2025
f5ceff7
[test] 모임방 알림 헬퍼 서비스의 단위/통합 테스트 코드 추가 (#296)
seongjunnoh Sep 12, 2025
48bacfb
[remove] dummy 파일 삭제 (#296)
seongjunnoh Sep 12, 2025
983abaf
[fix] FirebaseAdapter의 profile 설정 문법 오류 수정 (#296)
seongjunnoh Sep 12, 2025
05256bb
[refactor] SecurityConfig, JwtAuthenticationFilter 에서 모두 같은 whitelist…
seongjunnoh Sep 12, 2025
e57dc01
[refactor] 특정 책과 관련된 방 쿼리 필터링 조건을 정적으로 변경 (#289)
buzz0331 Sep 12, 2025
edc58dd
[fix] FIrebaseAdapter 프로필 조건 수정 (#289)
buzz0331 Sep 12, 2025
a41646a
[refactor] 방 도메인 내부 방 기간 유효성 검증 정적을 변경 (#289)
buzz0331 Sep 12, 2025
4b21c69
[refactor] 방 조회 쿼리 기간 필터링 조건 모두 정적으로 수정 (#289)
buzz0331 Sep 12, 2025
231c256
[refactor] 기간이 만료된 방 내부 게시물들은 조회만 가능하도록 예외처리 (#289)
buzz0331 Sep 12, 2025
1d8a507
[refactor] RoomStatus 도입에 따른 테스트 케이스 수정 (#289)
buzz0331 Sep 12, 2025
3f9abb9
[refactor] EventCommandPort 의 모든 메서드 시그니처에 title, content 파라미터 추가 (#296)
seongjunnoh Sep 12, 2025
b07e0a5
[refactor] EventCommandPort 구현체의 모든 메서드에 title, content 코드 추가 (#296)
seongjunnoh Sep 12, 2025
bed7c40
[feat] 공통 헬퍼 서비스 및 함수형 인터페이스 도입 (#296)
seongjunnoh Sep 12, 2025
64728a7
[refactor] 기존 NotificationOrchestrator 구현체가 헬퍼서비스를 호출함으로써 공통 로직을 수행하도…
seongjunnoh Sep 12, 2025
30bc7c9
[refactor] 기존 NotificationOrchestrator 구현체 단위 테스트 코드 수정 (#296)
seongjunnoh Sep 12, 2025
a2102fa
[refactor] 기존 NotificationOrchestrator 구현체 통합 테스트 코드 수정 (#296)
seongjunnoh Sep 12, 2025
a2297a1
[refactor] 투표하기, 투표삭제하기 api에서 방 만료 검증 (#289)
buzz0331 Sep 12, 2025
0414562
[fix] 투표하기 단위테스트에 RoomCommandPort Mock 추가 (#295)
buzz0331 Sep 12, 2025
a99e536
[fix] 투표하기 단위테스트에 Room 스텁 객체 추가 (#295)
buzz0331 Sep 12, 2025
af159e1
[feat] JwtAuthenticationFilter 화이트리스트 경로 매칭을 PathPattern 기반으로 개선 (#296)
seongjunnoh Sep 13, 2025
1f07fef
[refactor] RoomQueryDto에 roomStatus 추가 (#295)
buzz0331 Sep 13, 2025
a06af47
[refactor] 진행중인 방이 아닌경우의 에러코드 추가 (#295)
buzz0331 Sep 13, 2025
ce1956d
[refactor] 진행중인 방이 아닌 경우(모집 중인 방)의 예외처리 추가 (#295)
buzz0331 Sep 13, 2025
5e43d56
[docs] 에러메시지 스웨거 명세 추가 (#295)
buzz0331 Sep 13, 2025
ed6d55f
[merge] 머지 (#295)
buzz0331 Sep 13, 2025
d87aa32
[test] TestEntityFactory에서 방 생성시 기본 진행중인 방이 생성되도록 수정 (#295)
buzz0331 Sep 13, 2025
fbaef2e
[chore] 주석 제거 (#295)
buzz0331 Sep 13, 2025
f25d4f2
[refactor] validateRoom 메서드 추출 해제 (#295)
buzz0331 Sep 13, 2025
a4819f3
[refactor] RoomStatus not null assertion 추가 (#295)
buzz0331 Sep 13, 2025
4c68d4c
[refactor] 푸시 알림 이벤트 퍼블리시 중 예외가 발생하더라도 이를 외부로 던지지 않도록 try-catch 문 도입 …
seongjunnoh Sep 13, 2025
6850a50
[test] NotificationExecutor 단위 테스트 코드 작성 (#296)
seongjunnoh Sep 13, 2025
7a05757
develop merge
seongjunnoh Sep 14, 2025
aac37d0
Merge pull request #298 from THIP-TextHip/feat/#296-notification-api
seongjunnoh Sep 14, 2025
104aa6b
Merge pull request #300 from THIP-TextHip/feat/#295-apply-room-status
buzz0331 Sep 14, 2025
654cab7
[refactor] 테스트 환경 tearDown()에서 @ Transactional 어노테이션으로 수정 및 안쓰는 함수,의존…
hd0rable Sep 14, 2025
58d9d0f
[refactor] 3.jwt 토큰 유효기간 환경 변수 추출 (#297)
hd0rable Sep 14, 2025
604523b
[refactor] 1.만료된 방 상세조회 api추가
hd0rable Sep 14, 2025
23eb3d3
[refactor] 5. 최근생성된 모임방 추가 및 모집 마감 입박한 방/ 인기 방/ 최근 방 생성된 방 --> 모집중인방/…
hd0rable Sep 14, 2025
22e5a80
[refactor] 6.모임방 검색시에 전체방 보여주는 파라미터 추가
hd0rable Sep 14, 2025
948285f
[refactor] 7.모임방 블라인드 관련 - 글자 수 및 공백(띄어쓰기)을 원본 내용과 동일하게 치환
hd0rable Sep 14, 2025
3c89c83
[refactor] 관련에러코드 추가
hd0rable Sep 14, 2025
6eeed8a
[refactor] 테스트 환경 tearDown()에서 @ Transactional 어노테이션으로 수정 및 안쓰는 함수,의존…
hd0rable Sep 14, 2025
cf038d1
[refactor] 테스트코드 수정 (#302)
hd0rable Sep 14, 2025
ca8feae
[refactor] 리뷰반영 수정 (#302)
hd0rable Sep 15, 2025
4a376c8
[chore] notifications 테이블 구조 수정 with flyway (#303)
seongjunnoh Sep 16, 2025
8b8085e
[refactor] notification 저장 시, Template 으로부터 notification category 값 받…
seongjunnoh Sep 16, 2025
474a23a
[refactor] NotificationTemplate 구현체 코드 수정 (#303)
seongjunnoh Sep 16, 2025
851d29f
[refactor] NotificationMapper 수정 (#303)
seongjunnoh Sep 16, 2025
2254190
[refactor] 기존 notification 관련 테스트 수정 (#303)
seongjunnoh Sep 16, 2025
c987296
[refactor] 리뷰반영 수정 (#302)
hd0rable Sep 16, 2025
0597611
[refactor] 공백일 경우(전체검색) 최근검색어 추가 x (#302)
hd0rable Sep 16, 2025
73d9504
Merge pull request #304 from THIP-TextHip/refactor/#302-room-improve
hd0rable Sep 16, 2025
ebc410d
[refactor] 모임홈화면에서 response 스웨거값 명시적으로 수정 (#302)
hd0rable Sep 16, 2025
f616f09
[move] notification 패키지 내의 fcm 토큰 관련 java 파일들 패키징 처리 (#303)
seongjunnoh Sep 16, 2025
16e5744
[feat] 알림센터 조회 api controller 구현 (#303)
seongjunnoh Sep 16, 2025
dcf9d6f
[feat] 알림센터 조회 api use case 구현 (#303)
seongjunnoh Sep 16, 2025
981bf81
[feat] 알림센터 조회 api 영속성 어댑터 구현 (#303)
seongjunnoh Sep 16, 2025
450b5e3
[feat] 알림센터 조회 api query dsl 조회 로직 구현 (#303)
seongjunnoh Sep 16, 2025
9f3d389
[feat] 알림센터 조회 관련 error code 추가 (#303)
seongjunnoh Sep 16, 2025
63b93e8
[feat] NotificationQueryMapper 추가 (#303)
seongjunnoh Sep 16, 2025
c53b2bc
[test] 알림센터 조회 api 통합 테스트 (#303)
seongjunnoh Sep 16, 2025
d76c0b2
[refactor] 내 모임방 목록 조회 api use case 의 메서드 시그니처 수정 (#303)
seongjunnoh Sep 16, 2025
dbc53e9
Merge remote-tracking branch 'origin' into feat/#303-notification-get
seongjunnoh Sep 16, 2025
20fc36b
[refactor] 알림센터 조회 api userId request param 를 swagger hidden 으로 설정 (#…
seongjunnoh Sep 17, 2025
a688492
[refactor] NotificationQueryRepositoryImpl 내부에 Q클래스 선언 중복 제거 (#303)
seongjunnoh Sep 17, 2025
a7ed4ad
Merge pull request #305 from THIP-TextHip/feat/#303-notification-get
seongjunnoh Sep 18, 2025
80bea58
[refactor] argument_resover 패키지 -> resolver로 네이밍 수정 (#303)
buzz0331 Sep 18, 2025
2e3b64e
[refactor] redirect_url 쿼리 파라미터 리졸버 구현 (#303)
buzz0331 Sep 18, 2025
9d428ff
[refactor] 허용 도메인 properties 설정 파일 선언 (#303)
buzz0331 Sep 18, 2025
fe121b3
[refactor] Security 설정에서 소셜 로그인 시 resolver 추가 (#303)
buzz0331 Sep 18, 2025
c5b31fc
[refactor] 소셜 로그인 성공시 세션 레포지토리에 저장해둔 redirect_url 복원 (#303)
buzz0331 Sep 18, 2025
3f96981
[refactor] LoginTokenStorage를 메모리 기반에서 Redis 기반으로 수정 (#303)
buzz0331 Sep 18, 2025
7accbaf
[refactor] Setter 제거 후 불변 리스트로 수정 (#303)
buzz0331 Sep 18, 2025
ef217a4
[refactor] Slf4j 어노테이션 제거 (#303)
buzz0331 Sep 18, 2025
ebbbf09
[refactor] 닉네임 중복검증 탈퇴한 유저의 닉네임은 사용가능하도록 수정
hd0rable Sep 20, 2025
d300c60
[refactor] 쿼리파라미터 미전달시 세션 제거 (#303)
buzz0331 Sep 20, 2025
4c263aa
[merge] merge(#303)
buzz0331 Sep 20, 2025
a780168
[refactor] 닉네임 중복검증 탈퇴한 유저의 닉네임은 사용가능하도록 수정 --> 관련테스트코드 수정
hd0rable Sep 20, 2025
2052fcf
[refactor] entrypoint에서 넘어오는 예상치 못한 에러를 AuthException으로 감싸서 handler에 …
buzz0331 Sep 20, 2025
d96c12e
Merge pull request #307 from THIP-TextHip/refactor/#303-oauth-redirect
buzz0331 Sep 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;

@Service
@RequiredArgsConstructor
public class BookRecruitingRoomsService implements BookRecruitingRoomsUseCase {
Expand All @@ -26,7 +24,7 @@ public class BookRecruitingRoomsService implements BookRecruitingRoomsUseCase {
@Transactional(readOnly = true)
public BookRecruitingRoomsResponse getRecruitingRoomsWithBook(String isbn, String cursorStr) {
Integer totalRoomCount = (cursorStr == null || cursorStr.isBlank()) ? // 첫 요청 여부 판단
roomQueryPort.countRecruitingRoomsByBookAndStartDateAfter(isbn, LocalDate.now()) : null;
roomQueryPort.countRecruitingRoomsByBookIsbn(isbn) : null;

Cursor cursor = Cursor.from(cursorStr, DEFAULT_PAGE_SIZE);
CursorBasedList<RoomQueryDto> roomDtos = roomQueryPort.findRoomsByIsbnOrderByDeadline(isbn, cursor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,8 @@ public BookDetailSearchResult searchDetailBooks(String isbn,Long userId) {
}

private int getRecruitingRoomCount(Book book) {
//오늘 날짜 기준으로 방 활동 시작 기간이 이후인 방 찾기(모집중인 방)
LocalDate today = LocalDate.now();
return roomQueryPort.countRecruitingRoomsByBookAndStartDateAfter(book.getIsbn(), today);
// 모집 중인 방 개수
return roomQueryPort.countRecruitingRoomsByBookIsbn(book.getIsbn());
}

private int getReadCount(Book book) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
import konkuk.thip.comment.application.service.validator.CommentAuthorizationValidator;
import konkuk.thip.comment.domain.Comment;
import konkuk.thip.common.exception.InvalidStateException;
import konkuk.thip.message.application.port.out.FeedEventCommandPort;
import konkuk.thip.message.application.port.out.RoomEventCommandPort;
import konkuk.thip.notification.application.port.in.FeedNotificationOrchestrator;
import konkuk.thip.notification.application.port.in.RoomNotificationOrchestrator;
import konkuk.thip.post.application.port.out.dto.PostQueryDto;
import konkuk.thip.post.domain.CountUpdatable;
import konkuk.thip.post.application.service.handler.PostHandler;
Expand All @@ -26,7 +26,6 @@
import static konkuk.thip.common.exception.code.ErrorCode.INVALID_COMMENT_CREATE;
import static konkuk.thip.post.domain.PostType.*;


@Service
@RequiredArgsConstructor
public class CommentCreateService implements CommentCreateUseCase {
Expand All @@ -40,8 +39,8 @@ public class CommentCreateService implements CommentCreateUseCase {
private final PostHandler postHandler;
private final CommentAuthorizationValidator commentAuthorizationValidator;

private final FeedEventCommandPort feedEventCommandPort;
private final RoomEventCommandPort roomEventCommandPort;
private final FeedNotificationOrchestrator feedNotificationOrchestrator;
private final RoomNotificationOrchestrator roomNotificationOrchestrator;

@Override
@Transactional
Expand Down Expand Up @@ -96,10 +95,10 @@ private void sendNotificationsToPostWriter(PostQueryDto postQueryDto, User actor

if (postQueryDto.postType().equals(FEED.getType())) {
// 피드 댓글 알림 이벤트 발행
feedEventCommandPort.publishFeedCommentedEvent(postQueryDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.postId());
feedNotificationOrchestrator.notifyFeedCommented(postQueryDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.postId());
} else if (postQueryDto.postType().equals(RECORD.getType()) || postQueryDto.postType().equals(VOTE.getType())) {
// 모임방 게시글 댓글 알림 이벤트 발행
roomEventCommandPort.publishRoomPostCommentedEvent(postQueryDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postQueryDto.postType());
roomNotificationOrchestrator.notifyRoomPostCommented(postQueryDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postQueryDto.postType());
}
}

Expand All @@ -108,10 +107,10 @@ private void sendNotificationsToParentCommentWriter(PostQueryDto postQueryDto, C

if (postQueryDto.postType().equals(FEED.getType())) {
// 피드 답글 알림 이벤트 발행
feedEventCommandPort.publishFeedRepliedEvent(parentCommentDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.postId());
feedNotificationOrchestrator.notifyFeedReplied(parentCommentDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.postId());
} else if (postQueryDto.postType().equals(RECORD.getType()) || postQueryDto.postType().equals(VOTE.getType())) {
// 모임방 게시글 답글 알림 이벤트 발행
roomEventCommandPort.publishRoomPostCommentRepliedEvent(parentCommentDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postQueryDto.postType());
roomNotificationOrchestrator.notifyRoomPostCommentReplied(parentCommentDto.creatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postQueryDto.postType());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import konkuk.thip.comment.application.port.out.CommentLikeQueryPort;
import konkuk.thip.comment.application.service.validator.CommentAuthorizationValidator;
import konkuk.thip.comment.domain.Comment;
import konkuk.thip.message.application.port.out.FeedEventCommandPort;
import konkuk.thip.message.application.port.out.RoomEventCommandPort;
import konkuk.thip.notification.application.port.in.FeedNotificationOrchestrator;
import konkuk.thip.notification.application.port.in.RoomNotificationOrchestrator;
import konkuk.thip.post.application.port.out.dto.PostQueryDto;
import konkuk.thip.post.application.service.handler.PostHandler;
import konkuk.thip.post.domain.CountUpdatable;
Expand All @@ -32,8 +32,8 @@ public class CommentLikeService implements CommentLikeUseCase {
private final PostHandler postHandler;
private final CommentAuthorizationValidator commentAuthorizationValidator;

private final FeedEventCommandPort feedEventCommandPort;
private final RoomEventCommandPort roomEventCommandPort;
private final FeedNotificationOrchestrator feedNotificationOrchestrator;
private final RoomNotificationOrchestrator roomNotificationOrchestrator;

@Override
@Transactional
Expand Down Expand Up @@ -73,11 +73,11 @@ private void sendNotifications(CommentIsLikeCommand command, Comment comment) {
User actorUser = userCommandPort.findById(command.userId());
// 좋아요 푸쉬알림 전송
if (comment.getPostType() == PostType.FEED) {
feedEventCommandPort.publishFeedCommentLikedEvent(comment.getCreatorId(), actorUser.getId(), actorUser.getNickname(), comment.getTargetPostId());
feedNotificationOrchestrator.notifyFeedCommentLiked(comment.getCreatorId(), actorUser.getId(), actorUser.getNickname(), comment.getTargetPostId());
}
if (comment.getPostType() == PostType.RECORD || comment.getPostType() == PostType.VOTE) {
PostQueryDto postQueryDto = postHandler.getPostQueryDto(comment.getPostType(), comment.getTargetPostId());
roomEventCommandPort.publishRoomCommentLikedEvent(comment.getCreatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postQueryDto.postType());
roomNotificationOrchestrator.notifyRoomCommentLiked(comment.getCreatorId(), actorUser.getId(), actorUser.getNickname(), postQueryDto.roomId(), postQueryDto.page(), postQueryDto.postId(), postQueryDto.postType());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import konkuk.thip.post.domain.CountUpdatable;
import konkuk.thip.room.application.service.validator.RoomParticipantValidator;
import konkuk.thip.room.application.service.validator.RoomValidator;
import konkuk.thip.roompost.domain.RoomPost;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
Expand All @@ -11,11 +12,13 @@
public class RoomPostCommentAccessPolicy implements CommentAccessPolicy {

private final RoomParticipantValidator roomParticipantValidator;
private final RoomValidator roomValidator;

@Override
public void validateCommentAccess(CountUpdatable post, Long userId) {
RoomPost roomPost = (RoomPost) post;
roomParticipantValidator.validateUserIsRoomMember(roomPost.getRoomId(), userId);
roomValidator.validateRoomInProgress(roomPost.getRoomId());
}

}
13 changes: 11 additions & 2 deletions src/main/java/konkuk/thip/common/exception/code/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ public enum ErrorCode implements ResponseCode {
AUTH_UNSUPPORTED_SOCIAL_LOGIN(HttpStatus.UNAUTHORIZED, 40105, "지원하지 않는 소셜 로그인입니다."),
AUTH_INVALID_LOGIN_TOKEN_KEY(HttpStatus.UNAUTHORIZED, 40106, "유효하지 않은 로그인 토큰 키입니다."),
AUTH_BLACKLIST_TOKEN(HttpStatus.UNAUTHORIZED, 40107, "블랙리스트에 등록된 토큰입니다."),
AUTH_INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 40108, "인증 처리 중 서버 오류가 발생했습니다."),

JSON_PROCESSING_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 50100, "JSON 직렬화/역직렬화에 실패했습니다."),
AWS_BUCKET_BASE_URL_NOT_CONFIGURED(HttpStatus.INTERNAL_SERVER_ERROR, 50101, "aws s3 bucket base url 설정이 누락되었습니다."),
WEB_DOMAIN_ORIGIN_EMPTY(HttpStatus.INTERNAL_SERVER_ERROR, 50102, "허용된 웹 도메인 설정이 비어있습니다."),

PERSISTENCE_TRANSACTION_REQUIRED(HttpStatus.INTERNAL_SERVER_ERROR, 50110, "@Transactional 컨텍스트가 필요합니다. 트랜잭션 범위 내에서만 사용할 수 있습니다."),

Expand Down Expand Up @@ -99,8 +101,9 @@ public enum ErrorCode implements ResponseCode {
INVALID_ROOM_SEARCH_SORT(HttpStatus.BAD_REQUEST, 100005, "방 검색 시 정렬 조건이 잘못되었습니다."),
ROOM_MEMBER_COUNT_EXCEEDED(HttpStatus.BAD_REQUEST, 100006, "방의 최대 인원 수를 초과했습니다."),
ROOM_MEMBER_COUNT_UNDERFLOW(HttpStatus.BAD_REQUEST, 100007, "방의 인원 수가 1 이하(방장 포함)입니다."),
ROOM_IS_EXPIRED(HttpStatus.BAD_REQUEST, 100008, "방이 만료되었습니다."),
ROOM_IS_EXPIRED(HttpStatus.BAD_REQUEST, 100008, " 완료된 모임방에서는 기존 기록에 대한 조회만 가능해요."),
ROOM_POST_TYPE_NOT_MATCH(HttpStatus.BAD_REQUEST, 100009, "일치하는 방 게시물 타입 이름이 없습니다. [RECORD, VOTE] 중 하나여야 합니다."),
ROOM_NOT_IN_PROGRESS(HttpStatus.BAD_REQUEST, 100010, "진행 중인 방이 아닙니다."),

/**
* 110000 : vote error
Expand Down Expand Up @@ -221,7 +224,13 @@ public enum ErrorCode implements ResponseCode {
FCM_TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, 200000, "존재하지 않는 FCM TOKEN 입니다."),
FCM_TOKEN_ENABLED_STATE_ALREADY(HttpStatus.BAD_REQUEST, 200001, "요청한 상태로 이미 푸쉬 알림 여부가 설정되어 있습니다."),
FCM_TOKEN_ACCESS_FORBIDDEN(HttpStatus.FORBIDDEN, 200002, "토큰을 소유하고 있는 계정이 아닙니다."),
FIREBASE_SEND_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 200003, "FCM 푸쉬 알림 전송에 실패했습니다.")
FIREBASE_SEND_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 200003, "FCM 푸쉬 알림 전송에 실패했습니다."),


/**
* 205000 : notification error
*/
INVALID_NOTIFICATION_TYPE(HttpStatus.BAD_REQUEST, 205000, "유효하지 않은 알림 타입입니다."),

;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public enum AuthParameters {
COOKIE_ACCESS_TOKEN("access_token"),
COOKIE_TEMP_TOKEN("temp_token"),

REDIRECT_URL_KEY("redirect_url"),
REDIRECT_SESSION_KEY("oauth2_return_to"),

;

private final String value;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package konkuk.thip.common.security.constant;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

import java.util.Arrays;
import java.util.List;

@RequiredArgsConstructor
@Getter
public enum SecurityWhitelist {

SWAGGER_UI("/swagger-ui/**"),
API_DOCS("/api-docs/**"),
SWAGGER_UI_HTML("/swagger-ui.html"),
V3_API_DOCS("/v3/api-docs/**"),
OAUTH2_AUTHORIZATION("/oauth2/authorization/**"),
LOGIN_OAUTH2_CODE("/login/oauth2/code/**"),
ACTUATOR_HEALTH("/actuator/health"),
AUTH_USERS("/auth/users"),
AUTH_TOKEN("/auth/token"),
API_TEST("/api/test/**"),
AUTH_EXCHANGE_TEMP_TOKEN("/auth/exchange-temp-token"),
AUTH_SET_COOKIE("/auth/set-cookie");

private final String pattern;

// SecurityConfig 용 전체 리스트
public static String[] patterns() {
return Arrays.stream(values())
.map(SecurityWhitelist::getPattern)
.toArray(String[]::new);
}

// JwtAuthenticationFilter.shouldNotFilter() 용 편의 메서드
public static List<String> patternsList() {
return Arrays.asList(patterns());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import konkuk.thip.common.exception.AuthException;
import konkuk.thip.common.exception.code.ErrorCode;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
Expand All @@ -15,16 +17,32 @@
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
private final HandlerExceptionResolver resolver;

public JwtAuthenticationEntryPoint(@Qualifier("handlerExceptionResolver") HandlerExceptionResolver resolver){
public JwtAuthenticationEntryPoint(@Qualifier("handlerExceptionResolver")
HandlerExceptionResolver resolver) {
this.resolver = resolver;
}

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
Exception e = (Exception) request.getAttribute("exception");
if(e == null){
e = authException;
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {

// 필터에서 set한 예외 우선
Exception original = (Exception) request.getAttribute("exception");
if (original == null) {
original = authException;
}

Exception mapped = wrapAsAuthException(original);

resolver.resolveException(request, response, null, mapped);
}

// 모든 예외를 AuthException(401)으로 감싸는 메서드
private Exception wrapAsAuthException(Exception e) {
if (e instanceof AuthException) {
return e;
}
resolver.resolveException(request, response, null, e);
return new AuthException(ErrorCode.AUTH_INTERNAL_SERVER_ERROR, e);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
package konkuk.thip.common.security.filter;

import jakarta.annotation.PostConstruct;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import konkuk.thip.common.exception.AuthException;
import konkuk.thip.common.security.constant.SecurityWhitelist;
import konkuk.thip.common.security.oauth2.CustomOAuth2User;
import konkuk.thip.common.security.oauth2.LoginUser;
import konkuk.thip.common.security.util.JwtUtil;
import konkuk.thip.user.application.port.UserTokenBlacklistQueryPort;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.PathContainer;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.pattern.PathPattern;
import org.springframework.web.util.pattern.PathPatternParser;

import java.io.IOException;
import java.util.List;

import static konkuk.thip.common.exception.code.ErrorCode.*;
import static konkuk.thip.common.security.constant.AuthParameters.*;
Expand All @@ -30,6 +36,31 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserTokenBlacklistQueryPort userTokenBlacklistQueryPort;

private List<PathPattern> whitelistPatterns;
private final PathPatternParser pathPatternParser = new PathPatternParser();

@PostConstruct
void initWhitelistPatterns() {
this.whitelistPatterns = SecurityWhitelist.patternsList().stream()
.map(pathPatternParser::parse) // 애플리케이션 시작 시 1회 컴파일
.toList();
}

@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
// 컨텍스트 패스 고려한 실제 경로(= path) 추출
String requestUri = request.getRequestURI();
String contextPath = request.getContextPath();
String path = (contextPath != null && !contextPath.isEmpty() && requestUri.startsWith(contextPath))
? requestUri.substring(contextPath.length())
: requestUri;

PathContainer container = PathContainer.parsePath(path);

// PathPattern으로 세그먼트 기반 매칭
return whitelistPatterns.stream().anyMatch(p -> p.matches(container));
}

@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
Expand Down Expand Up @@ -92,24 +123,4 @@ private String extractToken(HttpServletRequest request) {
log.info("토큰이 없습니다.");
return null;
}

@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getRequestURI();

// 화이트리스트 경로에 대해서는 JWT 필터 제외
return path.startsWith("/swagger-ui")
|| path.startsWith("/v3/api-docs")
|| path.startsWith("/api-docs")
|| path.startsWith("/actuator/health")
|| path.startsWith("/oauth2/authorization")
|| path.startsWith("/login/oauth2/code")
|| path.startsWith("/auth/users")
|| path.equals("/auth/token")

// || path.equals("/auth/set-cookie")
// || path.equals("/auth/exchange-temp-token")
;
}

}
Loading