Skip to content

Commit 8f50a7e

Browse files
loseminhonamgigun
andauthored
Fix: 인프라 환경설정에 맞춰 파일업로드 매핑 수정 (#279) (#284)
* refactor: 스더티룸 권한에 대한 로직 개선 * fix: ci에서 통과 못한 테스트코드 수정 * fix:rest api와 웹소켓 중간 경로 통합 * fix:rest api와 웹소켓 중간 경로 통합 * fix: 에러 확인을 위한 통합테스트 추가, Room.create()메서드 수정 * refactor, feat : 조회 분할 * refactor: redis 로직 최적화 및 중복 검증 로직 제거 * fix: 에러 번호 수정 * feat: 스터디룸 방 비밀번호 변경 및 삭제 기능 구현 * fix:app-dev 제거 * feat: 웹소켓 기반 소극적 하트비트 * feat: 스터디룸 썸네일 기능 추가 및 webrtc 설정 변경에서 주석처리 * fix:소극적 하트비트 사용 주석처리 * Feat: 스터디 룸 내에 고양이 아바타 시스템과 프로필 이미지 url 연동 * fix: 기존 작성되어있던 test 코드 수정 * test: 아바타 테스트 코드 완료 * refactor: 프론트엔드 요청 사항에 따른 스터디룸 조회 마스킹 제거 * feat: 스터디룸 방 초대 코드 시스템 * Infra: main branch 로컬 환경과 운영 환경 동기화 * Infra: docker-compose 파일 수정 - Redis 버전 업그레이드 기존: 6.2 -> 변경: 7.0 * Fix: 백엔드 CD 파일 수정 - 자동화 시, 잘못된 도메인으로 호스트 ID 검증하는 오류 해결 * Infra: EC2 환경변수 수정 - 잘못 표기한 도메인 네임 변경 * Chore: CD 파일 수정 - Github Actions commandLine 인식 문제로 인해 set -Eeuo pipefail 줄바꿈 * Chore: 백엔드 CD 파일 수정 - 인스턴스 ID 체크 삭제 * Infra: 백엔드 CD 파일 수정 - .env 파일 추가시, $DOT_ENV_PROD -> $DOT_ENV 로 변경 * Infra: 도커 컴포즈 수정 - mysql 사용자 정보 변경 * Infra: 운영환경 설정 - application-prod.yml 과 application.yml 동기화 * Fix: SecurityConfig 수정 - H2 DB 허용 X * test,fix: 방 초대에 대한 테스트 코드 작성 및 에러 수정 * fix: 스터디룸 파일 업로드 맵핑 형식으로 변환 * fix: 병합충돌 제어 수정 * fix: 병합충돌 제어 --------- Co-authored-by: namgigun <[email protected]>
1 parent b8b9bc5 commit 8f50a7e

File tree

8 files changed

+265
-41
lines changed

8 files changed

+265
-41
lines changed

src/main/java/com/back/domain/studyroom/controller/RoomController.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public ResponseEntity<RsData<RoomResponse>> createRoom(
6565
request.getMaxParticipants() != null ? request.getMaxParticipants() : 10,
6666
currentUserId,
6767
request.getUseWebRTC() != null ? request.getUseWebRTC() : true, // 디폴트: true
68-
request.getThumbnailUrl() // 썸네일 URL
68+
request.getThumbnailAttachmentId() // 썸네일 Attachment ID
6969
);
7070

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

src/main/java/com/back/domain/studyroom/dto/CreateRoomRequest.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ public class CreateRoomRequest {
1616
@Size(max = 500, message = "방 설명은 500자를 초과할 수 없습니다")
1717
private String description;
1818

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

2326
private Boolean isPrivate = false;
2427

src/main/java/com/back/domain/studyroom/dto/UpdateRoomSettingsRequest.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,13 @@ public class UpdateRoomSettingsRequest {
2020
@Max(value = 100, message = "최대 100명까지 가능합니다")
2121
private Integer maxParticipants;
2222

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

2731
// ===== WebRTC 설정 (추후 팀원 구현 시 주석 해제) =====
2832
// WebRTC 기능은 방 생성 이후 별도 API로 관리 예정

src/main/java/com/back/domain/studyroom/service/RoomService.java

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public class RoomService {
5252
private final SimpMessagingTemplate messagingTemplate;
5353
private final ApplicationEventPublisher eventPublisher;
5454
private final AvatarService avatarService;
55+
private final RoomThumbnailService roomThumbnailService;
5556

5657
/**
5758
* 방 생성 메서드
@@ -68,21 +69,32 @@ public class RoomService {
6869
*/
6970
@Transactional
7071
public Room createRoom(String title, String description, boolean isPrivate,
71-
String password, int maxParticipants, Long creatorId, boolean useWebRTC, String thumbnailUrl) {
72+
String password, int maxParticipants, Long creatorId, boolean useWebRTC, Long thumbnailAttachmentId) {
7273

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

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

82+
// 썸네일 매핑 생성 및 URL 업데이트
83+
if (thumbnailAttachmentId != null) {
84+
String thumbnailUrl = roomThumbnailService.createThumbnailMapping(
85+
savedRoom.getId(), thumbnailAttachmentId);
86+
savedRoom.updateSettings(title, description, maxParticipants, thumbnailUrl);
87+
}
88+
89+
// 방장 등록
7990
RoomMember hostMember = RoomMember.createHost(savedRoom, creator);
8091
roomMemberRepository.save(hostMember);
8192

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

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

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

260-
// ⭐ 비공개 방 접근 제한 제거 - 모든 사용자가 조회 가능
261-
// (프론트엔드에서 입장 시 로그인 체크)
262-
263272
return room;
264273
}
265274

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

270279
@Transactional
271280
public void updateRoomSettings(Long roomId, String title, String description,
272-
int maxParticipants, String thumbnailUrl, Long userId) {
281+
int maxParticipants, Long thumbnailAttachmentId, Long userId) {
273282

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

297+
// 썸네일 변경 처리
298+
String thumbnailUrl = room.getRawThumbnailUrl(); // 기존 URL 유지
299+
if (thumbnailAttachmentId != null) {
300+
// 기존 매핑 삭제 + 새 매핑 생성
301+
thumbnailUrl = roomThumbnailService.updateThumbnailMapping(
302+
roomId, thumbnailAttachmentId);
303+
}
304+
288305
room.updateSettings(title, description, maxParticipants, thumbnailUrl);
289306

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

294311
/**
@@ -353,6 +370,9 @@ public void terminateRoom(Long roomId, Long userId) {
353370
throw new CustomException(ErrorCode.NOT_ROOM_MANAGER);
354371
}
355372

373+
// 썸네일 매핑 삭제
374+
roomThumbnailService.deleteThumbnailMapping(roomId);
375+
356376
room.terminate();
357377

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

533-
// ⭐ 비공개 방 접근 제한 제거 - 모든 사용자가 조회 가능
534-
535553
// 1. Redis에서 온라인 사용자 ID 조회
536554
Set<Long> onlineUserIds = roomParticipantService.getParticipants(roomId);
537555

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package com.back.domain.studyroom.service;
2+
3+
import com.back.domain.file.entity.AttachmentMapping;
4+
import com.back.domain.file.entity.EntityType;
5+
import com.back.domain.file.entity.FileAttachment;
6+
import com.back.domain.file.repository.AttachmentMappingRepository;
7+
import com.back.domain.file.repository.FileAttachmentRepository;
8+
import com.back.global.exception.CustomException;
9+
import com.back.global.exception.ErrorCode;
10+
import lombok.RequiredArgsConstructor;
11+
import lombok.extern.slf4j.Slf4j;
12+
import org.springframework.stereotype.Service;
13+
import org.springframework.transaction.annotation.Transactional;
14+
15+
import java.util.List;
16+
17+
/**
18+
* 스터디룸 썸네일 전용 서비스
19+
* 매핑 전략에 따라 AttachmentMapping을 통해 관리
20+
*/
21+
@Service
22+
@RequiredArgsConstructor
23+
@Slf4j
24+
public class RoomThumbnailService {
25+
26+
private final FileAttachmentRepository fileAttachmentRepository;
27+
private final AttachmentMappingRepository attachmentMappingRepository;
28+
29+
/**
30+
* 방 생성 시 썸네일 매핑 생성
31+
*
32+
* @param roomId 방 ID
33+
* @param thumbnailAttachmentId 썸네일 파일 ID
34+
* @return 썸네일 URL
35+
*/
36+
@Transactional
37+
public String createThumbnailMapping(Long roomId, Long thumbnailAttachmentId) {
38+
if (thumbnailAttachmentId == null) {
39+
return null;
40+
}
41+
42+
// FileAttachment 조회
43+
FileAttachment fileAttachment = fileAttachmentRepository.findById(thumbnailAttachmentId)
44+
.orElseThrow(() -> new CustomException(ErrorCode.FILE_NOT_FOUND));
45+
46+
// AttachmentMapping 생성
47+
AttachmentMapping mapping = new AttachmentMapping(
48+
fileAttachment,
49+
EntityType.STUDY_ROOM,
50+
roomId
51+
);
52+
53+
attachmentMappingRepository.save(mapping);
54+
55+
log.info("썸네일 매핑 생성 - RoomId: {}, AttachmentId: {}, URL: {}",
56+
roomId, thumbnailAttachmentId, fileAttachment.getPublicURL());
57+
58+
return fileAttachment.getPublicURL();
59+
}
60+
61+
/**
62+
* 방 수정 시 썸네일 변경
63+
* 1. 기존 매핑 삭제
64+
* 2. 새 매핑 생성
65+
*
66+
* @param roomId 방 ID
67+
* @param newThumbnailAttachmentId 새 썸네일 파일 ID (null이면 변경 없음)
68+
* @return 새 썸네일 URL (null이면 변경 없음)
69+
*/
70+
@Transactional
71+
public String updateThumbnailMapping(Long roomId, Long newThumbnailAttachmentId) {
72+
if (newThumbnailAttachmentId == null) {
73+
// null이면 썸네일 변경 없음
74+
return null;
75+
}
76+
77+
// 기존 매핑 모두 삭제
78+
attachmentMappingRepository.deleteAllByEntityTypeAndEntityId(
79+
EntityType.STUDY_ROOM, roomId);
80+
81+
log.info("기존 썸네일 매핑 삭제 - RoomId: {}", roomId);
82+
83+
// 새 매핑 생성
84+
return createThumbnailMapping(roomId, newThumbnailAttachmentId);
85+
}
86+
87+
/**
88+
* 방 삭제 시 썸네일 매핑 삭제
89+
*
90+
* @param roomId 방 ID
91+
*/
92+
@Transactional
93+
public void deleteThumbnailMapping(Long roomId) {
94+
// 연결된 파일 매핑 모두 삭제
95+
attachmentMappingRepository.deleteAllByEntityTypeAndEntityId(
96+
EntityType.STUDY_ROOM, roomId);
97+
98+
log.info("방 삭제 - 썸네일 매핑 삭제 완료 - RoomId: {}", roomId);
99+
}
100+
101+
/**
102+
* 방의 썸네일 URL 조회
103+
*
104+
* @param roomId 방 ID
105+
* @return 썸네일 URL (없으면 null)
106+
*/
107+
@Transactional(readOnly = true)
108+
public String getThumbnailUrl(Long roomId) {
109+
List<AttachmentMapping> mappings = attachmentMappingRepository
110+
.findAllByEntityTypeAndEntityId(EntityType.STUDY_ROOM, roomId);
111+
112+
if (mappings.isEmpty()) {
113+
return null;
114+
}
115+
116+
// 첫 번째 매핑의 URL 반환 (썸네일은 단일 파일)
117+
return mappings.get(0).getFileAttachment().getPublicURL();
118+
}
119+
120+
/**
121+
* 방의 썸네일 FileAttachment 조회
122+
*
123+
* @param roomId 방 ID
124+
* @return FileAttachment (없으면 null)
125+
*/
126+
@Transactional(readOnly = true)
127+
public FileAttachment getThumbnailAttachment(Long roomId) {
128+
List<AttachmentMapping> mappings = attachmentMappingRepository
129+
.findAllByEntityTypeAndEntityId(EntityType.STUDY_ROOM, roomId);
130+
131+
if (mappings.isEmpty()) {
132+
return null;
133+
}
134+
135+
// 첫 번째 매핑의 FileAttachment 반환 (썸네일은 단일 파일)
136+
return mappings.get(0).getFileAttachment();
137+
}
138+
}

src/main/resources/application-prod.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,6 @@ spring:
7373
token-uri: https://github.com/login/oauth/access_token
7474
user-info-uri: https://api.github.com/user
7575
user-name-attribute: id
76-
7776
servlet:
7877
multipart:
7978
max-file-size: 10MB # 업로드할 수 있는 개별 파일의 최대 크기

0 commit comments

Comments
 (0)