Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 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
23f0312
Merge branch 'dev' into fix/279
loseminho Oct 14, 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 @@ -65,7 +65,7 @@ public ResponseEntity<RsData<RoomResponse>> createRoom(
request.getMaxParticipants() != null ? request.getMaxParticipants() : 10,
currentUserId,
request.getUseWebRTC() != null ? request.getUseWebRTC() : true, // 디폴트: true
request.getThumbnailUrl() // 썸네일 URL
request.getThumbnailAttachmentId() // 썸네일 Attachment ID
);

RoomResponse response = roomService.toRoomResponse(room);
Expand Down Expand Up @@ -336,7 +336,7 @@ public ResponseEntity<RsData<List<MyRoomResponse>>> getMyRooms() {
@PutMapping("/{roomId}")
@Operation(
summary = "방 설정 수정",
description = "방의 제목, 설명, 정원, 썸네일을 수정합니다. 방장만 수정 가능합니다. WebRTC 설정은 현재 수정 불가합니다."
description = "방의 제목, 설명, 정원, 썸네일을 수정합니다.thumbnailAttachmentId가 null이면 썸네일 변경 없이 기존 유지됩니다. 방장만 수정 가능합니다. WebRTC 설정은 현재 수정 불가합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "수정 성공"),
Expand All @@ -356,7 +356,7 @@ public ResponseEntity<RsData<Void>> updateRoom(
request.getTitle(),
request.getDescription(),
request.getMaxParticipants(),
request.getThumbnailUrl(),
request.getThumbnailAttachmentId(), // 썸네일 Attachment ID
currentUserId
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ public class CreateRoomRequest {
@Size(max = 500, message = "방 설명은 500자를 초과할 수 없습니다")
private String description;

// 방 썸네일 이미지 URL (선택)
@Size(max = 500, message = "썸네일 URL은 500자를 초과할 수 없습니다")
private String thumbnailUrl;
// 방 썸네일 FileAttachment ID (선택)
// 사용 방법:
// 1. POST /api/files/upload로 파일 업로드
// 2. 받은 fileId를 여기에 설정
// 3. 방 생성 시 AttachmentMapping 자동 생성
private Long thumbnailAttachmentId;

private Boolean isPrivate = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ public class UpdateRoomSettingsRequest {
@Max(value = 100, message = "최대 100명까지 가능합니다")
private Integer maxParticipants;

// 방 썸네일 이미지 URL (선택)
@Size(max = 500, message = "썸네일 URL은 500자를 초과할 수 없습니다")
private String thumbnailUrl;
// 방 썸네일 FileAttachment ID (선택)
// 사용 방법:
// 1. POST /api/files/upload로 새 파일 업로드
// 2. 받은 fileId를 여기에 설정
// 3. 방 수정 시 기존 매핑 삭제 + 새 매핑 생성
// null인 경우: 썸네일 변경 없음 (기존 유지)
private Long thumbnailAttachmentId;

// ===== WebRTC 설정 (추후 팀원 구현 시 주석 해제) =====
// WebRTC 기능은 방 생성 이후 별도 API로 관리 예정
Expand Down
42 changes: 30 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 @@ -52,6 +52,7 @@ public class RoomService {
private final SimpMessagingTemplate messagingTemplate;
private final ApplicationEventPublisher eventPublisher;
private final AvatarService avatarService;
private final RoomThumbnailService roomThumbnailService;

/**
* 방 생성 메서드
Expand All @@ -68,21 +69,32 @@ public class RoomService {
*/
@Transactional
public Room createRoom(String title, String description, boolean isPrivate,
String password, int maxParticipants, Long creatorId, boolean useWebRTC, String thumbnailUrl) {
String password, int maxParticipants, Long creatorId, boolean useWebRTC, Long thumbnailAttachmentId) {

User creator = userRepository.findById(creatorId)
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));

Room room = Room.create(title, description, isPrivate, password, maxParticipants, creator, null, useWebRTC, thumbnailUrl);
// 방 생성 (썸네일 URL은 null로 시작)
Room room = Room.create(title, description, isPrivate, password,
maxParticipants, creator, null, useWebRTC, null);
Room savedRoom = roomRepository.save(room);

// 썸네일 매핑 생성 및 URL 업데이트
if (thumbnailAttachmentId != null) {
String thumbnailUrl = roomThumbnailService.createThumbnailMapping(
savedRoom.getId(), thumbnailAttachmentId);
savedRoom.updateSettings(title, description, maxParticipants, thumbnailUrl);
}

// 방장 등록
RoomMember hostMember = RoomMember.createHost(savedRoom, creator);
roomMemberRepository.save(hostMember);

// savedRoom.incrementParticipant(); // Redis로 이관 - DB 업데이트 제거

log.info("방 생성 완료 - RoomId: {}, Title: {}, CreatorId: {}, WebRTC: {}, Thumbnail: {}",
savedRoom.getId(), title, creatorId, useWebRTC, thumbnailUrl != null ? "설정됨" : "없음");
log.info("방 생성 완료 - RoomId: {}, Title: {}, CreatorId: {}, WebRTC: {}, ThumbnailId: {}",
savedRoom.getId(), title, creatorId, useWebRTC,
thumbnailAttachmentId != null ? thumbnailAttachmentId : "없음");

return savedRoom;
}
Expand Down Expand Up @@ -257,9 +269,6 @@ public Room getRoomDetail(Long roomId, Long userId) {
Room room = roomRepository.findById(roomId)
.orElseThrow(() -> new CustomException(ErrorCode.ROOM_NOT_FOUND));

// ⭐ 비공개 방 접근 제한 제거 - 모든 사용자가 조회 가능
// (프론트엔드에서 입장 시 로그인 체크)

return room;
}

Expand All @@ -269,7 +278,7 @@ public List<Room> getUserRooms(Long userId) {

@Transactional
public void updateRoomSettings(Long roomId, String title, String description,
int maxParticipants, String thumbnailUrl, Long userId) {
int maxParticipants, Long thumbnailAttachmentId, Long userId) {

Room room = roomRepository.findById(roomId)
.orElseThrow(() -> new CustomException(ErrorCode.ROOM_NOT_FOUND));
Expand All @@ -285,10 +294,18 @@ public void updateRoomSettings(Long roomId, String title, String description,
throw new CustomException(ErrorCode.BAD_REQUEST);
}

// 썸네일 변경 처리
String thumbnailUrl = room.getRawThumbnailUrl(); // 기존 URL 유지
if (thumbnailAttachmentId != null) {
// 기존 매핑 삭제 + 새 매핑 생성
thumbnailUrl = roomThumbnailService.updateThumbnailMapping(
roomId, thumbnailAttachmentId);
}

room.updateSettings(title, description, maxParticipants, thumbnailUrl);

log.info("방 설정 변경 완료 - RoomId: {}, UserId: {}, Thumbnail: {}",
roomId, userId, thumbnailUrl != null ? "변경됨" : "없음");
log.info("방 설정 변경 완료 - RoomId: {}, UserId: {}, ThumbnailId: {}",
roomId, userId, thumbnailAttachmentId != null ? thumbnailAttachmentId : "변경 없음");
}

/**
Expand Down Expand Up @@ -353,6 +370,9 @@ public void terminateRoom(Long roomId, Long userId) {
throw new CustomException(ErrorCode.NOT_ROOM_MANAGER);
}

// 썸네일 매핑 삭제
roomThumbnailService.deleteThumbnailMapping(roomId);

room.terminate();

// Redis에서 모든 온라인 사용자 제거
Expand Down Expand Up @@ -530,8 +550,6 @@ public List<RoomMember> getRoomMembers(Long roomId, Long userId) {
Room room = roomRepository.findById(roomId)
.orElseThrow(() -> new CustomException(ErrorCode.ROOM_NOT_FOUND));

// ⭐ 비공개 방 접근 제한 제거 - 모든 사용자가 조회 가능

// 1. Redis에서 온라인 사용자 ID 조회
Set<Long> onlineUserIds = roomParticipantService.getParticipants(roomId);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package com.back.domain.studyroom.service;

import com.back.domain.file.entity.AttachmentMapping;
import com.back.domain.file.entity.EntityType;
import com.back.domain.file.entity.FileAttachment;
import com.back.domain.file.repository.AttachmentMappingRepository;
import com.back.domain.file.repository.FileAttachmentRepository;
import com.back.global.exception.CustomException;
import com.back.global.exception.ErrorCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
* 스터디룸 썸네일 전용 서비스
* 매핑 전략에 따라 AttachmentMapping을 통해 관리
*/
@Service
@RequiredArgsConstructor
@Slf4j
public class RoomThumbnailService {

private final FileAttachmentRepository fileAttachmentRepository;
private final AttachmentMappingRepository attachmentMappingRepository;

/**
* 방 생성 시 썸네일 매핑 생성
*
* @param roomId 방 ID
* @param thumbnailAttachmentId 썸네일 파일 ID
* @return 썸네일 URL
*/
@Transactional
public String createThumbnailMapping(Long roomId, Long thumbnailAttachmentId) {
if (thumbnailAttachmentId == null) {
return null;
}

// FileAttachment 조회
FileAttachment fileAttachment = fileAttachmentRepository.findById(thumbnailAttachmentId)
.orElseThrow(() -> new CustomException(ErrorCode.FILE_NOT_FOUND));

// AttachmentMapping 생성
AttachmentMapping mapping = new AttachmentMapping(
fileAttachment,
EntityType.STUDY_ROOM,
roomId
);

attachmentMappingRepository.save(mapping);

log.info("썸네일 매핑 생성 - RoomId: {}, AttachmentId: {}, URL: {}",
roomId, thumbnailAttachmentId, fileAttachment.getPublicURL());

return fileAttachment.getPublicURL();
}

/**
* 방 수정 시 썸네일 변경
* 1. 기존 매핑 삭제
* 2. 새 매핑 생성
*
* @param roomId 방 ID
* @param newThumbnailAttachmentId 새 썸네일 파일 ID (null이면 변경 없음)
* @return 새 썸네일 URL (null이면 변경 없음)
*/
@Transactional
public String updateThumbnailMapping(Long roomId, Long newThumbnailAttachmentId) {
if (newThumbnailAttachmentId == null) {
// null이면 썸네일 변경 없음
return null;
}

// 기존 매핑 모두 삭제
attachmentMappingRepository.deleteAllByEntityTypeAndEntityId(
EntityType.STUDY_ROOM, roomId);

log.info("기존 썸네일 매핑 삭제 - RoomId: {}", roomId);

// 새 매핑 생성
return createThumbnailMapping(roomId, newThumbnailAttachmentId);
}

/**
* 방 삭제 시 썸네일 매핑 삭제
*
* @param roomId 방 ID
*/
@Transactional
public void deleteThumbnailMapping(Long roomId) {
// 연결된 파일 매핑 모두 삭제
attachmentMappingRepository.deleteAllByEntityTypeAndEntityId(
EntityType.STUDY_ROOM, roomId);

log.info("방 삭제 - 썸네일 매핑 삭제 완료 - RoomId: {}", roomId);
}

/**
* 방의 썸네일 URL 조회
*
* @param roomId 방 ID
* @return 썸네일 URL (없으면 null)
*/
@Transactional(readOnly = true)
public String getThumbnailUrl(Long roomId) {
List<AttachmentMapping> mappings = attachmentMappingRepository
.findAllByEntityTypeAndEntityId(EntityType.STUDY_ROOM, roomId);

if (mappings.isEmpty()) {
return null;
}

// 첫 번째 매핑의 URL 반환 (썸네일은 단일 파일)
return mappings.get(0).getFileAttachment().getPublicURL();
}

/**
* 방의 썸네일 FileAttachment 조회
*
* @param roomId 방 ID
* @return FileAttachment (없으면 null)
*/
@Transactional(readOnly = true)
public FileAttachment getThumbnailAttachment(Long roomId) {
List<AttachmentMapping> mappings = attachmentMappingRepository
.findAllByEntityTypeAndEntityId(EntityType.STUDY_ROOM, roomId);

if (mappings.isEmpty()) {
return null;
}

// 첫 번째 매핑의 FileAttachment 반환 (썸네일은 단일 파일)
return mappings.get(0).getFileAttachment();
}
}
1 change: 0 additions & 1 deletion src/main/resources/application-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ spring:
token-uri: https://github.com/login/oauth/access_token
user-info-uri: https://api.github.com/user
user-name-attribute: id

servlet:
multipart:
max-file-size: 10MB # 업로드할 수 있는 개별 파일의 최대 크기
Expand Down
Loading