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 @@ -3,14 +3,16 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.tuna.zoopzoop.backend.domain.archive.folder.dto.FolderResponse;
import org.tuna.zoopzoop.backend.domain.archive.folder.dto.reqBodyForCreateFolder;
import org.tuna.zoopzoop.backend.domain.archive.folder.dto.resBodyForCreateFolder;
import org.tuna.zoopzoop.backend.domain.archive.folder.service.FolderService;
import org.tuna.zoopzoop.backend.domain.datasource.dto.FolderFilesDto;
import org.tuna.zoopzoop.backend.domain.member.entity.Member;
import org.tuna.zoopzoop.backend.global.rsData.RsData;
import org.tuna.zoopzoop.backend.global.security.StubAuthUtil;
import org.tuna.zoopzoop.backend.global.security.jwt.CustomUserDetails;

import java.util.HashMap;
import java.util.List;
Expand All @@ -28,13 +30,13 @@ public class FolderController {
* @param rq reqBodyForCreateFolder
* @return resBodyForCreateFolder
*/
@PostMapping("")
@PostMapping
public RsData<resBodyForCreateFolder> createFolder(
@Valid @RequestBody reqBodyForCreateFolder rq
@Valid @RequestBody reqBodyForCreateFolder rq,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
// 임시 인증 정보
Integer currentMemberId = StubAuthUtil.currentMemberId();
FolderResponse createFile = folderService.createFolderForPersonal(currentMemberId, rq.folderName());
Member member = userDetails.getMember();
FolderResponse createFile = folderService.createFolderForPersonal(member.getId(), rq.folderName());

resBodyForCreateFolder rs = new resBodyForCreateFolder(createFile.folderName(), createFile.folderId());

Expand All @@ -43,16 +45,19 @@ public RsData<resBodyForCreateFolder> createFolder(
rq.folderName() + " 폴더가 생성됐습니다.",
rs
);

}

/**
* 내 PersonalArchive 안의 folder 삭제
* @param folderId 삭제할 folderId
*/
@DeleteMapping("/{folderId}")
public ResponseEntity<Map<String, Object>> deleteFolder(@PathVariable Integer folderId) {
String deletedFolderName = folderService.deleteFolder(folderId);
public ResponseEntity<Map<String, Object>> deleteFolder(
@PathVariable Integer folderId,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
Member member = userDetails.getMember();
String deletedFolderName = folderService.deleteFolder(member.getId(), folderId);

Map<String, Object> body = new HashMap<>();
body.put("status", 200);
Expand All @@ -70,10 +75,12 @@ public ResponseEntity<Map<String, Object>> deleteFolder(@PathVariable Integer fo
@PatchMapping("/{folderId}")
public ResponseEntity<Map<String, Object>> updateFolderName(
@PathVariable Integer folderId,
@RequestBody Map<String, String> body
@RequestBody Map<String, String> body,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
Member member = userDetails.getMember();
String newName = body.get("folderName");
String updatedName = folderService.updateFolderName(folderId, newName);
String updatedName = folderService.updateFolderName(member.getId(), folderId, newName);

Map<String, Object> response = new HashMap<>();
response.put("status", 200);
Expand All @@ -87,13 +94,12 @@ public ResponseEntity<Map<String, Object>> updateFolderName(
* 개인 아카이브의 폴더 이름 전부 조회
* "default", "폴더1", "폴더2"
*/
@GetMapping("")
public ResponseEntity<?> getFolders() {
// 로그인된 멤버 ID 가져오기
Integer currentMemberId = StubAuthUtil.currentMemberId();

// 내 personal archive 안의 폴더 조회
List<FolderResponse> folders = folderService.getFoldersForPersonal(currentMemberId);
@GetMapping
public ResponseEntity<?> getFolders(
@AuthenticationPrincipal CustomUserDetails userDetails
) {
Member member = userDetails.getMember();
List<FolderResponse> folders = folderService.getFoldersForPersonal(member.getId());

return ResponseEntity.ok(
Map.of(
Expand All @@ -108,10 +114,12 @@ public ResponseEntity<?> getFolders() {
* 폴더(내 PersonalArchive 소속) 안의 파일 목록 조회
*/
@GetMapping("/{folderId}/files")
public ResponseEntity<?> getFilesInFolder(@PathVariable Integer folderId) {
Integer currentMemberId = StubAuthUtil.currentMemberId();

FolderFilesDto rs = folderService.getFilesInFolderForPersonal(currentMemberId, folderId);
public ResponseEntity<?> getFilesInFolder(
@PathVariable Integer folderId,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
Member member = userDetails.getMember();
FolderFilesDto rs = folderService.getFilesInFolderForPersonal(member.getId(), folderId);

return ResponseEntity.ok(
Map.of(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.tuna.zoopzoop.backend.domain.archive.archive.entity.Archive;
import org.tuna.zoopzoop.backend.domain.archive.folder.entity.Folder;

Expand Down Expand Up @@ -45,4 +46,16 @@ public interface FolderRepository extends JpaRepository<Folder, Integer>{
and f.isDefault = true
""")
Optional<Folder> findDefaultFolderByMemberId(Integer memberId);

// 한 번의 조인으로 존재 + 소유권(memberId) 검증
@Query("""
select f
from Folder f
join f.archive a
join PersonalArchive pa on pa.archive = a
where f.id = :folderId
and pa.member.id = :memberId
""")
Optional<Folder> findByIdAndMemberId(@Param("folderId") Integer folderId,
@Param("memberId") Integer memberId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,8 @@ public class FolderService {
*/
@Transactional
public FolderResponse createFolderForPersonal(Integer currentMemberId, String folderName) {
if (folderName == null || folderName.trim().isEmpty()) {
if (folderName == null || folderName.trim().isEmpty())
throw new IllegalArgumentException("폴더 이름은 비어 있을 수 없습니다.");
}

Member member = memberRepository.findById(currentMemberId)
.orElseThrow(() -> new IllegalArgumentException("멤버를 찾을 수 없습니다."));
Expand Down Expand Up @@ -115,8 +114,9 @@ private static String pickNextAvailable(String file, List<String> existing) {
* soft delete 아직 구현 X
*/
@Transactional
public String deleteFolder(Integer folderId) {
Folder folder = folderRepository.findById(folderId)
public String deleteFolder(Integer currentId, Integer folderId) {
// 공격자에게 리소스 존재 여부를 노출 X (존재하지 않음 / 남의 폴더)
Folder folder = folderRepository.findByIdAndMemberId(folderId, currentId)
.orElseThrow(() -> new NoResultException("존재하지 않는 폴더입니다."));

if (folder.isDefault())
Expand All @@ -131,8 +131,8 @@ public String deleteFolder(Integer folderId) {
* folderId에 해당하는 이름 변경
*/
@Transactional
public String updateFolderName(Integer folderId, String newName) {
Folder folder = folderRepository.findById(folderId)
public String updateFolderName(Integer currentId, Integer folderId, String newName) {
Folder folder = folderRepository.findByIdAndMemberId(folderId, currentId)
.orElseThrow(() -> new NoResultException("존재하지 않는 폴더입니다."));

// 같은 아카이브 내에서 중복 폴더 이름 확인
Expand Down Expand Up @@ -173,18 +173,19 @@ public List<FolderResponse> getFoldersForPersonal(Integer memberId) {
*/
@Transactional(readOnly = true)
public FolderFilesDto getFilesInFolderForPersonal(Integer memberId, Integer folderId) {
Folder folder = folderRepository.findById(folderId)
Folder folder = folderRepository.findByIdAndMemberId(folderId, memberId)
.orElseThrow(() -> new NoResultException("존재하지 않는 폴더입니다."));

var files = dataSourceRepository.findAllByFolder(folder).stream()
.map(ds -> new FileSummary(
ds.getId(),
ds.getTitle(),
ds.getCreateDate(), // LocalDateTime
ds.getDataCreatedDate(), // LocalDate
ds.getSummary(),
ds.getSourceUrl(),
ds.getImageUrl(),
ds.getTags() == null ? List.of() : ds.getTags()
ds.getTags() == null ? List.of() : ds.getTags(),
ds.getCategory() == null ? null : ds.getCategory().toString()
))
.toList();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.tuna.zoopzoop.backend.domain.datasource.dto.*;
import org.tuna.zoopzoop.backend.domain.datasource.service.DataSourceService;
import org.tuna.zoopzoop.backend.global.security.StubAuthUtil;
import org.tuna.zoopzoop.backend.domain.member.entity.Member;
import org.tuna.zoopzoop.backend.global.security.jwt.CustomUserDetails;

import java.util.HashMap;
import java.util.Map;
Expand All @@ -24,10 +26,14 @@ public class DatasourceController {
* folderId 등록될 폴더 위치(null 이면 default)
*/
@PostMapping("")
public ResponseEntity<?> createDataSource(@Valid @RequestBody reqBodyForCreateDataSource rq) {
public ResponseEntity<?> createDataSource(
@Valid @RequestBody reqBodyForCreateDataSource rq,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
// 로그인된 멤버 Id 사용
Member member = userDetails.getMember();
Integer currentMemberId = member.getId();

//임시 인증 정보
Integer currentMemberId = StubAuthUtil.currentMemberId();
int rs = dataSourceService.createDataSource(currentMemberId, rq.sourceUrl(), rq.folderId());
return ResponseEntity.ok()
.body(
Expand All @@ -39,8 +45,12 @@ public ResponseEntity<?> createDataSource(@Valid @RequestBody reqBodyForCreateDa
* 자료 단건 삭제
*/
@DeleteMapping("/{dataSourceId}")
public ResponseEntity<Map<String, Object>> delete(@PathVariable Integer dataSourceId) {
int deletedId = dataSourceService.deleteById(dataSourceId);
public ResponseEntity<Map<String, Object>> delete(
@PathVariable Integer dataSourceId,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
Member member = userDetails.getMember();
int deletedId = dataSourceService.deleteById(member.getId(), dataSourceId);
return ResponseEntity.ok(
Map.of(
"status", 200,
Expand All @@ -55,11 +65,12 @@ public ResponseEntity<Map<String, Object>> delete(@PathVariable Integer dataSour
*/
@PostMapping("/delete")
public ResponseEntity<Map<String, Object>> deleteMany(
@Valid @RequestBody reqBodyForDeleteMany body
@Valid @RequestBody reqBodyForDeleteMany body,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
dataSourceService.deleteMany(body.dataSourceId());
Member member = userDetails.getMember();
dataSourceService.deleteMany(member.getId(), body.dataSourceId());

// Map.of 는 null 불가 → LinkedHashMap 사용
Map<String, Object> res = new java.util.LinkedHashMap<>();
res.put("status", 200);
res.put("msg", "복수개의 자료가 삭제됐습니다.");
Expand All @@ -75,9 +86,11 @@ public ResponseEntity<Map<String, Object>> deleteMany(
@PatchMapping("/{dataSourceId}/move")
public ResponseEntity<?> moveDataSource(
@PathVariable Integer dataSourceId,
@Valid @RequestBody reqBodyForMoveDataSource rq
@Valid @RequestBody reqBodyForMoveDataSource rq,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
Integer currentMemberId = StubAuthUtil.currentMemberId();
Member member = userDetails.getMember();
Integer currentMemberId = member.getId();

DataSourceService.MoveResult result =
dataSourceService.moveDataSource(currentMemberId, dataSourceId, rq.folderId());
Expand All @@ -101,8 +114,12 @@ public ResponseEntity<?> moveDataSource(
* 자료 다건 이동
*/
@PatchMapping("/move")
public ResponseEntity<?> moveMany(@Valid @RequestBody reqBodyForMoveMany rq) {
Integer currentMemberId = StubAuthUtil.currentMemberId();
public ResponseEntity<?> moveMany(
@Valid @RequestBody reqBodyForMoveMany rq,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
Member member = userDetails.getMember();
Integer currentMemberId = member.getId();

dataSourceService.moveDataSources(currentMemberId, rq.folderId(), rq.dataSourceId());

Expand All @@ -122,7 +139,8 @@ public ResponseEntity<?> moveMany(@Valid @RequestBody reqBodyForMoveMany rq) {
@PatchMapping("/{dataSourceId}")
public ResponseEntity<?> updateDataSource(
@PathVariable Integer dataSourceId,
@Valid @RequestBody reqBodyForUpdateDataSource body
@Valid @RequestBody reqBodyForUpdateDataSource body,
@AuthenticationPrincipal CustomUserDetails userDetails
) {
// title, summary 둘 다 비어있으면 의미 없는 요청 → 400
boolean noTitle = (body.title() == null || body.title().isBlank());
Expand All @@ -131,7 +149,8 @@ public ResponseEntity<?> updateDataSource(
throw new IllegalArgumentException("변경할 값이 없습니다. title 또는 summary 중 하나 이상을 전달하세요.");
}

Integer updatedId = dataSourceService.updateDataSource(dataSourceId, body.title(), body.summary());
Member member = userDetails.getMember();
Integer updatedId = dataSourceService.updateDataSource(member.getId(), dataSourceId, body.title(), body.summary()); // CHANGED
String msg = updatedId + "번 자료가 수정됐습니다.";
return ResponseEntity.ok(
new ApiResponse<>(200, msg, new resBodyForUpdateDataSource(updatedId))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

import org.tuna.zoopzoop.backend.domain.datasource.entity.Tag;

import java.time.LocalDateTime;
import java.time.LocalDate;
import java.util.List;

public record FileSummary(
Integer dataSourceId,
String title,
LocalDateTime createdAt,
LocalDate createdAt,
String summary,
String sourceUrl,
String imageUrl,
List<Tag> tags
List<Tag> tags,
String category
) {}
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,35 @@

import java.util.Collection;
import java.util.List;
import java.util.Optional;

@Repository
public interface DataSourceRepository extends JpaRepository<DataSource, Integer> {
List<DataSource> findAllByFolder(Folder folder);

@Query("select d.id from DataSource d where d.id in ?1")
java.util.List<Integer> findExistingIds(Collection<Integer> ids);

List<DataSource> findAllByIdIn(Collection<Integer> ids);

// CHANGED: 특정 멤버(개인 아카이브 소유자) 범위에서 id로 조회 (ownership check)
@Query("""
select d from DataSource d
join d.folder f
join f.archive a
join PersonalArchive pa on pa.archive = a
where d.id = :id
and pa.member.id = :memberId
""")
Optional<DataSource> findByIdAndMemberId(@Param("id") Integer id, @Param("memberId") Integer memberId);

// CHANGED: 여러 id 중에서 해당 member 소유인 id만 반환 (다건 삭제/검증용)
@Query("""
select d.id from DataSource d
join d.folder f
join f.archive a
join PersonalArchive pa on pa.archive = a
where pa.member.id = :memberId
and d.id in :ids
""")
List<Integer> findExistingIdsInMember(@Param("memberId") Integer memberId, @Param("ids") Collection<Integer> ids);

}

Loading