diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/repository/DataSourceQRepositoryImpl.java b/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/repository/DataSourceQRepositoryImpl.java index a652105a..a6f67dca 100644 --- a/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/repository/DataSourceQRepositoryImpl.java +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/repository/DataSourceQRepositoryImpl.java @@ -18,10 +18,11 @@ import org.tuna.zoopzoop.backend.domain.datasource.dto.DataSourceSearchCondition; import org.tuna.zoopzoop.backend.domain.datasource.dto.DataSourceSearchItem; import org.tuna.zoopzoop.backend.domain.datasource.entity.Category; +import org.tuna.zoopzoop.backend.domain.datasource.entity.DataSource; import org.tuna.zoopzoop.backend.domain.datasource.entity.QDataSource; import org.tuna.zoopzoop.backend.domain.datasource.entity.QTag; -import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -89,7 +90,7 @@ public Page search(Integer memberId, DataSourceSearchCondi // content JPAQuery contentQuery = queryFactory - .select(ds.id, ds.title, ds.dataCreatedDate, ds.summary, ds.source, ds.sourceUrl, ds.imageUrl, ds.category) + .select(ds.id, ds.title, ds.createDate, ds.summary, ds.source, ds.sourceUrl, ds.imageUrl, ds.category) .from(ds) .join(ds.folder, folder) .join(pa).on(pa.archive.eq(folder.archive)) @@ -97,7 +98,7 @@ public Page search(Integer memberId, DataSourceSearchCondi List> orderSpecifiers = toOrderSpecifiers(pageable.getSort()); if (!orderSpecifiers.isEmpty()) contentQuery.orderBy(orderSpecifiers.toArray(new OrderSpecifier[0])); - else contentQuery.orderBy(ds.createDate.desc()); + else contentQuery.orderBy(ds.createDate.desc()); // 기본 정렬: 생성일시 내림차순 List tuples = contentQuery.offset(pageable.getOffset()).limit(pageable.getPageSize()).fetch(); Long totalCount = countQuery.fetchOne(); @@ -123,7 +124,7 @@ public Page search(Integer memberId, DataSourceSearchCondi .map(row -> new DataSourceSearchItem( row.get(ds.id), row.get(ds.title), - row.get(ds.dataCreatedDate), + row.get(ds.createDate).toLocalDate(), row.get(ds.summary), row.get(ds.source), row.get(ds.sourceUrl), @@ -140,8 +141,8 @@ public Page search(Integer memberId, DataSourceSearchCondi private List> toOrderSpecifiers(Sort sort) { if (sort == null || sort.isEmpty()) return List.of(); - PathBuilder root = - new PathBuilder<>(org.tuna.zoopzoop.backend.domain.datasource.entity.DataSource.class, "dataSource"); + PathBuilder root = + new PathBuilder<>(DataSource.class, "dataSource"); List> specs = new ArrayList<>(); for (Sort.Order o : sort) { @@ -149,7 +150,7 @@ private List> toOrderSpecifiers(Sort sort) { switch (o.getProperty()) { case "title" -> specs.add(new OrderSpecifier<>(dir, root.getString("title"))); case "createdAt" -> specs.add( - new OrderSpecifier<>(dir, root.getDate("dataCreatedDate", LocalDate.class)) + new OrderSpecifier<>(dir, root.getDateTime("createDate", LocalDateTime.class)) ); default -> { /* 무시 */ } } @@ -207,7 +208,8 @@ public Page searchInArchive(Integer archiveId, DataSourceS // content JPAQuery contentQuery = queryFactory - .select(ds.id, ds.title, ds.dataCreatedDate, ds.summary, ds.source, ds.sourceUrl, ds.imageUrl, ds.category) + // 이미 공유 쪽은 createDate 사용 중 + .select(ds.id, ds.title, ds.createDate, ds.summary, ds.source, ds.sourceUrl, ds.imageUrl, ds.category) .from(ds) .join(ds.folder, folder) .where(where.and(scope)); @@ -237,7 +239,8 @@ public Page searchInArchive(Integer archiveId, DataSourceS .map(row -> new DataSourceSearchItem( row.get(ds.id), row.get(ds.title), - row.get(ds.dataCreatedDate), + // LocalDateTime(createDate) → LocalDate + row.get(ds.createDate).toLocalDate(), row.get(ds.summary), row.get(ds.source), row.get(ds.sourceUrl), diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/service/DataSourceService.java b/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/service/DataSourceService.java index 34dfb9db..9eceb0ff 100644 --- a/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/service/DataSourceService.java +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/service/DataSourceService.java @@ -181,7 +181,7 @@ public void moveMany(List ids, int targetFolderId) { public void hardDeleteOne(int dataSourceId) { DataSource ds = dataSourceRepository.findById(dataSourceId) .orElseThrow(() -> new NoResultException("존재하지 않는 자료입니다.")); - deleteOwnedImageIfAny(ds); +// deleteOwnedImageIfAny(ds); dataSourceRepository.delete(ds); } @@ -190,7 +190,7 @@ public void hardDeleteMany(List ids) { if (ids == null || ids.isEmpty()) return; List list = dataSourceRepository.findAllById(ids); if (list.size() != ids.size()) throw new NoResultException("존재하지 않는 자료 포함"); - for (DataSource ds : list) deleteOwnedImageIfAny(ds); +// for (DataSource ds : list) deleteOwnedImageIfAny(ds); dataSourceRepository.deleteAll(list); } @@ -321,19 +321,15 @@ public String uploadThumbnailAndReturnFinalUrl(MultipartFile image, String key) // ===== S3 삭제 관련 유틸 ===== // 소유한 이미지가 있으면 S3에서 삭제 - private void deleteOwnedImageIfAny(DataSource ds) { - String url = ds.getImageUrl(); - if (url == null || url.isBlank()) return; - if (!isOurS3Url(url)) return; - - String key = extractKeyFromUrl(url); - if (key == null || key.isBlank()) return; - - try { - s3Service.delete(key); - } catch (Exception ignore) { - // 파일 삭제 실패로 전체 삭제를 롤백하지 않음 - // 필요하면 warn 로그 추가 + public void deleteIfOwnedByExactKey(String imageUrl, String expectedKey) { + if (imageUrl == null || imageUrl.isBlank() || expectedKey == null || expectedKey.isBlank()) return; + String key = extractKeyFromUrl(imageUrl); + if (expectedKey.equals(key)) { + try { + s3Service.delete(key); + } catch (Exception ignore) { + // S3 삭제 실패가 전체 트랜잭션을 막지 않도록 무시 + } } } diff --git a/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/service/PersonalDataSourceService.java b/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/service/PersonalDataSourceService.java index e711257a..1e9c126a 100644 --- a/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/service/PersonalDataSourceService.java +++ b/src/main/java/org/tuna/zoopzoop/backend/domain/datasource/service/PersonalDataSourceService.java @@ -16,6 +16,7 @@ import org.tuna.zoopzoop.backend.domain.datasource.dto.DataSourceSearchCondition; import org.tuna.zoopzoop.backend.domain.datasource.dto.DataSourceSearchItem; import org.tuna.zoopzoop.backend.domain.datasource.dto.UpdateOutcome; +import org.tuna.zoopzoop.backend.domain.datasource.entity.DataSource; import org.tuna.zoopzoop.backend.domain.datasource.entity.Tag; import org.tuna.zoopzoop.backend.domain.datasource.repository.DataSourceRepository; @@ -79,9 +80,13 @@ public int create(int memberId, String sourceUrl, Integer folderIdOrZero, DataSo // hard delete @Transactional public int deleteOne(int memberId, int dataSourceId) { - dataSourceRepository.findByIdAndMemberId(dataSourceId, memberId) + DataSource ds = dataSourceRepository.findByIdAndMemberId(dataSourceId, memberId) .orElseThrow(() -> new NoResultException("존재하지 않는 자료입니다.")); + // S3 삭제 + String expectedKey = domain.thumbnailKeyForPersonal(memberId, dataSourceId); + domain.deleteIfOwnedByExactKey(ds.getImageUrl(), expectedKey); + domain.hardDeleteOne(dataSourceId); return dataSourceId; } @@ -98,6 +103,13 @@ public void deleteMany(int memberId, List ids) { missing.removeAll(new HashSet<>(existing)); throw new NoResultException("존재하지 않거나 소유자가 다른 자료 ID 포함: " + missing); } + // S3 삭제 + List list = dataSourceRepository.findAllById(ids); + for (DataSource ds : list) { + String expectedKey = domain.thumbnailKeyForPersonal(memberId, ds.getId()); + domain.deleteIfOwnedByExactKey(ds.getImageUrl(), expectedKey); + } + domain.hardDeleteMany(ids); }