Skip to content
Merged
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
54 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
a096d93
Merge remote-tracking branch 'origin/dev' into refactor/257
loseminho Oct 13, 2025
df98fc7
feat: 스터디룸 방 초대 코드 시스템
loseminho Oct 13, 2025
4be3aab
Infra: main branch 로컬 환경과 운영 환경 동기화
namgigun Oct 13, 2025
e0a3af3
Infra: docker-compose 파일 수정
namgigun Oct 1, 2025
3aaebb6
Fix: 백엔드 CD 파일 수정
namgigun Oct 2, 2025
cdf13b7
Infra: EC2 환경변수 수정
namgigun Oct 13, 2025
bc471c4
Chore: CD 파일 수정
namgigun Oct 2, 2025
eee9529
Chore: 백엔드 CD 파일 수정
namgigun Oct 2, 2025
849fbf5
Infra: 백엔드 CD 파일 수정
namgigun Oct 2, 2025
670984c
Infra: 도커 컴포즈 수정
namgigun Oct 5, 2025
2fc31f0
Infra: 운영환경 설정
namgigun Oct 13, 2025
401fa4b
Fix: SecurityConfig 수정
namgigun Oct 13, 2025
f68ff22
test,fix: 방 초대에 대한 테스트 코드 작성 및 에러 수정
loseminho Oct 13, 2025
2c49fc6
Merge remote-tracking branch 'origin/dev' into fix/279
loseminho Oct 14, 2025
8cbb11f
fix: 스터디룸 파일 업로드 맵핑 형식으로 변환
loseminho Oct 14, 2025
6c9e254
Merge remote-tracking branch 'origin' into fix/279
loseminho Oct 14, 2025
0cea892
fix: 병합충돌 제어 수정
loseminho Oct 14, 2025
4563fef
fix: 병합충돌 제어
loseminho Oct 14, 2025
6f5596a
fix: 스터디 룸 내 프론트엔드 요구 사항 및 오류사항 수정
loseminho Oct 14, 2025
b23ba21
Merge remote-tracking branch 'origin/dev' into fix/287
loseminho Oct 14, 2025
ab6baa8
feat: 방 즐겨찾기, 방 공지사항 구현
loseminho Oct 15, 2025
1c05a8a
Merge remote-tracking branch 'origin/dev' into fix/287
loseminho Oct 15, 2025
b70548c
fix: mockbean 수정
loseminho Oct 15, 2025
03419b6
fix: 테스트에서 빠진 비로그인 사용자 추가
loseminho Oct 15, 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
@@ -0,0 +1,171 @@
package com.back.domain.studyroom.controller;

import com.back.domain.studyroom.dto.CreateAnnouncementRequest;
import com.back.domain.studyroom.dto.RoomAnnouncementResponse;
import com.back.domain.studyroom.dto.UpdateAnnouncementRequest;
import com.back.domain.studyroom.service.RoomAnnouncementService;
import com.back.global.common.dto.RsData;
import com.back.global.security.user.CurrentUser;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
* 방 공지사항 API 컨트롤러
*/
@RestController
@RequestMapping("/api/rooms/{roomId}/announcements")
@RequiredArgsConstructor
@Tag(name = "Room Announcement API", description = "방 공지사항 관련 API")
@SecurityRequirement(name = "Bearer Authentication")
public class RoomAnnouncementController {

private final RoomAnnouncementService announcementService;
private final CurrentUser currentUser;

@PostMapping
@Operation(
summary = "공지사항 생성",
description = "새로운 공지사항을 생성합니다. 방장만 생성 가능합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "201", description = "공지사항 생성 성공"),
@ApiResponse(responseCode = "403", description = "방장 권한 없음"),
@ApiResponse(responseCode = "404", description = "존재하지 않는 방"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
public ResponseEntity<RsData<RoomAnnouncementResponse>> createAnnouncement(
@Parameter(description = "방 ID", required = true) @PathVariable Long roomId,
@Valid @RequestBody CreateAnnouncementRequest request) {

Long userId = currentUser.getUserId();
RoomAnnouncementResponse response = announcementService.createAnnouncement(
roomId, request.getTitle(), request.getContent(), userId);

return ResponseEntity
.status(HttpStatus.CREATED)
.body(RsData.success("공지사항 생성 완료", response));
}

@GetMapping
@Operation(
summary = "공지사항 목록 조회",
description = "방의 모든 공지사항을 조회합니다. 핀 고정된 공지가 먼저 표시되고, 그 다음 최신순으로 정렬됩니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "404", description = "존재하지 않는 방")
})
public ResponseEntity<RsData<List<RoomAnnouncementResponse>>> getAnnouncements(
@Parameter(description = "방 ID", required = true) @PathVariable Long roomId) {

List<RoomAnnouncementResponse> announcements = announcementService.getAnnouncements(roomId);

return ResponseEntity
.status(HttpStatus.OK)
.body(RsData.success("공지사항 목록 조회 완료", announcements));
}

@GetMapping("/{announcementId}")
@Operation(
summary = "공지사항 단건 조회",
description = "특정 공지사항의 상세 정보를 조회합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "404", description = "존재하지 않는 공지사항")
})
public ResponseEntity<RsData<RoomAnnouncementResponse>> getAnnouncement(
@Parameter(description = "방 ID", required = true) @PathVariable Long roomId,
@Parameter(description = "공지사항 ID", required = true) @PathVariable Long announcementId) {

RoomAnnouncementResponse announcement = announcementService.getAnnouncement(announcementId);

return ResponseEntity
.status(HttpStatus.OK)
.body(RsData.success("공지사항 조회 완료", announcement));
}

@PutMapping("/{announcementId}")
@Operation(
summary = "공지사항 수정",
description = "공지사항의 제목과 내용을 수정합니다. 방장만 수정 가능합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "수정 성공"),
@ApiResponse(responseCode = "403", description = "방장 권한 없음"),
@ApiResponse(responseCode = "404", description = "존재하지 않는 공지사항"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
public ResponseEntity<RsData<RoomAnnouncementResponse>> updateAnnouncement(
@Parameter(description = "방 ID", required = true) @PathVariable Long roomId,
@Parameter(description = "공지사항 ID", required = true) @PathVariable Long announcementId,
@Valid @RequestBody UpdateAnnouncementRequest request) {

Long userId = currentUser.getUserId();
RoomAnnouncementResponse response = announcementService.updateAnnouncement(
announcementId, request.getTitle(), request.getContent(), userId);

return ResponseEntity
.status(HttpStatus.OK)
.body(RsData.success("공지사항 수정 완료", response));
}

@DeleteMapping("/{announcementId}")
@Operation(
summary = "공지사항 삭제",
description = "공지사항을 삭제합니다. 방장만 삭제 가능합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "삭제 성공"),
@ApiResponse(responseCode = "403", description = "방장 권한 없음"),
@ApiResponse(responseCode = "404", description = "존재하지 않는 공지사항"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
public ResponseEntity<RsData<Void>> deleteAnnouncement(
@Parameter(description = "방 ID", required = true) @PathVariable Long roomId,
@Parameter(description = "공지사항 ID", required = true) @PathVariable Long announcementId) {

Long userId = currentUser.getUserId();
announcementService.deleteAnnouncement(announcementId, userId);

return ResponseEntity
.status(HttpStatus.OK)
.body(RsData.success("공지사항 삭제 완료", null));
}

@PutMapping("/{announcementId}/pin")
@Operation(
summary = "공지사항 핀 고정/해제",
description = "공지사항을 상단에 고정하거나 고정을 해제합니다. 방장만 실행 가능합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "핀 토글 성공"),
@ApiResponse(responseCode = "403", description = "방장 권한 없음"),
@ApiResponse(responseCode = "404", description = "존재하지 않는 공지사항"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
public ResponseEntity<RsData<RoomAnnouncementResponse>> togglePin(
@Parameter(description = "방 ID", required = true) @PathVariable Long roomId,
@Parameter(description = "공지사항 ID", required = true) @PathVariable Long announcementId) {

Long userId = currentUser.getUserId();
RoomAnnouncementResponse response = announcementService.togglePin(announcementId, userId);

String message = response.getIsPinned() ? "공지사항 핀 고정 완료" : "공지사항 핀 고정 해제 완료";

return ResponseEntity
.status(HttpStatus.OK)
.body(RsData.success(message, response));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,9 @@ public ResponseEntity<RsData<Map<String, Object>>> getAllRooms(
Pageable pageable = PageRequest.of(page, size);
Page<Room> rooms = roomService.getAllRooms(pageable);

// 모든 정보 공개
List<RoomResponse> roomList = roomService.toRoomResponseList(rooms.getContent());
// 비로그인 사용자도 조회 가능 (userId = null이면 isFavorite = false)
Long userId = currentUser.getUserIdOrNull();
List<RoomResponse> roomList = roomService.toRoomResponseList(rooms.getContent(), userId);

Map<String, Object> response = new HashMap<>();
response.put("rooms", roomList);
Expand Down Expand Up @@ -173,7 +174,9 @@ public ResponseEntity<RsData<Map<String, Object>>> getPublicRooms(
Pageable pageable = PageRequest.of(page, size);
Page<Room> rooms = roomService.getPublicRooms(includeInactive, pageable);

List<RoomResponse> roomList = roomService.toRoomResponseList(rooms.getContent());
// 비로그인 사용자도 조회 가능
Long userId = currentUser.getUserIdOrNull();
List<RoomResponse> roomList = roomService.toRoomResponseList(rooms.getContent(), userId);

Map<String, Object> response = new HashMap<>();
response.put("rooms", roomList);
Expand Down Expand Up @@ -207,7 +210,7 @@ public ResponseEntity<RsData<Map<String, Object>>> getMyPrivateRooms(
Pageable pageable = PageRequest.of(page, size);
Page<Room> rooms = roomService.getMyPrivateRooms(currentUserId, includeInactive, pageable);

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

Map<String, Object> response = new HashMap<>();
response.put("rooms", roomList);
Expand Down Expand Up @@ -240,7 +243,7 @@ public ResponseEntity<RsData<Map<String, Object>>> getMyHostingRooms(
Pageable pageable = PageRequest.of(page, size);
Page<Room> rooms = roomService.getMyHostingRooms(currentUserId, pageable);

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

Map<String, Object> response = new HashMap<>();
response.put("rooms", roomList);
Expand Down Expand Up @@ -270,7 +273,9 @@ public ResponseEntity<RsData<Map<String, Object>>> getRooms(
Pageable pageable = PageRequest.of(page, size);
Page<Room> rooms = roomService.getJoinableRooms(pageable);

List<RoomResponse> roomList = roomService.toRoomResponseList(rooms.getContent());
// 비로그인 사용자도 조회 가능
Long userId = currentUser.getUserIdOrNull();
List<RoomResponse> roomList = roomService.toRoomResponseList(rooms.getContent(), userId);

Map<String, Object> response = new HashMap<>();
response.put("rooms", roomList);
Expand Down Expand Up @@ -303,7 +308,7 @@ public ResponseEntity<RsData<RoomDetailResponse>> getRoomDetail(
Room room = roomService.getRoomDetail(roomId, currentUserId);
List<RoomMember> members = roomService.getRoomMembers(roomId, currentUserId);

RoomDetailResponse response = roomService.toRoomDetailResponse(room, members);
RoomDetailResponse response = roomService.toRoomDetailResponse(room, members, currentUserId);

return ResponseEntity
.status(HttpStatus.OK)
Expand Down Expand Up @@ -554,7 +559,9 @@ public ResponseEntity<RsData<Map<String, Object>>> getPopularRooms(
Pageable pageable = PageRequest.of(page, size);
Page<Room> rooms = roomService.getPopularRooms(pageable);

List<RoomResponse> roomList = roomService.toRoomResponseList(rooms.getContent());
// 비로그인 사용자도 조회 가능
Long userId = currentUser.getUserIdOrNull();
List<RoomResponse> roomList = roomService.toRoomResponseList(rooms.getContent(), userId);

Map<String, Object> response = new HashMap<>();
response.put("rooms", roomList);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package com.back.domain.studyroom.controller;

import com.back.domain.studyroom.dto.RoomFavoriteResponse;
import com.back.domain.studyroom.service.RoomFavoriteService;
import com.back.global.common.dto.RsData;
import com.back.global.security.user.CurrentUser;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
* 방 즐겨찾기 API 컨트롤러
*/
@RestController
@RequestMapping("/api/rooms")
@RequiredArgsConstructor
@Tag(name = "Room Favorite API", description = "방 즐겨찾기 관련 API")
@SecurityRequirement(name = "Bearer Authentication")
public class RoomFavoriteController {

private final RoomFavoriteService favoriteService;
private final CurrentUser currentUser;

@PostMapping("/{roomId}/favorite")
@Operation(
summary = "즐겨찾기 추가",
description = "특정 방을 즐겨찾기에 추가합니다. 이미 추가된 경우 무시됩니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "즐겨찾기 추가 성공"),
@ApiResponse(responseCode = "404", description = "존재하지 않는 방"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
public ResponseEntity<RsData<Void>> addFavorite(
@Parameter(description = "방 ID", required = true) @PathVariable Long roomId) {

Long userId = currentUser.getUserId();
favoriteService.addFavorite(roomId, userId);

return ResponseEntity
.status(HttpStatus.OK)
.body(RsData.success("즐겨찾기 추가 완료", null));
}

@DeleteMapping("/{roomId}/favorite")
@Operation(
summary = "즐겨찾기 제거",
description = "특정 방을 즐겨찾기에서 제거합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "즐겨찾기 제거 성공"),
@ApiResponse(responseCode = "404", description = "즐겨찾기되지 않은 방"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
public ResponseEntity<RsData<Void>> removeFavorite(
@Parameter(description = "방 ID", required = true) @PathVariable Long roomId) {

Long userId = currentUser.getUserId();
favoriteService.removeFavorite(roomId, userId);

return ResponseEntity
.status(HttpStatus.OK)
.body(RsData.success("즐겨찾기 제거 완료", null));
}

@GetMapping("/favorites")
@Operation(
summary = "내 즐겨찾기 목록 조회",
description = "내가 즐겨찾기한 모든 방 목록을 최신 즐겨찾기 순으로 조회합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
public ResponseEntity<RsData<List<RoomFavoriteResponse>>> getMyFavorites() {

Long userId = currentUser.getUserId();
List<RoomFavoriteResponse> favorites = favoriteService.getMyFavorites(userId);

return ResponseEntity
.status(HttpStatus.OK)
.body(RsData.success("즐겨찾기 목록 조회 완료", favorites));
}

@GetMapping("/{roomId}/favorite")
@Operation(
summary = "즐겨찾기 여부 확인",
description = "특정 방이 즐겨찾기되어 있는지 확인합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "401", description = "인증 실패")
})
public ResponseEntity<RsData<Boolean>> isFavorite(
@Parameter(description = "방 ID", required = true) @PathVariable Long roomId) {

Long userId = currentUser.getUserId();
boolean isFavorite = favoriteService.isFavorite(roomId, userId);

return ResponseEntity
.status(HttpStatus.OK)
.body(RsData.success("즐겨찾기 여부 조회 완료", isFavorite));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.back.domain.studyroom.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
* 공지사항 생성 요청 DTO
*/
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class CreateAnnouncementRequest {

@NotBlank(message = "공지사항 제목은 필수입니다")
@Size(max = 100, message = "제목은 100자 이내여야 합니다")
private String title;

@NotBlank(message = "공지사항 내용은 필수입니다")
@Size(max = 5000, message = "내용은 5000자 이내여야 합니다")
private String content;
}
Loading
Loading