Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -70,11 +70,11 @@ public String getAllProblems(

@GetMapping("/problem/{problemId}")
public String getProblemDetail(@PathVariable(name = "problemId") Long problemId, Model model) {
ProblemResponseDto problem = problemService.findProblem(problemId);
ProblemResponseDto problem = problemService.findProblemForAdmin(problemId);
model.addAttribute("problem", problem);

// 폴더 및 작성자 정보 조회
FolderResponseDto folder = folderService.findFolder(problem.folderId());
FolderResponseDto folder = folderService.findFolderForAdmin(problem.folderId());
UserResponseDto user = userService.findUser(folder.userId());
List<ProblemSolveResponseDto> problemSolves = problemSolveService.getAdminProblemSolvesByProblemId(problemId);
model.addAttribute("folder", folder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public CommonResponse<FolderResponseDto> getRootFolder() {
public CommonResponse<FolderResponseDto> getFolder(@PathVariable("folderId") Long folderId) {
Long userId = (Long) SecurityContextHolder.getContext().getAuthentication().getPrincipal();

return CommonResponse.success(folderService.findFolder(folderId));
return CommonResponse.success(folderService.findFolder(folderId, userId));
}

// ✅ V2 API: 커서 기반 하위 폴더 조회 (무한 스크롤)
Expand All @@ -47,7 +47,7 @@ public CommonResponse<CursorPageResponse<FolderThumbnailResponseDto>> getSubFold
Long userId = (Long) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
log.info("userId: {} get subfolders for folderId: {} with cursor: {}, size: {}", userId, folderId, cursor, size);

return CommonResponse.success(folderService.findSubFoldersWithCursor(folderId, cursor, size));
return CommonResponse.success(folderService.findSubFoldersWithCursor(folderId, userId, cursor, size));
}

// ✅ 모든 폴더 조회
Expand Down Expand Up @@ -98,7 +98,8 @@ public CommonResponse<String> updateFolderInfo(@RequestBody FolderRegisterDto fo
// ✅ 폴더 삭제 기능
@DeleteMapping("")
public CommonResponse<String> deleteFoldersWithProblems(@RequestBody FolderDeleteRequestDto folderDeleteRequestDto) {
folderService.deleteFoldersWithProblems(folderDeleteRequestDto.deleteFolderIdList());
Long userId = (Long) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
folderService.deleteFoldersWithProblems(userId, folderDeleteRequestDto.deleteFolderIdList());
return CommonResponse.success("폴더가 성공적으로 삭제되었습니다.");
}

Expand All @@ -110,4 +111,4 @@ public CommonResponse<String> deleteAllFoldersWithProblems() {

return CommonResponse.success("폴더가 성공적으로 삭제되었습니다.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public FolderResponseDto findRootFolder(Long userId) {
}

@Transactional(readOnly = true)
public FolderResponseDto findFolder(Long folderId) {
public FolderResponseDto findFolderForAdmin(Long folderId) {
Folder folder = folderRepository.findFolderWithDetailsByFolderId(folderId)
.orElseThrow(() -> new ApplicationException(FolderErrorCase.FOLDER_NOT_FOUND));

Expand All @@ -68,11 +68,26 @@ public FolderResponseDto findFolder(Long folderId) {
}

@Transactional(readOnly = true)
public Folder findFolderEntity(Long folderId) {
public FolderResponseDto findFolder(Long folderId, Long userId) {
Folder folder = findFolderWithDetailsOwnedByUser(folderId, userId);

List<Long> problemIdList = folderRepository.findProblemIdsByFolder(folder.getId());
return FolderResponseDto.from(folder, problemIdList);
}

@Transactional(readOnly = true)
private Folder findFolderEntity(Long folderId) {
return folderRepository.findById(folderId)
.orElseThrow(() -> new ApplicationException(FolderErrorCase.FOLDER_NOT_FOUND));
}

@Transactional(readOnly = true)
public Folder findFolderEntity(Long folderId, Long userId) {
Folder folder = findFolderEntity(folderId);
validateFolderOwner(folder, userId);
return folder;
}

@Transactional(readOnly = true)
public List<FolderThumbnailResponseDto> findAllUserFolderThumbnails(Long userId) {
List<Folder> folderList = folderRepository.findAllByUserId(userId);
Expand Down Expand Up @@ -104,7 +119,7 @@ private void createDefaultSubFolder(Folder rootFolder, Long userId) {

public Long createFolder(FolderRegisterDto folderRegisterDto, Long userId) {
Folder folder = Folder.from(folderRegisterDto, userId);
Folder parentFolder = findFolderEntity(folderRegisterDto.parentFolderId());
Folder parentFolder = findFolderEntity(folderRegisterDto.parentFolderId(), userId);

folder.updateParentFolder(parentFolder);
folderRepository.save(folder);
Expand All @@ -114,23 +129,23 @@ public Long createFolder(FolderRegisterDto folderRegisterDto, Long userId) {
}

public void updateFolder(FolderRegisterDto folderRegisterDto, Long userId) {
Folder folder = findFolderEntity(folderRegisterDto.folderId());
Folder folder = findFolderEntity(folderRegisterDto.folderId(), userId);
folder.updateFolderInfo(folderRegisterDto);

if (folderRegisterDto.parentFolderId() != null && folder.getParentFolder() != null) {
Folder newParentFolder = findFolderEntity(folderRegisterDto.parentFolderId());
Folder newParentFolder = findFolderEntity(folderRegisterDto.parentFolderId(), userId);

folder.updateParentFolder(newParentFolder);
}

log.info("userId : {} update folder id: {}", userId, folder.getId());
}

public void deleteFoldersWithProblems(List<Long> folderIds) {
public void deleteFoldersWithProblems(Long userId, List<Long> folderIds) {
// 삭제할 모든 폴더의 ID 조회 (하위 폴더 포함)
Set<Long> allFolderIds = getAllFolderIdsIncludingSubFolders(folderIds);
Set<Long> allFolderIds = getAllFolderIdsIncludingSubFolders(userId, folderIds);

problemService.deleteAllByFolderIds(allFolderIds);
problemService.deleteAllByFolderIds(userId, allFolderIds);

deleteAllByFolderIds(allFolderIds);
}
Expand All @@ -142,12 +157,11 @@ public void deleteAllUserFoldersWithProblems(Long userId) {
deleteAllUserFolders(userId);
}

public Set<Long> getAllFolderIdsIncludingSubFolders(List<Long> folderIds) {
public Set<Long> getAllFolderIdsIncludingSubFolders(Long userId, List<Long> folderIds) {
Set<Long> allFolderIds = new HashSet<>();

for (Long folderId : folderIds) {
Folder folder = folderRepository.findById(folderId)
.orElseThrow(() -> new ApplicationException(FolderErrorCase.FOLDER_NOT_FOUND));
Folder folder = findFolderEntity(folderId, userId);

if (folder.getParentFolder() == null) {
throw new ApplicationException(FolderErrorCase.ROOT_FOLDER_CANNOT_REMOVE);
Expand All @@ -170,7 +184,7 @@ private Set<Long> getSubFolderIdsRecursive(Folder folder) {
return subFolderIds;
}

public void deleteAllByFolderIds(Collection<Long> folderIds) {
private void deleteAllByFolderIds(Collection<Long> folderIds) {
List<Folder> foldersToDelete = folderRepository.findAllById(folderIds);
folderRepository.deleteAll(foldersToDelete);
}
Expand All @@ -190,7 +204,8 @@ public void deleteAllUserFolders(Long userId) {
* @return 커서 기반 페이징 응답
*/
@Transactional(readOnly = true)
public CursorPageResponse<FolderThumbnailResponseDto> findSubFoldersWithCursor(Long folderId, Long cursor, int size) {
public CursorPageResponse<FolderThumbnailResponseDto> findSubFoldersWithCursor(Long folderId, Long userId, Long cursor, int size) {
findFolderEntity(folderId, userId);
List<Folder> folders = folderRepository.findSubFoldersWithCursor(folderId, cursor, size);

boolean hasNext = folders.size() > size;
Expand All @@ -205,6 +220,19 @@ public CursorPageResponse<FolderThumbnailResponseDto> findSubFoldersWithCursor(L
return CursorPageResponse.of(dtoList, nextCursor, hasNext, size);
}

private Folder findFolderWithDetailsOwnedByUser(Long folderId, Long userId) {
Folder folder = folderRepository.findFolderWithDetailsByFolderId(folderId)
.orElseThrow(() -> new ApplicationException(FolderErrorCase.FOLDER_NOT_FOUND));
validateFolderOwner(folder, userId);
return folder;
}

private void validateFolderOwner(Folder folder, Long userId) {
if (!Objects.equals(folder.getUserId(), userId)) {
throw new ApplicationException(FolderErrorCase.FOLDER_USER_UNMATCHED);
}
}

/**
* V2 API: 커서 기반 유저의 모든 폴더 썸네일 조회
* @param userId 유저 ID
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public CommonResponse<PracticeNoteDetailResponseDto> getPracticeDetail(@PathVari
Long userId = (Long) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
log.info("userId: {} get problem practice for practice id: {}", userId, practiceId);

return CommonResponse.success(practiceNoteService.findPracticeNoteDetail(practiceId));
return CommonResponse.success(practiceNoteService.findPracticeNoteDetail(practiceId, userId));
}

// ✅ 사용자의 모든 복습 리스트 썸네일 조회
Expand Down Expand Up @@ -94,7 +94,7 @@ public CommonResponse<String> updatePractice(@RequestBody PracticeNoteUpdateDto
@DeleteMapping("")
public CommonResponse<String> deletePractices(@RequestBody PracticeNoteDeleteRequestDto practiceNoteDeleteRequestDto) {
Long userId = (Long) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
practiceNoteService.deletePractices(practiceNoteDeleteRequestDto.deletePracticeIdList());
practiceNoteService.deletePractices(userId, practiceNoteDeleteRequestDto.deletePracticeIdList());

return CommonResponse.success("선택한 복습 노트가 삭제되었습니다.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
@RequiredArgsConstructor
public enum PracticeNoteErrorCase implements ErrorCase {

PRACTICE_NOTE_NOT_FOUND(404, 6001, "복습 노트를 찾을 수 없습니다.");
PRACTICE_NOTE_NOT_FOUND(404, 6001, "복습 노트를 찾을 수 없습니다."),

PRACTICE_NOTE_USER_UNMATCHED(403, 6002, "복습 노트를 소유한 유저가 아닙니다.");

private final Integer httpStatusCode;
private final Integer errorCode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
Expand All @@ -56,10 +57,13 @@ public class PracticeNoteService {

private final UserRepository userRepository;

private PracticeNote getPracticeEntity(Long practiceId){
private PracticeNote getPracticeEntity(Long practiceId, Long userId){

return practiceNoteRepository.findById(practiceId)
PracticeNote practiceNote = practiceNoteRepository.findById(practiceId)
.orElseThrow(() -> new ApplicationException(PracticeNoteErrorCase.PRACTICE_NOTE_NOT_FOUND));

validatePracticeOwner(practiceNote, userId);
return practiceNote;
}

public Long registerPractice(PracticeNoteRegisterDto practiceNoteRegisterDto, Long userId) {
Expand All @@ -69,7 +73,7 @@ public Long registerPractice(PracticeNoteRegisterDto practiceNoteRegisterDto, Lo

if (practiceNoteRegisterDto.problemIdList() != null) {
practiceNoteRegisterDto.problemIdList().forEach(problemId ->
addProblemToPractice(practiceNote, problemId));
addProblemToPractice(practiceNote, problemId, userId));
}

if(practiceNoteRegisterDto.practiceNotification() != null) {
Expand All @@ -94,10 +98,11 @@ public void registerPracticeNotification(Long userId, Long practiceId, String pr
}

@Transactional(readOnly = true)
public PracticeNoteDetailResponseDto findPracticeNoteDetail(Long practiceId){
public PracticeNoteDetailResponseDto findPracticeNoteDetail(Long practiceId, Long userId){
log.info("find practiceId: {}", practiceId);
PracticeNote practiceNote = practiceNoteRepository.findPracticeNoteWithDetails(practiceId)
.orElseThrow(() -> new ApplicationException(PracticeNoteErrorCase.PRACTICE_NOTE_NOT_FOUND));
validatePracticeOwner(practiceNote, userId);

List<Long> problemIdList = practiceNoteRepository.findProblemIdListByPracticeNoteId(practiceId);

Expand Down Expand Up @@ -132,7 +137,7 @@ public List<PracticeNoteDetailResponseDto> findAllPracticesByUser(Long userId){
}

public void addPracticeNoteCount(Long userId, Long practiceId) {
PracticeNote practiceNote = getPracticeEntity(practiceId);
PracticeNote practiceNote = getPracticeEntity(practiceId, userId);
practiceNote.updatePracticeNoteCount();

// 복습노트 사용 미션 등록
Expand All @@ -143,15 +148,15 @@ public void addPracticeNoteCount(Long userId, Long practiceId) {

public void updatePracticeInfo(Long userId, PracticeNoteUpdateDto practiceNoteUpdateDto) {
Long practiceId = practiceNoteUpdateDto.practiceNoteId();
PracticeNote practiceNote = getPracticeEntity(practiceId);
PracticeNote practiceNote = getPracticeEntity(practiceId, userId);

practiceNote.updateTitle(practiceNoteUpdateDto.practiceTitle());

practiceNote.updateNotification(PracticeNotification.from(practiceNoteUpdateDto.practiceNotification()));

if (!practiceNoteUpdateDto.addProblemIdList().isEmpty()) {
practiceNoteUpdateDto.addProblemIdList().forEach(problemId -> {
addProblemToPractice(practiceNote, problemId);
addProblemToPractice(practiceNote, problemId, userId);
});
}

Expand All @@ -176,32 +181,40 @@ public void updatePracticeNotification(Long userId, Long practiceId, String prac

}

public void deletePractice(Long practiceId) {
public void deletePractice(Long practiceId, Long userId) {
getPracticeEntity(practiceId, userId);
deletePracticeWithoutOwnerCheck(practiceId);
}

private void deletePracticeWithoutOwnerCheck(Long practiceId) {
List<ProblemPracticeNoteMapping> problemPracticeNoteMappingList = problemPracticeNoteMappingRepository.findAllByPracticeNoteId(practiceId);
problemPracticeNoteMappingList.forEach(ProblemPracticeNoteMapping::removeMappingFromProblemAndPractice);

practiceNoteRepository.deleteById(practiceId);
log.info("practiceId: {} has deleted", practiceId);
}

public void deletePractices(List<Long> practiceIdList) {
practiceIdList.forEach(this::deletePractice);
public void deletePractices(Long userId, List<Long> practiceIdList) {
practiceIdList.forEach(practiceId -> deletePractice(practiceId, userId));
}

public void deleteAllPracticesByUser(Long userId) {
List<Long> practiceIdList = practiceNoteRepository.findAllPracticeIdsByUserId(userId);

deletePractices(practiceIdList);
practiceIdList.forEach(this::deletePracticeWithoutOwnerCheck);
log.info("userId: {} has delete all practices", userId);
}

private void addProblemToPractice(PracticeNote practiceNote, Long problemId) {
private void addProblemToPractice(PracticeNote practiceNote, Long problemId, Long userId) {

boolean exists = practiceNoteRepository.checkProblemAlreadyMatchingWithPractice(practiceNote.getId(), problemId);

if (!exists) {
Problem problem = problemRepository.findById(problemId)
.orElseThrow(() -> new ApplicationException(ProblemErrorCase.PROBLEM_NOT_FOUND));
if (!Objects.equals(problem.getUserId(), userId)) {
throw new ApplicationException(ProblemErrorCase.PROBLEM_USER_UNMATCHED);
}

ProblemPracticeNoteMapping problemPracticeNoteMapping = ProblemPracticeNoteMapping.from();
problemPracticeNoteMapping.addMappingToProblemAndPractice(problem, practiceNote);
Expand All @@ -221,6 +234,12 @@ public void deleteProblemsFromAllPractice(List<Long> deleteProblemIdList) {
practiceNoteRepository.deleteProblemsFromAllPractice(deleteProblemIdList);
}

private void validatePracticeOwner(PracticeNote practiceNote, Long userId) {
if (!Objects.equals(practiceNote.getUserId(), userId)) {
throw new ApplicationException(PracticeNoteErrorCase.PRACTICE_NOTE_USER_UNMATCHED);
}
}

/**
* V2 API: 커서 기반 복습노트 썸네일 조회
* @param userId 유저 ID
Expand Down
Loading
Loading