Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 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
f68ff22
test,fix: 방 초대에 대한 테스트 코드 작성 및 에러 수정
loseminho Oct 13, 2025
c1b6465
fix: 룸 썸네일 파일 업로드 + url 저장 하이브리드 구현
loseminho Oct 14, 2025
e30ff16
Merge remote-tracking branch 'origin/dev' into fix/273
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
4 changes: 2 additions & 2 deletions src/main/java/com/back/domain/file/entity/EntityType.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.back.domain.file.entity;

public enum EntityType {
POST, AVATAR, PROFILE
}
POST, AVATAR, PROFILE, ROOM_THUMBNAIL
}
73 changes: 73 additions & 0 deletions src/main/java/com/back/domain/file/service/FileService.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,77 @@ private void checkAccessPermission(FileAttachment fileAttachment, Long userId) {
throw new CustomException(ErrorCode.FILE_ACCESS_DENIED);
}
}

/**
* URL이 우리 S3 버킷의 파일인지 확인
* @param url 확인할 URL
* @return S3 파일이면 true, 외부 URL이면 false
*/
public boolean isOurS3File(String url) {
if (url == null || url.trim().isEmpty()) {
return false;
}

// S3 URL 패턴 체크
// 패턴 1: https://bucket-name.s3.amazonaws.com/...
// 패턴 2: https://s3.amazonaws.com/bucket-name/...
// 패턴 3: https://bucket-name.s3.ap-northeast-2.amazonaws.com/...
return url.contains(".s3.") && url.contains(".amazonaws.com") && url.contains(bucket);
}

/**
* S3 URL에서 파일명(Key) 추출
* @param url S3 전체 URL
* @return 파일명 (예: "uuid-filename.jpg")
*/
public String extractFileNameFromUrl(String url) {
if (url == null || url.trim().isEmpty()) {
return null;
}

try {
// URL에서 마지막 "/" 이후의 파일명 추출
int lastSlashIndex = url.lastIndexOf('/');
if (lastSlashIndex >= 0 && lastSlashIndex < url.length() - 1) {
return url.substring(lastSlashIndex + 1);
}
} catch (Exception e) {
// 추출 실패 시 null 반환
}

return null;
}

/**
* S3 파일을 파일명으로 삭제
* RoomService 등 다른 도메인에서 썸네일 삭제 시 사용
* @param fileName S3에 저장된 파일명
*/
public void deleteS3FileByName(String fileName) {
if (fileName == null || fileName.trim().isEmpty()) {
return;
}

try {
s3Delete(fileName);
} catch (Exception e) {
// S3 삭제 실패해도 무시 (파일이 이미 없을 수 있음)
// 로그만 남기고 계속 진행
}
}

/**
* URL로 S3 파일 삭제 (권한 체크 없음 - 내부 사용용)
* 우리 S3 파일인지 확인 후 삭제
* @param url 삭제할 파일의 전체 URL
*/
public void deleteS3FileByUrl(String url) {
if (!isOurS3File(url)) {
// 외부 URL이면 삭제하지 않음
return;
}

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

// 방 썸네일 이미지 URL (선택)
// 방 썸네일 이미지 URL (선택해서 진행 가능)
// 파일 업로드를 사용하는 경우: 먼저 /api/files/upload로 파일을 업로드하고 받은 URL을 여기에 설정
// 직접 URL을 입력하는 경우: 외부 이미지 URL을 직접 입력하게 됨
@Size(max = 500, message = "썸네일 URL은 500자를 초과할 수 없습니다")
private String thumbnailUrl;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class MyRoomResponse {
private String title;
private String description;
private Boolean isPrivate; // 비공개 방 여부 (UI에서 🔒 아이콘 표시용)
private String thumbnailUrl; // 썸네일 이미지 URL
private int currentParticipants;
private int maxParticipants;
private RoomStatus status;
Expand All @@ -26,8 +27,9 @@ 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에서 조회한 실시간 값
.isPrivate(room.isPrivate())
.thumbnailUrl(room.getThumbnailUrl())
.currentParticipants((int) currentParticipants)
.maxParticipants(room.getMaxParticipants())
.status(room.getStatus())
.myRole(myRole)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ public class UpdateRoomSettingsRequest {
private Integer maxParticipants;

// 방 썸네일 이미지 URL (선택)
// 파일 업로드를 사용하는 경우: 먼저 /api/files/upload로 파일을 업로드하고 받은 URL을 여기에 설정
// 직접 URL을 입력하는 경우: 외부 이미지 URL을 직접 입력
// null인 경우: 기존 썸네일 유지
@Size(max = 500, message = "썸네일 URL은 500자를 초과할 수 없습니다")
private String thumbnailUrl;

Expand Down
27 changes: 20 additions & 7 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 com.back.domain.file.service.FileService fileService;

/**
* 방 생성 메서드
Expand All @@ -78,8 +79,6 @@ public Room createRoom(String title, String description, boolean isPrivate,

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 ? "설정됨" : "없음");
Expand Down Expand Up @@ -196,7 +195,6 @@ public Page<Room> getJoinableRooms(Pageable pageable) {

/**
* 모든 방 조회 (공개 + 비공개 전체)
* 비공개 방은 정보 마스킹
*/
public Page<Room> getAllRooms(Pageable pageable) {
return roomRepository.findAllRooms(pageable);
Expand Down Expand Up @@ -228,7 +226,7 @@ public Page<Room> getMyHostingRooms(Long userId, Pageable pageable) {
/**
* 모든 방을 RoomResponse로 변환 (비공개 방 마스킹 포함)
* @param rooms 방 목록
* @return 마스킹된 RoomResponse 리스트
* @return RoomResponse 리스트
*/
public java.util.List<RoomResponse> toRoomResponseListWithMasking(java.util.List<Room> rooms) {
java.util.List<Long> roomIds = rooms.stream()
Expand Down Expand Up @@ -257,8 +255,6 @@ public Room getRoomDetail(Long roomId, Long userId) {
Room room = roomRepository.findById(roomId)
.orElseThrow(() -> new CustomException(ErrorCode.ROOM_NOT_FOUND));

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

return room;
}
Expand All @@ -285,8 +281,18 @@ public void updateRoomSettings(Long roomId, String title, String description,
throw new CustomException(ErrorCode.BAD_REQUEST);
}

// 기존 썸네일 URL 저장
String oldThumbnailUrl = room.getRawThumbnailUrl();

// 방 설정 업데이트
room.updateSettings(title, description, maxParticipants, thumbnailUrl);

// 썸네일이 변경되었고, 기존 썸네일이 S3 파일인 경우 삭제
if (oldThumbnailUrl != null && !oldThumbnailUrl.equals(thumbnailUrl)) {
fileService.deleteS3FileByUrl(oldThumbnailUrl);
log.info("기존 썸네일 삭제 완료 - RoomId: {}, OldUrl: {}", roomId, oldThumbnailUrl);
}

log.info("방 설정 변경 완료 - RoomId: {}, UserId: {}, Thumbnail: {}",
roomId, userId, thumbnailUrl != null ? "변경됨" : "없음");
}
Expand Down Expand Up @@ -353,6 +359,13 @@ public void terminateRoom(Long roomId, Long userId) {
throw new CustomException(ErrorCode.NOT_ROOM_MANAGER);
}

// 썸네일이 S3 파일인 경우 삭제
String thumbnailUrl = room.getRawThumbnailUrl();
if (thumbnailUrl != null) {
fileService.deleteS3FileByUrl(thumbnailUrl);
log.info("방 종료 - 썸네일 삭제 완료 - RoomId: {}, ThumbnailUrl: {}", roomId, thumbnailUrl);
}

room.terminate();

// Redis에서 모든 온라인 사용자 제거
Expand Down Expand Up @@ -530,7 +543,7 @@ 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