Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e508157
refactor: 스더티룸 권한에 대한 로직 개선
loseminho Oct 2, 2025
8b766f3
Merge remote-tracking branch 'origin/dev' into refactor/146
loseminho Oct 2, 2025
ef46ed0
fix: ci에서 통과 못한 테스트코드 수정
loseminho Oct 3, 2025
23e55ea
fix:rest api와 웹소켓 중간 경로 통합
loseminho Oct 4, 2025
2979e6e
fix:rest api와 웹소켓 중간 경로 통합
loseminho Oct 4, 2025
2de8631
fix: 병합 충돌 제어
loseminho Oct 4, 2025
e576231
Merge remote-tracking branch 'origin/dev' into refactor/146
loseminho Oct 5, 2025
98c9e4c
fix: 에러 확인을 위한 통합테스트 추가, Room.create()메서드 수정
loseminho Oct 5, 2025
8cb4561
refactor, feat
loseminho Oct 5, 2025
be970fd
Merge remote-tracking branch 'origin/dev' into refactor/146
loseminho Oct 5, 2025
57c38ae
refactor: redis 로직 최적화 및 중복 검증 로직 제거
loseminho Oct 7, 2025
ad752e7
fix : 병합 충돌 제어
loseminho Oct 7, 2025
1af4d1e
fix: 에러 번호 수정
loseminho Oct 8, 2025
483a0fc
feat: 스터디룸 방 비밀번호 변경 및 삭제 기능 구현
loseminho Oct 9, 2025
89971f2
Merge remote-tracking branch 'origin/dev' into feat-215
loseminho Oct 9, 2025
77ab976
fix:app-dev 제거
loseminho Oct 9, 2025
7a17167
feat: 웹소켓 기반 소극적 하트비트
loseminho Oct 9, 2025
dd229f2
Merge remote-tracking branch 'origin/dev' into feat/217
loseminho Oct 10, 2025
cf32230
feat: 스터디룸 썸네일 기능 추가 및 webrtc 설정 변경에서 주석처리
loseminho Oct 10, 2025
be00c11
Merge branch 'feat/217' of https://github.com/prgrms-web-devcourse-fi…
loseminho Oct 10, 2025
da453f6
fix:소극적 하트비트 사용 주석처리
loseminho Oct 10, 2025
70813b6
Merge remote-tracking branch 'origin/dev' into feat/217
loseminho Oct 11, 2025
2de5447
Feat: 스터디 룸 내에 고양이 아바타 시스템과 프로필 이미지 url 연동
loseminho Oct 11, 2025
2bc4e95
Merge remote-tracking branch 'origin/dev' into feat/217
loseminho Oct 11, 2025
7d4237c
fix: 기존 작성되어있던 test 코드 수정
loseminho Oct 11, 2025
ad988c8
Merge remote-tracking branch 'origin/dev' into feat/217
loseminho Oct 12, 2025
030cbc8
Merge remote-tracking branch 'origin/dev' into feat/217
loseminho Oct 12, 2025
08a976f
test: 아바타 테스트 코드 완료
loseminho Oct 12, 2025
8761b03
refactor: 프론트엔드 요청 사항에 따른 스터디룸 조회 마스킹 제거
loseminho Oct 13, 2025
4a770d6
Merge remote-tracking branch 'origin/dev' into refactor/257
loseminho Oct 13, 2025
c690605
Merge branch 'dev' into refactor/257
loseminho Oct 13, 2025
810f427
Merge branch 'dev' into refactor/257
loseminho Oct 13, 2025
ce98b9f
Merge branch 'dev' into refactor/257
loseminho Oct 13, 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 @@ -130,11 +130,10 @@ public ResponseEntity<RsData<Void>> leaveRoom(
@GetMapping("/all")
@Operation(
summary = "모든 방 목록 조회",
description = "공개 방과 비공개 방 전체를 조회합니다. 비공개 방은 제목과 방장 정보가 마스킹됩니다. 열린 방(WAITING, ACTIVE)이 우선 표시되고, 닫힌 방(PAUSED, TERMINATED)은 뒤로 밀립니다."
description = "공개 방과 비공개 방 전체를 조회합니다. 열린 방(WAITING, ACTIVE)이 우선 표시되고, 닫힌 방(PAUSED, TERMINATED)은 뒤로 밀립니다. 비로그인 사용자도 조회 가능합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "401", description = "인증 실패")
@ApiResponse(responseCode = "200", description = "조회 성공")
})
public ResponseEntity<RsData<Map<String, Object>>> getAllRooms(
@Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page,
Expand All @@ -143,8 +142,8 @@ public ResponseEntity<RsData<Map<String, Object>>> getAllRooms(
Pageable pageable = PageRequest.of(page, size);
Page<Room> rooms = roomService.getAllRooms(pageable);

// 비공개 방 마스킹 포함한 변환
List<RoomResponse> roomList = roomService.toRoomResponseListWithMasking(rooms.getContent());
// 모든 정보 공개
List<RoomResponse> roomList = roomService.toRoomResponseList(rooms.getContent());

Map<String, Object> response = new HashMap<>();
response.put("rooms", roomList);
Expand All @@ -162,11 +161,10 @@ public ResponseEntity<RsData<Map<String, Object>>> getAllRooms(
@GetMapping("/public")
@Operation(
summary = "공개 방 목록 조회",
description = "공개 방 전체를 조회합니다. includeInactive=true로 설정하면 닫힌 방도 포함됩니다 (기본값: true). 열린 방이 우선 표시됩니다."
description = "공개 방 전체를 조회합니다. includeInactive=true로 설정하면 닫힌 방도 포함됩니다 (기본값: true). 열린 방이 우선 표시됩니다. 비로그인 사용자도 조회 가능합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "401", description = "인증 실패")
@ApiResponse(responseCode = "200", description = "조회 성공")
})
public ResponseEntity<RsData<Map<String, Object>>> getPublicRooms(
@Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page,
Expand Down Expand Up @@ -261,11 +259,10 @@ public ResponseEntity<RsData<Map<String, Object>>> getMyHostingRooms(
@GetMapping
@Operation(
summary = "입장 가능한 공개 방 목록 조회 (기존)",
description = "입장 가능한 공개 스터디 룸 목록을 페이징하여 조회합니다. 최신 생성 순으로 정렬됩니다."
description = "입장 가능한 공개 스터디 룸 목록을 페이징하여 조회합니다. 최신 생성 순으로 정렬됩니다. 비로그인 사용자도 조회 가능합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "401", description = "인증 실패")
@ApiResponse(responseCode = "200", description = "조회 성공")
})
public ResponseEntity<RsData<Map<String, Object>>> getRooms(
@Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page,
Expand All @@ -292,18 +289,17 @@ public ResponseEntity<RsData<Map<String, Object>>> getRooms(
@GetMapping("/{roomId}")
@Operation(
summary = "방 상세 정보 조회",
description = "특정 방의 상세 정보와 현재 온라인 멤버 목록을 조회합니다. 비공개 방은 멤버만 조회 가능합니다."
description = "특정 방의 상세 정보와 현재 온라인 멤버 목록을 조회합니다. 비로그인 사용자도 조회 가능합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "403", description = "비공개 방에 대한 접근 권한 없음"),
@ApiResponse(responseCode = "404", description = "존재하지 않는 방"),
@ApiResponse(responseCode = "401", description = "인증 실패")
@ApiResponse(responseCode = "404", description = "존재하지 않는 방")
})
public ResponseEntity<RsData<RoomDetailResponse>> getRoomDetail(
@Parameter(description = "방 ID", required = true) @PathVariable Long roomId) {

Long currentUserId = currentUser.getUserId();
// 비로그인 사용자는 userId = null로 처리
Long currentUserId = currentUser.getUserIdOrNull();

Room room = roomService.getRoomDetail(roomId, currentUserId);
List<RoomMember> members = roomService.getRoomMembers(roomId, currentUserId);
Expand Down Expand Up @@ -522,11 +518,10 @@ public ResponseEntity<RsData<List<RoomMemberResponse>>> getRoomMembers(
@GetMapping("/popular")
@Operation(
summary = "인기 방 목록 조회",
description = "참가자 수가 많은 인기 방 목록을 페이징하여 조회합니다. 참가자 수 내림차순에서 최신순으로 정렬됩니다."
description = "참가자 수가 많은 인기 방 목록을 페이징하여 조회합니다. 공개방과 비공개방 모두 포함됩니다. 참가자 수 내림차순에서 최신순으로 정렬됩니다. 비로그인 사용자도 조회 가능합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "401", description = "인증 실패")
@ApiResponse(responseCode = "200", description = "조회 성공")
})
public ResponseEntity<RsData<Map<String, Object>>> getPopularRooms(
@Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,13 +157,13 @@ public Page<Room> findRoomsWithFilters(String title, RoomStatus status, Boolean
}

/**
* 인기 방 조회 (참가자 수 기준)
* 인기 방 조회 (참가자 수 기준) - 공개+비공개 포함
*
* 참고: 참가자 수는 Redis에서 조회하므로 DB에서는 정렬 불가
* 서비스 레이어에서 Redis 데이터로 정렬 필요
*
* 조회 조건:
* - 공개 방만 (isPrivate = false)
* - 공개 방 + 비공개 방 모두 포함
* - 활성화된 방만 (isActive = true)
* @param pageable 페이징 정보
* @return 페이징된 방 목록 (최신순 정렬)
Expand All @@ -174,7 +174,6 @@ public Page<Room> findPopularRooms(Pageable pageable) {
.selectFrom(room)
.leftJoin(room.createdBy, user).fetchJoin() // N+1 방지
.where(
room.isPrivate.eq(false),
room.isActive.eq(true)
)
.orderBy(room.createdAt.desc()) // 최신순 (서비스에서 Redis 기반으로 재정렬)
Expand All @@ -187,7 +186,6 @@ public Page<Room> findPopularRooms(Pageable pageable) {
.select(room.count())
.from(room)
.where(
room.isPrivate.eq(false),
room.isActive.eq(true)
)
.fetchOne();
Expand Down
15 changes: 3 additions & 12 deletions src/main/java/com/back/domain/studyroom/service/RoomService.java
Original file line number Diff line number Diff line change
Expand Up @@ -257,12 +257,8 @@ public Room getRoomDetail(Long roomId, Long userId) {
Room room = roomRepository.findById(roomId)
.orElseThrow(() -> new CustomException(ErrorCode.ROOM_NOT_FOUND));

if (room.isPrivate()) {
boolean isMember = roomMemberRepository.existsByRoomIdAndUserId(roomId, userId);
if (!isMember) {
throw new CustomException(ErrorCode.ROOM_FORBIDDEN);
}
}
// ⭐ 비공개 방 접근 제한 제거 - 모든 사용자가 조회 가능
// (프론트엔드에서 입장 시 로그인 체크)

return room;
}
Expand Down Expand Up @@ -534,12 +530,7 @@ public List<RoomMember> getRoomMembers(Long roomId, Long userId) {
Room room = roomRepository.findById(roomId)
.orElseThrow(() -> new CustomException(ErrorCode.ROOM_NOT_FOUND));

if (room.isPrivate()) {
boolean isMember = roomMemberRepository.existsByRoomIdAndUserId(roomId, userId);
if (!isMember) {
throw new CustomException(ErrorCode.ROOM_FORBIDDEN);
}
}
// ⭐ 비공개 방 접근 제한 제거 - 모든 사용자가 조회 가능

// 1. Redis에서 온라인 사용자 ID 조회
Set<Long> onlineUserIds = roomParticipantService.getParticipants(roomId);
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/com/back/global/security/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.requestMatchers("api/ws/**", "/ws/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/posts/**").permitAll()
.requestMatchers("/api/rooms/*/messages/**").permitAll() //스터디 룸 내에 잡혀있어 있는 채팅 관련 전체 허용
// 방 목록 조회 API 비로그인 허용
.requestMatchers(HttpMethod.GET, "/api/rooms").permitAll()
.requestMatchers(HttpMethod.GET, "/api/rooms/all").permitAll()
.requestMatchers(HttpMethod.GET, "/api/rooms/public").permitAll()
.requestMatchers(HttpMethod.GET, "/api/rooms/popular").permitAll()
.requestMatchers(HttpMethod.GET, "/api/rooms/*").permitAll() // 방 상세 조회
//.requestMatchers("/api/rooms/RoomChatApiControllerTest").permitAll() // 테스트용 임시 허용
.requestMatchers("/","/swagger-ui/**", "/v3/api-docs/**").permitAll() // Swagger 허용
.requestMatchers("/h2-console/**").permitAll() // H2 Console 허용
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/com/back/global/security/user/CurrentUser.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,21 @@ public boolean isAuthenticated() {
}

public Long getUserId() { return getDetails().getUserId(); }

/**
* 현재 사용자 ID 조회 (비로그인 시 null 반환)
* 비로그인 접근이 허용되는 API에서 사용
*/
public Long getUserIdOrNull() {
try {
return getDetails().getUserId();
} catch (CustomException e) {
if (e.getErrorCode() == ErrorCode.UNAUTHORIZED) {
return null;
}
throw e;
}
}

public String getUsername() { return getDetails().getUsername(); }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ void getRooms() {
@DisplayName("방 상세 정보 조회 API 테스트 - JWT 인증")
void getRoomDetail() {
// given
given(currentUser.getUserId()).willReturn(1L);
given(currentUser.getUserIdOrNull()).willReturn(1L);

given(roomService.getRoomDetail(eq(1L), eq(1L))).willReturn(testRoom);
given(roomService.getRoomMembers(eq(1L), eq(1L))).willReturn(Arrays.asList(testMember));
Expand All @@ -229,7 +229,7 @@ void getRoomDetail() {
assertThat(response.getBody().isSuccess()).isTrue();
assertThat(response.getBody().getData().getTitle()).isEqualTo("테스트 방");

verify(currentUser, times(1)).getUserId();
verify(currentUser, times(1)).getUserIdOrNull();
verify(roomService, times(1)).getRoomDetail(eq(1L), eq(1L));
verify(roomService, times(1)).getRoomMembers(eq(1L), eq(1L));
verify(roomService, times(1)).toRoomDetailResponse(any(Room.class), anyList());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,8 @@ void getRoomDetail_Success() {
}

@Test
@DisplayName("방 상세 정보 조회 - 비공개 방 권한 없음")
void getRoomDetail_PrivateRoomForbidden() {
@DisplayName("방 상세 정보 조회 - 비공개 방도 조회 가능")
void getRoomDetail_PrivateRoomAllowed() {
// given
Room privateRoom = Room.create(
"비공개 방",
Expand All @@ -273,12 +273,16 @@ void getRoomDetail_PrivateRoomForbidden() {
null // thumbnailUrl
);
given(roomRepository.findById(1L)).willReturn(Optional.of(privateRoom));
given(roomMemberRepository.existsByRoomIdAndUserId(1L, 2L)).willReturn(false);
// ⭐ 비공개 방 접근 제한 제거되었으므로 권한 체크 안 함

// when & then
assertThatThrownBy(() -> roomService.getRoomDetail(1L, 2L))
.isInstanceOf(CustomException.class)
.hasFieldOrPropertyWithValue("errorCode", ErrorCode.ROOM_FORBIDDEN);
// when
Room result = roomService.getRoomDetail(1L, 2L);

// then
assertThat(result).isNotNull();
assertThat(result.getTitle()).isEqualTo("비공개 방");
assertThat(result.isPrivate()).isTrue();
verify(roomMemberRepository, never()).existsByRoomIdAndUserId(any(), any());
}

@Test
Expand Down