Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,140 @@ public ResponseEntity<RsData<Void>> leaveRoom(
.body(RsData.success("방 퇴장 완료", null));
}

@GetMapping
@GetMapping("/all")
@Operation(
summary = "모든 방 목록 조회",
description = "공개 방과 비공개 방 전체를 조회합니다. 비공개 방은 제목과 방장 정보가 마스킹됩니다. 열린 방(WAITING, ACTIVE)이 우선 표시되고, 닫힌 방(PAUSED, TERMINATED)은 뒤로 밀립니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
public ResponseEntity<RsData<Map<String, Object>>> getAllRooms(
@Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page,
@Parameter(description = "페이지 크기") @RequestParam(defaultValue = "20") int size) {

Pageable pageable = PageRequest.of(page, size);
Page<Room> rooms = roomService.getAllRooms(pageable);

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

Map<String, Object> response = new HashMap<>();
response.put("rooms", roomList);
response.put("page", rooms.getNumber());
response.put("size", rooms.getSize());
response.put("totalElements", rooms.getTotalElements());
response.put("totalPages", rooms.getTotalPages());
response.put("hasNext", rooms.hasNext());

return ResponseEntity
.status(HttpStatus.OK)
.body(RsData.success("모든 방 목록 조회 완료", response));
}

@GetMapping("/public")
@Operation(
summary = "공개 방 목록 조회",
description = "공개 방 전체를 조회합니다. includeInactive=true로 설정하면 닫힌 방도 포함됩니다 (기본값: true). 열린 방이 우선 표시됩니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
public ResponseEntity<RsData<Map<String, Object>>> getPublicRooms(
@Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page,
@Parameter(description = "페이지 크기") @RequestParam(defaultValue = "20") int size,
@Parameter(description = "닫힌 방 포함 여부") @RequestParam(defaultValue = "true") boolean includeInactive) {

Pageable pageable = PageRequest.of(page, size);
Page<Room> rooms = roomService.getPublicRooms(includeInactive, pageable);

List<RoomResponse> roomList = roomService.toRoomResponseList(rooms.getContent());

Map<String, Object> response = new HashMap<>();
response.put("rooms", roomList);
response.put("page", rooms.getNumber());
response.put("size", rooms.getSize());
response.put("totalElements", rooms.getTotalElements());
response.put("totalPages", rooms.getTotalPages());
response.put("hasNext", rooms.hasNext());

return ResponseEntity
.status(HttpStatus.OK)
.body(RsData.success("공개 방 목록 조회 완료", response));
}

@GetMapping("/private")
@Operation(
summary = "내 비공개 방 목록 조회",
description = "내가 멤버로 등록된 비공개 방을 조회합니다. includeInactive=true로 설정하면 닫힌 방도 포함됩니다 (기본값: true). 열린 방이 우선 표시됩니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
public ResponseEntity<RsData<Map<String, Object>>> getMyPrivateRooms(
@Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page,
@Parameter(description = "페이지 크기") @RequestParam(defaultValue = "20") int size,
@Parameter(description = "닫힌 방 포함 여부") @RequestParam(defaultValue = "true") boolean includeInactive) {

Long currentUserId = currentUser.getUserId();

Pageable pageable = PageRequest.of(page, size);
Page<Room> rooms = roomService.getMyPrivateRooms(currentUserId, includeInactive, pageable);

List<RoomResponse> roomList = roomService.toRoomResponseList(rooms.getContent());

Map<String, Object> response = new HashMap<>();
response.put("rooms", roomList);
response.put("page", rooms.getNumber());
response.put("size", rooms.getSize());
response.put("totalElements", rooms.getTotalElements());
response.put("totalPages", rooms.getTotalPages());
response.put("hasNext", rooms.hasNext());

return ResponseEntity
.status(HttpStatus.OK)
.body(RsData.success("내 비공개 방 목록 조회 완료", response));
}

@GetMapping("/my/hosting")
@Operation(
summary = "내가 호스트인 방 목록 조회",
description = "내가 방장으로 있는 방을 조회합니다. 열린 방이 우선 표시되고, 닫힌 방은 뒤로 밀립니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
public ResponseEntity<RsData<Map<String, Object>>> getMyHostingRooms(
@Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page,
@Parameter(description = "페이지 크기") @RequestParam(defaultValue = "20") int size) {

Long currentUserId = currentUser.getUserId();

Pageable pageable = PageRequest.of(page, size);
Page<Room> rooms = roomService.getMyHostingRooms(currentUserId, pageable);

List<RoomResponse> roomList = roomService.toRoomResponseList(rooms.getContent());

Map<String, Object> response = new HashMap<>();
response.put("rooms", roomList);
response.put("page", rooms.getNumber());
response.put("size", rooms.getSize());
response.put("totalElements", rooms.getTotalElements());
response.put("totalPages", rooms.getTotalPages());
response.put("hasNext", rooms.hasNext());

return ResponseEntity
.status(HttpStatus.OK)
.body(RsData.success("내가 호스트인 방 목록 조회 완료", response));
}

@GetMapping
@Operation(
summary = "입장 가능한 공개 방 목록 조회 (기존)",
description = "입장 가능한 공개 스터디 룸 목록을 페이징하여 조회합니다. 최신 생성 순으로 정렬됩니다."
)
@ApiResponses({
Expand Down Expand Up @@ -262,6 +393,54 @@ public ResponseEntity<RsData<Void>> deleteRoom(
.body(RsData.success("방 종료 완료", null));
}

@PutMapping("/{roomId}/pause")
@Operation(
summary = "방 일시정지",
description = "방을 일시정지 상태로 변경합니다. 일시정지된 방은 입장할 수 없으며, 방 목록에서 뒤로 밀립니다. 방장만 실행 가능합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "일시정지 성공"),
@ApiResponse(responseCode = "400", description = "이미 종료되었거나 일시정지 불가능한 상태"),
@ApiResponse(responseCode = "403", description = "방장 권한 없음"),
@ApiResponse(responseCode = "404", description = "존재하지 않는 방"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
public ResponseEntity<RsData<Void>> pauseRoom(
@Parameter(description = "방 ID", required = true) @PathVariable Long roomId) {

Long currentUserId = currentUser.getUserId();

roomService.pauseRoom(roomId, currentUserId);

return ResponseEntity
.status(HttpStatus.OK)
.body(RsData.success("방 일시정지 완료", null));
}

@PutMapping("/{roomId}/activate")
@Operation(
summary = "방 활성화/재개",
description = "일시정지된 방을 다시 활성화합니다. 활성화된 방은 다시 입장 가능하며, 방 목록 앞쪽에 표시됩니다. 방장만 실행 가능합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "활성화 성공"),
@ApiResponse(responseCode = "400", description = "이미 종료되었거나 활성화 불가능한 상태"),
@ApiResponse(responseCode = "403", description = "방장 권한 없음"),
@ApiResponse(responseCode = "404", description = "존재하지 않는 방"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
public ResponseEntity<RsData<Void>> activateRoom(
@Parameter(description = "방 ID", required = true) @PathVariable Long roomId) {

Long currentUserId = currentUser.getUserId();

roomService.activateRoom(roomId, currentUserId);

return ResponseEntity
.status(HttpStatus.OK)
.body(RsData.success("방 활성화 완료", null));
}

@GetMapping("/{roomId}/members")
@Operation(
summary = "방 멤버 목록 조회",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class MyRoomResponse {
private Long roomId;
private String title;
private String description;
private Boolean isPrivate; // 비공개 방 여부 (UI에서 🔒 아이콘 표시용)
private int currentParticipants;
private int maxParticipants;
private RoomStatus status;
Expand All @@ -25,6 +26,7 @@ public static MyRoomResponse of(Room room, long currentParticipants, RoomRole my
.roomId(room.getId())
.title(room.getTitle())
.description(room.getDescription() != null ? room.getDescription() : "")
.isPrivate(room.isPrivate()) // 비공개 방 여부
.currentParticipants((int) currentParticipants) // Redis에서 조회한 실시간 값
.maxParticipants(room.getMaxParticipants())
.status(room.getStatus())
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/com/back/domain/studyroom/dto/RoomResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class RoomResponse {
private Long roomId;
private String title;
private String description;
private Boolean isPrivate; // 비공개 방 여부 (UI에서 🔒 아이콘 표시용)
private int currentParticipants;
private int maxParticipants;
private RoomStatus status;
Expand All @@ -29,6 +30,7 @@ public static RoomResponse from(Room room, long currentParticipants) {
.roomId(room.getId())
.title(room.getTitle())
.description(room.getDescription() != null ? room.getDescription() : "")
.isPrivate(room.isPrivate()) // 비공개 방 여부
.currentParticipants((int) currentParticipants) // Redis에서 조회한 실시간 값
.maxParticipants(room.getMaxParticipants())
.status(room.getStatus())
Expand All @@ -39,4 +41,25 @@ public static RoomResponse from(Room room, long currentParticipants) {
.allowScreenShare(room.isAllowScreenShare())
.build();
}

/**
* 비공개 방 정보 마스킹 버전 (전체 목록에서 볼 때 사용)
* "모든 방" 조회 시 사용 - 비공개 방의 민감한 정보를 숨김
*/
public static RoomResponse fromMasked(Room room) {
return RoomResponse.builder()
.roomId(room.getId())
.title("🔒 비공개 방") // 제목 마스킹
.description("비공개 방입니다") // 설명 마스킹
.isPrivate(true)
.currentParticipants(0) // 참가자 수 숨김
.maxParticipants(0) // 정원 숨김
.status(room.getStatus())
.createdBy("익명") // 방장 정보 숨김
.createdAt(room.getCreatedAt())
.allowCamera(false) // RTC 정보 숨김
.allowAudio(false)
.allowScreenShare(false)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,41 @@ public interface RoomRepositoryCustom {
* 비관적 락으로 방 조회 (동시성 제어용)
*/
Optional<Room> findByIdWithLock(Long roomId);

/**
* 모든 방 조회 (공개 + 비공개 전체)
* 정렬: 열린 방(WAITING, ACTIVE) 우선 → 최신순
* 비공개 방은 정보 마스킹하여 반환
* @param pageable 페이징 정보
* @return 페이징된 방 목록
*/
Page<Room> findAllRooms(Pageable pageable);

/**
* 공개 방 전체 조회
* 정렬: 열린 방 우선 → 최신순
* @param includeInactive 닫힌 방(PAUSED, TERMINATED) 포함 여부
* @param pageable 페이징 정보
* @return 페이징된 공개 방 목록
*/
Page<Room> findPublicRoomsWithStatus(boolean includeInactive, Pageable pageable);

/**
* 내가 멤버인 비공개 방 조회
* 정렬: 열린 방 우선 → 최신순
* @param userId 사용자 ID
* @param includeInactive 닫힌 방 포함 여부
* @param pageable 페이징 정보
* @return 페이징된 비공개 방 목록
*/
Page<Room> findMyPrivateRooms(Long userId, boolean includeInactive, Pageable pageable);

/**
* 내가 호스트(방장)인 방 조회
* 정렬: 열린 방 우선 → 최신순
* @param userId 사용자 ID
* @param pageable 페이징 정보
* @return 페이징된 방 목록
*/
Page<Room> findRoomsByHostId(Long userId, Pageable pageable);
}
Loading