From ee0ede38767d106cb043b825e2a52c7431fa75d3 Mon Sep 17 00:00:00 2001 From: chanrhan Date: Thu, 20 Nov 2025 16:48:20 +0900 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20=EC=95=A8=EB=B2=94=20=EB=82=B4=20?= =?UTF-8?q?=EC=82=AC=EC=A7=84=20=EC=A1=B0=ED=9A=8C=20=EB=B0=8F=20=EC=95=A8?= =?UTF-8?q?=EB=B2=94=20=EC=A0=9C=EB=AA=A9=20=EC=88=98=EC=A0=95=20API=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../album/controller/AlbumController.java | 22 +++++++++++ .../album/dto/request/AlbumCreateRequest.java | 2 +- .../dto/request/AlbumTitleUpdateRequest.java | 9 +++++ .../dto/response/AlbumCreateResponse.java | 2 +- .../album/dto/response/AlbumsResponse.java | 2 +- .../photoliner/domain/album/model/Album.java | 22 ++++++++++- .../domain/album/model/AlbumItem.java | 26 +++++++++++++ .../photoliner/domain/album/model/Albums.java | 12 ------ .../album/repository/AlbumRepository.java | 3 ++ .../domain/album/service/AlbumService.java | 20 +++++++++- .../photo/controller/PhotoController.java | 11 +----- .../domain/photo/infra/ExifExtractor.java | 2 +- .../domain/photo/infra/FileStorage.java | 4 +- .../domain/photo/model/AlbumPhoto.java | 39 ------------------- .../domain/photo/model/AlbumPhotos.java | 22 ----------- .../photoliner/domain/photo/model/Photo.java | 15 +++---- .../photoliner/domain/photo/model/Photos.java | 12 +++++- .../repository/AlbumPhotoRepository.java | 17 -------- .../photo/repository/PhotoRepository.java | 10 ++--- .../domain/photo/service/PhotoService.java | 28 +++++++------ .../global/code/ApiResponseCode.java | 4 +- .../db/migration/V6__alter_albums_table.sql | 2 + 22 files changed, 145 insertions(+), 141 deletions(-) create mode 100644 src/main/java/kr/kro/photoliner/domain/album/dto/request/AlbumTitleUpdateRequest.java create mode 100644 src/main/java/kr/kro/photoliner/domain/album/model/AlbumItem.java delete mode 100644 src/main/java/kr/kro/photoliner/domain/album/model/Albums.java delete mode 100644 src/main/java/kr/kro/photoliner/domain/photo/model/AlbumPhoto.java delete mode 100644 src/main/java/kr/kro/photoliner/domain/photo/model/AlbumPhotos.java delete mode 100644 src/main/java/kr/kro/photoliner/domain/photo/repository/AlbumPhotoRepository.java create mode 100644 src/main/resources/db/migration/V6__alter_albums_table.sql diff --git a/src/main/java/kr/kro/photoliner/domain/album/controller/AlbumController.java b/src/main/java/kr/kro/photoliner/domain/album/controller/AlbumController.java index f4c3c89..c3c6930 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/controller/AlbumController.java +++ b/src/main/java/kr/kro/photoliner/domain/album/controller/AlbumController.java @@ -3,9 +3,11 @@ import jakarta.validation.Valid; import kr.kro.photoliner.domain.album.dto.request.AlbumCreateRequest; import kr.kro.photoliner.domain.album.dto.request.AlbumDeleteRequest; +import kr.kro.photoliner.domain.album.dto.request.AlbumTitleUpdateRequest; import kr.kro.photoliner.domain.album.dto.response.AlbumCreateResponse; import kr.kro.photoliner.domain.album.dto.response.AlbumsResponse; import kr.kro.photoliner.domain.album.service.AlbumService; +import kr.kro.photoliner.domain.photo.dto.response.PhotosResponse; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -14,6 +16,8 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -43,6 +47,15 @@ public ResponseEntity getAlbums( return ResponseEntity.ok(albumService.getAlbums(userId, pageable)); } + @PatchMapping("/{albumId}/title") + public ResponseEntity updateAlbumTitle( + @PathVariable Long albumId, + @RequestBody @Valid AlbumTitleUpdateRequest request + ) { + albumService.updateAlbumTitle(albumId, request); + return ResponseEntity.noContent().build(); + } + @DeleteMapping public ResponseEntity deletePhoto( @Valid @RequestBody AlbumDeleteRequest request @@ -50,4 +63,13 @@ public ResponseEntity deletePhoto( albumService.deleteAlbums(request); return ResponseEntity.noContent().build(); } + + @GetMapping("/{albumId}/photos") + public ResponseEntity getAlbumItems( + @PathVariable Long albumId, + @RequestParam Long userId, + @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable + ) { + return ResponseEntity.ok(albumService.getAlbumItems(userId, albumId, pageable)); + } } diff --git a/src/main/java/kr/kro/photoliner/domain/album/dto/request/AlbumCreateRequest.java b/src/main/java/kr/kro/photoliner/domain/album/dto/request/AlbumCreateRequest.java index 87476fd..953c4dd 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/dto/request/AlbumCreateRequest.java +++ b/src/main/java/kr/kro/photoliner/domain/album/dto/request/AlbumCreateRequest.java @@ -7,6 +7,6 @@ public record AlbumCreateRequest( @NotNull Long userId, @NotEmpty - String name + String title ) { } diff --git a/src/main/java/kr/kro/photoliner/domain/album/dto/request/AlbumTitleUpdateRequest.java b/src/main/java/kr/kro/photoliner/domain/album/dto/request/AlbumTitleUpdateRequest.java new file mode 100644 index 0000000..926b39f --- /dev/null +++ b/src/main/java/kr/kro/photoliner/domain/album/dto/request/AlbumTitleUpdateRequest.java @@ -0,0 +1,9 @@ +package kr.kro.photoliner.domain.album.dto.request; + +import jakarta.validation.constraints.NotNull; + +public record AlbumTitleUpdateRequest( + @NotNull + String title +) { +} diff --git a/src/main/java/kr/kro/photoliner/domain/album/dto/response/AlbumCreateResponse.java b/src/main/java/kr/kro/photoliner/domain/album/dto/response/AlbumCreateResponse.java index 019d19f..77de7b5 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/dto/response/AlbumCreateResponse.java +++ b/src/main/java/kr/kro/photoliner/domain/album/dto/response/AlbumCreateResponse.java @@ -10,7 +10,7 @@ public static AlbumCreateResponse from(Album album) { return new AlbumCreateResponse( new InnerAlbum( album.getId(), - album.getName() + album.getTitle() ) ); } diff --git a/src/main/java/kr/kro/photoliner/domain/album/dto/response/AlbumsResponse.java b/src/main/java/kr/kro/photoliner/domain/album/dto/response/AlbumsResponse.java index bb33ce3..22c40e2 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/dto/response/AlbumsResponse.java +++ b/src/main/java/kr/kro/photoliner/domain/album/dto/response/AlbumsResponse.java @@ -26,7 +26,7 @@ public record InnerAlbum( public static InnerAlbum from(Album album) { return new InnerAlbum( album.getId(), - album.getName() + album.getTitle() ); } } diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/Album.java b/src/main/java/kr/kro/photoliner/domain/album/model/Album.java index 1156163..4401f15 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/model/Album.java +++ b/src/main/java/kr/kro/photoliner/domain/album/model/Album.java @@ -1,5 +1,6 @@ package kr.kro.photoliner.domain.album.model; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -8,8 +9,11 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.List; import kr.kro.photoliner.common.model.BaseEntity; import kr.kro.photoliner.domain.user.model.User; import lombok.AccessLevel; @@ -31,10 +35,24 @@ public class Album extends BaseEntity { private Long id; @NotNull - @Column(name = "name", nullable = false) - private String name; + @Column(name = "title", nullable = false) + private String title; @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "user_id", referencedColumnName = "id") private User user; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) + @JoinColumn(name = "album_id") + private List items = new ArrayList<>(); + + public void updateTitle(String title) { + this.title = title; + } + + public List getPhotoIds() { + return items.stream() + .map(AlbumItem::getPhotoId) + .toList(); + } } diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/AlbumItem.java b/src/main/java/kr/kro/photoliner/domain/album/model/AlbumItem.java new file mode 100644 index 0000000..97cb47f --- /dev/null +++ b/src/main/java/kr/kro/photoliner/domain/album/model/AlbumItem.java @@ -0,0 +1,26 @@ +package kr.kro.photoliner.domain.album.model; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@AllArgsConstructor +@Table(name = "albums_photos") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class AlbumItem { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "photo_id", nullable = false) + private Long photoId; +} diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/Albums.java b/src/main/java/kr/kro/photoliner/domain/album/model/Albums.java deleted file mode 100644 index 0309497..0000000 --- a/src/main/java/kr/kro/photoliner/domain/album/model/Albums.java +++ /dev/null @@ -1,12 +0,0 @@ -package kr.kro.photoliner.domain.album.model; - -import java.util.List; - -public record Albums( - List albums -) { - - public int count() { - return albums.size(); - } -} diff --git a/src/main/java/kr/kro/photoliner/domain/album/repository/AlbumRepository.java b/src/main/java/kr/kro/photoliner/domain/album/repository/AlbumRepository.java index 861939c..b760a10 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/repository/AlbumRepository.java +++ b/src/main/java/kr/kro/photoliner/domain/album/repository/AlbumRepository.java @@ -1,5 +1,6 @@ package kr.kro.photoliner.domain.album.repository; +import java.util.Optional; import kr.kro.photoliner.domain.album.model.Album; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -10,4 +11,6 @@ public interface AlbumRepository extends JpaRepository { Album save(Album album); Page findByUserId(Long userId, Pageable pageable); + + Optional findByIdAndUserId(Long albumId, Long userId); } diff --git a/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java b/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java index 5c1df17..7a44d5f 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java +++ b/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java @@ -2,10 +2,13 @@ import kr.kro.photoliner.domain.album.dto.request.AlbumCreateRequest; import kr.kro.photoliner.domain.album.dto.request.AlbumDeleteRequest; +import kr.kro.photoliner.domain.album.dto.request.AlbumTitleUpdateRequest; import kr.kro.photoliner.domain.album.dto.response.AlbumCreateResponse; import kr.kro.photoliner.domain.album.dto.response.AlbumsResponse; import kr.kro.photoliner.domain.album.model.Album; import kr.kro.photoliner.domain.album.repository.AlbumRepository; +import kr.kro.photoliner.domain.photo.dto.response.PhotosResponse; +import kr.kro.photoliner.domain.photo.service.PhotoService; import kr.kro.photoliner.domain.user.model.User; import kr.kro.photoliner.domain.user.repository.UserRepository; import kr.kro.photoliner.global.code.ApiResponseCode; @@ -22,13 +25,14 @@ public class AlbumService { private final UserRepository userRepository; private final AlbumRepository albumRepository; + private final PhotoService photoService; @Transactional public AlbumCreateResponse createAlbum(AlbumCreateRequest request) { User user = userRepository.findUserById(request.userId()) .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_USER, "user id: " + request.userId())); Album album = Album.builder() - .name(request.name()) + .title(request.title()) .user(user) .build(); Album savedAlbum = albumRepository.save(album); @@ -41,6 +45,20 @@ public AlbumsResponse getAlbums(Long userId, Pageable pageable) { return AlbumsResponse.from(albums); } + @Transactional(readOnly = true) + public PhotosResponse getAlbumItems(Long userId, Long albumId, Pageable pageable) { + Album album = albumRepository.findByIdAndUserId(albumId, userId) + .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_ALBUM, "album id: " + albumId)); + return photoService.getPhotosByIds(userId, album.getPhotoIds(), pageable); + } + + @Transactional + public void updateAlbumTitle(Long albumId, AlbumTitleUpdateRequest request) { + Album album = albumRepository.findById(albumId) + .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_ALBUM, "album id: " + albumId)); + album.updateTitle(request.title()); + } + @Transactional public void deleteAlbums(AlbumDeleteRequest request) { albumRepository.deleteAllByIdInBatch(request.ids()); diff --git a/src/main/java/kr/kro/photoliner/domain/photo/controller/PhotoController.java b/src/main/java/kr/kro/photoliner/domain/photo/controller/PhotoController.java index 9e61d68..34d0cb0 100644 --- a/src/main/java/kr/kro/photoliner/domain/photo/controller/PhotoController.java +++ b/src/main/java/kr/kro/photoliner/domain/photo/controller/PhotoController.java @@ -43,16 +43,7 @@ public ResponseEntity getPhotos( @RequestParam Long userId, @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable ) { - return ResponseEntity.ok(photoService.getPhotos(userId, pageable)); - } - - @GetMapping("/albums/{albumId}") - public ResponseEntity getPhotosByAlbumId( - @PathVariable Long albumId, - @RequestParam Long userId, - @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable - ) { - return ResponseEntity.ok(photoService.getPhotosByAlbumId(userId, albumId, pageable)); + return ResponseEntity.ok(photoService.getPhotosByIds(userId, pageable)); } @GetMapping("/markers") diff --git a/src/main/java/kr/kro/photoliner/domain/photo/infra/ExifExtractor.java b/src/main/java/kr/kro/photoliner/domain/photo/infra/ExifExtractor.java index 57b9c64..ea3a005 100644 --- a/src/main/java/kr/kro/photoliner/domain/photo/infra/ExifExtractor.java +++ b/src/main/java/kr/kro/photoliner/domain/photo/infra/ExifExtractor.java @@ -35,7 +35,7 @@ public ExifData extract(MultipartFile file) { Point location = extractGpsLocation(metadata); return new ExifData(capturedDt, location); } catch (ImageProcessingException | IOException e) { - throw CustomException.of(ApiResponseCode.FILE_PROCESSING_ERROR, "file name: " + file.getOriginalFilename(), + throw CustomException.of(ApiResponseCode.FILE_PROCESSING_ERROR, "file title: " + file.getOriginalFilename(), e); } } diff --git a/src/main/java/kr/kro/photoliner/domain/photo/infra/FileStorage.java b/src/main/java/kr/kro/photoliner/domain/photo/infra/FileStorage.java index a41ac18..4a6b346 100644 --- a/src/main/java/kr/kro/photoliner/domain/photo/infra/FileStorage.java +++ b/src/main/java/kr/kro/photoliner/domain/photo/infra/FileStorage.java @@ -94,7 +94,7 @@ private void validateFile(MultipartFile file) { String originalFilename = file.getOriginalFilename(); if (originalFilename == null || originalFilename.contains("..")) { - throw CustomException.of(ApiResponseCode.INVALID_FILE_NAME, "file name: " + originalFilename); + throw CustomException.of(ApiResponseCode.INVALID_FILE_NAME, "file title: " + originalFilename); } } @@ -110,7 +110,7 @@ private void saveFile(MultipartFile file, Path directory, String fileName) { Files.copy(inputStream, targetLocation, StandardCopyOption.REPLACE_EXISTING); } } catch (IOException e) { - throw CustomException.of(ApiResponseCode.FILE_STORE_ERROR, "file name: " + file.getOriginalFilename(), e); + throw CustomException.of(ApiResponseCode.FILE_STORE_ERROR, "file title: " + file.getOriginalFilename(), e); } } diff --git a/src/main/java/kr/kro/photoliner/domain/photo/model/AlbumPhoto.java b/src/main/java/kr/kro/photoliner/domain/photo/model/AlbumPhoto.java deleted file mode 100644 index 14c3a57..0000000 --- a/src/main/java/kr/kro/photoliner/domain/photo/model/AlbumPhoto.java +++ /dev/null @@ -1,39 +0,0 @@ -package kr.kro.photoliner.domain.photo.model; - -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.Table; -import java.util.Objects; -import kr.kro.photoliner.domain.album.model.Album; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Getter -@AllArgsConstructor -@Table(name = "albums_photos") -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class AlbumPhoto { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "album_id", nullable = false) - private Album album; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "photo_id", nullable = false) - private Photo photo; - - public boolean isIncludedInAlbum(Long albumId) { - return Objects.equals(album.getId(), albumId); - } -} diff --git a/src/main/java/kr/kro/photoliner/domain/photo/model/AlbumPhotos.java b/src/main/java/kr/kro/photoliner/domain/photo/model/AlbumPhotos.java deleted file mode 100644 index 4385cdd..0000000 --- a/src/main/java/kr/kro/photoliner/domain/photo/model/AlbumPhotos.java +++ /dev/null @@ -1,22 +0,0 @@ -package kr.kro.photoliner.domain.photo.model; - -import java.util.List; - -public record AlbumPhotos( - List albumPhotos -) { - public List getPhotoIncludedAlbum(Long albumId) { - return albumPhotos.stream() - .filter(albumPhoto -> albumPhoto.isIncludedInAlbum(albumId)) - .map(AlbumPhoto::getPhoto) - .toList(); - } - - public List getPhotoNotIncludedAlbum(Long albumId) { - return albumPhotos.stream() - .filter(albumPhoto -> albumPhoto.isIncludedInAlbum(albumId)) - .map(AlbumPhoto::getPhoto) - .toList(); - } - -} diff --git a/src/main/java/kr/kro/photoliner/domain/photo/model/Photo.java b/src/main/java/kr/kro/photoliner/domain/photo/model/Photo.java index 8c9cc47..6a2a366 100644 --- a/src/main/java/kr/kro/photoliner/domain/photo/model/Photo.java +++ b/src/main/java/kr/kro/photoliner/domain/photo/model/Photo.java @@ -10,11 +10,10 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; -import java.time.LocalDate; import java.time.LocalDateTime; import java.util.Objects; -import java.util.Optional; import kr.kro.photoliner.common.model.BaseEntity; +import kr.kro.photoliner.domain.album.model.Album; import kr.kro.photoliner.domain.user.model.User; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -57,13 +56,6 @@ public class Photo extends BaseEntity { @JoinColumn(name = "user_id", referencedColumnName = "id") private User user; - public boolean isBetween(LocalDate start, LocalDate end) { - return Optional.ofNullable(capturedDt) - .map(LocalDateTime::toLocalDate) - .filter(localDate -> localDate.isAfter(start) && localDate.isBefore(end)) - .isPresent(); - } - public void updateCapturedDate(LocalDateTime capturedDt) { this.capturedDt = capturedDt; } @@ -85,4 +77,9 @@ public Double getLongitude() { } return location.getY(); } + + public boolean isIncludedInAlbum(Album album) { + return album.getItems().stream() + .anyMatch(albumItem -> Objects.equals(albumItem.getPhotoId(), id)); + } } diff --git a/src/main/java/kr/kro/photoliner/domain/photo/model/Photos.java b/src/main/java/kr/kro/photoliner/domain/photo/model/Photos.java index 9b97f8a..1ddf76f 100644 --- a/src/main/java/kr/kro/photoliner/domain/photo/model/Photos.java +++ b/src/main/java/kr/kro/photoliner/domain/photo/model/Photos.java @@ -1,6 +1,7 @@ package kr.kro.photoliner.domain.photo.model; import java.util.List; +import kr.kro.photoliner.domain.album.model.Album; public record Photos( List photos @@ -10,9 +11,16 @@ public int count() { return photos.size(); } - public List getPhotoIds() { + public List filterInAlbum(Album album) { return photos.stream() - .map(Photo::getId) + .filter(photo -> photo.isIncludedInAlbum(album)) .toList(); } + + public List filterOutOfAlbum(Album album) { + return photos.stream() + .filter(photo -> !photo.isIncludedInAlbum(album)) + .toList(); + } + } diff --git a/src/main/java/kr/kro/photoliner/domain/photo/repository/AlbumPhotoRepository.java b/src/main/java/kr/kro/photoliner/domain/photo/repository/AlbumPhotoRepository.java deleted file mode 100644 index a376104..0000000 --- a/src/main/java/kr/kro/photoliner/domain/photo/repository/AlbumPhotoRepository.java +++ /dev/null @@ -1,17 +0,0 @@ -package kr.kro.photoliner.domain.photo.repository; - -import java.util.List; -import kr.kro.photoliner.domain.photo.model.AlbumPhoto; -import kr.kro.photoliner.domain.photo.model.AlbumPhotos; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface AlbumPhotoRepository extends JpaRepository { - - List findByPhotoIdIn(List ids); - - default AlbumPhotos getByPhotoIdIn(List ids) { - return new AlbumPhotos( - findByPhotoIdIn(ids) - ); - } -} diff --git a/src/main/java/kr/kro/photoliner/domain/photo/repository/PhotoRepository.java b/src/main/java/kr/kro/photoliner/domain/photo/repository/PhotoRepository.java index 0e65278..f47afc2 100644 --- a/src/main/java/kr/kro/photoliner/domain/photo/repository/PhotoRepository.java +++ b/src/main/java/kr/kro/photoliner/domain/photo/repository/PhotoRepository.java @@ -17,13 +17,7 @@ Page findByUserId( Pageable pageable ); - @Query(""" - select p - from Photo p - inner join AlbumPhoto ap on ap.photo.id = p.id - where ap.album.id = :albumId - """) - Page findByUserIdAndAlbumId(Long userId, Long albumId, Pageable pageable); + Page findByUserIdAndIdIn(Long user_id, List ids, Pageable pageable); @Query(""" select p @@ -46,4 +40,6 @@ default Photos getPhotosByUserIdInBox(Long userId, Point sw, Point ne) { Photo save(Photo photo); Optional findById(Long photoId); + + } diff --git a/src/main/java/kr/kro/photoliner/domain/photo/service/PhotoService.java b/src/main/java/kr/kro/photoliner/domain/photo/service/PhotoService.java index 0867d17..4803e03 100644 --- a/src/main/java/kr/kro/photoliner/domain/photo/service/PhotoService.java +++ b/src/main/java/kr/kro/photoliner/domain/photo/service/PhotoService.java @@ -1,6 +1,8 @@ package kr.kro.photoliner.domain.photo.service; import java.util.List; +import kr.kro.photoliner.domain.album.model.Album; +import kr.kro.photoliner.domain.album.repository.AlbumRepository; import kr.kro.photoliner.domain.photo.dto.DeletePhotosRequest; import kr.kro.photoliner.domain.photo.dto.request.MapMarkersRequest; import kr.kro.photoliner.domain.photo.dto.request.PhotoCapturedDateUpdateRequest; @@ -8,10 +10,8 @@ import kr.kro.photoliner.domain.photo.dto.response.MapMarkersResponse; import kr.kro.photoliner.domain.photo.dto.response.PhotosResponse; import kr.kro.photoliner.domain.photo.infra.FileStorage; -import kr.kro.photoliner.domain.photo.model.AlbumPhotos; import kr.kro.photoliner.domain.photo.model.Photo; import kr.kro.photoliner.domain.photo.model.Photos; -import kr.kro.photoliner.domain.photo.repository.AlbumPhotoRepository; import kr.kro.photoliner.domain.photo.repository.PhotoRepository; import kr.kro.photoliner.global.code.ApiResponseCode; import kr.kro.photoliner.global.exception.CustomException; @@ -19,6 +19,7 @@ import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.Point; +import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -27,32 +28,29 @@ @RequiredArgsConstructor public class PhotoService { - private final AlbumPhotoRepository albumPhotoRepository; + private final AlbumRepository albumRepository; private final PhotoRepository photoRepository; private final GeometryFactory geometryFactory; private final FileStorage fileStorage; @Transactional(readOnly = true) - public PhotosResponse getPhotos(Long userId, Pageable pageable) { + public PhotosResponse getPhotosByIds(Long userId, Pageable pageable) { return PhotosResponse.from(photoRepository.findByUserId(userId, pageable)); } - @Transactional(readOnly = true) - public PhotosResponse getPhotosByAlbumId(Long userId, Long albumId, Pageable pageable) { - return PhotosResponse.from(photoRepository.findByUserIdAndAlbumId(userId, albumId, pageable)); - } - @Transactional(readOnly = true) public MapMarkersResponse getMarkersInViewport(MapMarkersRequest request) { Point sw = geometryFactory.createPoint(request.getSouthWestCoordinate()); Point ne = geometryFactory.createPoint(request.getNorthEastCoordinate()); Photos photos = photoRepository.getPhotosByUserIdInBox(request.userId(), sw, ne); - AlbumPhotos albumPhotos = albumPhotoRepository.getByPhotoIdIn(photos.getPhotoIds()); + Album album = albumRepository.findById(request.albumId()) + .orElseThrow( + () -> CustomException.of(ApiResponseCode.NOT_FOUND_ALBUM, "album id: " + request.albumId())); return MapMarkersResponse.of( - albumPhotos.getPhotoIncludedAlbum(request.albumId()), - albumPhotos.getPhotoNotIncludedAlbum(request.albumId()) + photos.filterInAlbum(album), + photos.filterOutOfAlbum(album) ); } @@ -80,4 +78,10 @@ public void deletePhotos(DeletePhotosRequest request) { photos.forEach(photo -> fileStorage.deleteThumbnailImage(photo.getFilePath())); photoRepository.deleteAllByIdInBatch(request.ids()); } + + @Transactional(readOnly = true) + public PhotosResponse getPhotosByIds(Long userId, List ids, Pageable pageable) { + Page photos = photoRepository.findByUserIdAndIdIn(userId, ids, pageable); + return PhotosResponse.from(photos); + } } diff --git a/src/main/java/kr/kro/photoliner/global/code/ApiResponseCode.java b/src/main/java/kr/kro/photoliner/global/code/ApiResponseCode.java index 71903c9..f783982 100644 --- a/src/main/java/kr/kro/photoliner/global/code/ApiResponseCode.java +++ b/src/main/java/kr/kro/photoliner/global/code/ApiResponseCode.java @@ -40,6 +40,7 @@ public enum ApiResponseCode { */ NOT_FOUND_USER(HttpStatus.NOT_FOUND, "사용자가 존재하지 않습니다."), NOT_FOUND_PHOTO(HttpStatus.NOT_FOUND, "사진이 존재하지 않습니다."), + NOT_FOUND_ALBUM(HttpStatus.NOT_FOUND, "앨범이 존재하지 않습니다."), /** * 409 CONFLICT (중복 혹은 충돌) @@ -62,8 +63,7 @@ public enum ApiResponseCode { FILE_STORE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "파일 저장 중 오류가 발생했습니다."), FILE_CREATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "파일 생성 중 오류가 발생했습니다."), DIRECTORY_CREATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "폴더 생성 중 오류가 발생했습니다."), - FILE_DELETE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "파일 삭제 중 오류가 발생했습니다.") - ; + FILE_DELETE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "파일 삭제 중 오류가 발생했습니다."); private final HttpStatus httpStatus; private final String message; diff --git a/src/main/resources/db/migration/V6__alter_albums_table.sql b/src/main/resources/db/migration/V6__alter_albums_table.sql new file mode 100644 index 0000000..22788ae --- /dev/null +++ b/src/main/resources/db/migration/V6__alter_albums_table.sql @@ -0,0 +1,2 @@ +alter table albums + change name title varchar(20) From 3aea8b3e919312c704abed0624e5b723ae16d1c0 Mon Sep 17 00:00:00 2001 From: chanrhan Date: Thu, 20 Nov 2025 16:52:24 +0900 Subject: [PATCH 02/11] =?UTF-8?q?fix(PhotoService):=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20userId=20=ED=8C=8C=EB=9D=BC=EB=AF=B8?= =?UTF-8?q?=ED=84=B0=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kr/kro/photoliner/domain/album/service/AlbumService.java | 2 +- .../photoliner/domain/photo/repository/PhotoRepository.java | 2 +- .../kr/kro/photoliner/domain/photo/service/PhotoService.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java b/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java index 7a44d5f..d3b241c 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java +++ b/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java @@ -49,7 +49,7 @@ public AlbumsResponse getAlbums(Long userId, Pageable pageable) { public PhotosResponse getAlbumItems(Long userId, Long albumId, Pageable pageable) { Album album = albumRepository.findByIdAndUserId(albumId, userId) .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_ALBUM, "album id: " + albumId)); - return photoService.getPhotosByIds(userId, album.getPhotoIds(), pageable); + return photoService.getPhotosByIds(album.getPhotoIds(), pageable); } @Transactional diff --git a/src/main/java/kr/kro/photoliner/domain/photo/repository/PhotoRepository.java b/src/main/java/kr/kro/photoliner/domain/photo/repository/PhotoRepository.java index f47afc2..0970545 100644 --- a/src/main/java/kr/kro/photoliner/domain/photo/repository/PhotoRepository.java +++ b/src/main/java/kr/kro/photoliner/domain/photo/repository/PhotoRepository.java @@ -17,7 +17,7 @@ Page findByUserId( Pageable pageable ); - Page findByUserIdAndIdIn(Long user_id, List ids, Pageable pageable); + Page findByIdIn(List ids, Pageable pageable); @Query(""" select p diff --git a/src/main/java/kr/kro/photoliner/domain/photo/service/PhotoService.java b/src/main/java/kr/kro/photoliner/domain/photo/service/PhotoService.java index 4803e03..20b2511 100644 --- a/src/main/java/kr/kro/photoliner/domain/photo/service/PhotoService.java +++ b/src/main/java/kr/kro/photoliner/domain/photo/service/PhotoService.java @@ -80,8 +80,8 @@ public void deletePhotos(DeletePhotosRequest request) { } @Transactional(readOnly = true) - public PhotosResponse getPhotosByIds(Long userId, List ids, Pageable pageable) { - Page photos = photoRepository.findByUserIdAndIdIn(userId, ids, pageable); + public PhotosResponse getPhotosByIds(List ids, Pageable pageable) { + Page photos = photoRepository.findByIdIn(ids, pageable); return PhotosResponse.from(photos); } } From 05c15c4c54550a470856147cc41e2f15a2f4757a Mon Sep 17 00:00:00 2001 From: chanrhan Date: Thu, 20 Nov 2025 20:39:51 +0900 Subject: [PATCH 03/11] =?UTF-8?q?feat:=20=EC=95=A8=EB=B2=94-=EC=82=AC?= =?UTF-8?q?=EC=A7=84=20view=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../album/controller/AlbumController.java | 29 +++++++-- .../dto/request/AlbumItemCreateRequest.java | 10 ++++ .../dto/request/AlbumItemDeleteRequest.java | 10 ++++ .../dto/response/AlbumPhotoItemsResponse.java | 40 +++++++++++++ .../photoliner/domain/album/model/Album.java | 12 ++-- .../model/{AlbumItem.java => PhotoItem.java} | 5 +- .../album/model/view/AlbumPhotoView.java | 60 +++++++++++++++++++ .../album/model/view/AlbumPhotoViews.java | 21 +++++++ .../repository/AlbumPhotoRepository.java | 30 ++++++++++ .../album/repository/AlbumRepository.java | 2 - .../domain/album/service/AlbumService.java | 36 +++++++++-- .../dto/response/MapMarkersResponse.java | 17 +++--- .../photoliner/domain/photo/model/Photo.java | 6 -- .../photoliner/domain/photo/model/Photos.java | 13 ---- .../photo/repository/PhotoRepository.java | 2 +- .../domain/photo/service/PhotoService.java | 23 ++----- .../V7__create_album_photos_view.sql | 13 ++++ 17 files changed, 266 insertions(+), 63 deletions(-) create mode 100644 src/main/java/kr/kro/photoliner/domain/album/dto/request/AlbumItemCreateRequest.java create mode 100644 src/main/java/kr/kro/photoliner/domain/album/dto/request/AlbumItemDeleteRequest.java create mode 100644 src/main/java/kr/kro/photoliner/domain/album/dto/response/AlbumPhotoItemsResponse.java rename src/main/java/kr/kro/photoliner/domain/album/model/{AlbumItem.java => PhotoItem.java} (91%) create mode 100644 src/main/java/kr/kro/photoliner/domain/album/model/view/AlbumPhotoView.java create mode 100644 src/main/java/kr/kro/photoliner/domain/album/model/view/AlbumPhotoViews.java create mode 100644 src/main/java/kr/kro/photoliner/domain/album/repository/AlbumPhotoRepository.java create mode 100644 src/main/resources/db/migration/V7__create_album_photos_view.sql diff --git a/src/main/java/kr/kro/photoliner/domain/album/controller/AlbumController.java b/src/main/java/kr/kro/photoliner/domain/album/controller/AlbumController.java index c3c6930..52c8a90 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/controller/AlbumController.java +++ b/src/main/java/kr/kro/photoliner/domain/album/controller/AlbumController.java @@ -3,11 +3,13 @@ import jakarta.validation.Valid; import kr.kro.photoliner.domain.album.dto.request.AlbumCreateRequest; import kr.kro.photoliner.domain.album.dto.request.AlbumDeleteRequest; +import kr.kro.photoliner.domain.album.dto.request.AlbumItemCreateRequest; +import kr.kro.photoliner.domain.album.dto.request.AlbumItemDeleteRequest; import kr.kro.photoliner.domain.album.dto.request.AlbumTitleUpdateRequest; import kr.kro.photoliner.domain.album.dto.response.AlbumCreateResponse; +import kr.kro.photoliner.domain.album.dto.response.AlbumPhotoItemsResponse; import kr.kro.photoliner.domain.album.dto.response.AlbumsResponse; import kr.kro.photoliner.domain.album.service.AlbumService; -import kr.kro.photoliner.domain.photo.dto.response.PhotosResponse; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; @@ -65,11 +67,28 @@ public ResponseEntity deletePhoto( } @GetMapping("/{albumId}/photos") - public ResponseEntity getAlbumItems( + public ResponseEntity getAlbumItems( @PathVariable Long albumId, - @RequestParam Long userId, - @PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable + @PageableDefault(sort = "capturedDt", direction = Sort.Direction.DESC) Pageable pageable + ) { + return ResponseEntity.ok(albumService.getAlbumPhotoItems(albumId, pageable)); + } + + @PostMapping("/{albumId}/photos") + public ResponseEntity createAlbumItems( + @PathVariable Long albumId, + @RequestBody @Valid AlbumItemCreateRequest request ) { - return ResponseEntity.ok(albumService.getAlbumItems(userId, albumId, pageable)); + albumService.createAlbumItems(albumId, request); + return ResponseEntity.noContent().build(); + } + + @DeleteMapping("/{albumId}/photos") + public ResponseEntity deleteAlbumItems( + @PathVariable Long albumId, + @RequestBody @Valid AlbumItemDeleteRequest request + ) { + albumService.deleteAlbumItems(albumId, request); + return ResponseEntity.noContent().build(); } } diff --git a/src/main/java/kr/kro/photoliner/domain/album/dto/request/AlbumItemCreateRequest.java b/src/main/java/kr/kro/photoliner/domain/album/dto/request/AlbumItemCreateRequest.java new file mode 100644 index 0000000..0057f7f --- /dev/null +++ b/src/main/java/kr/kro/photoliner/domain/album/dto/request/AlbumItemCreateRequest.java @@ -0,0 +1,10 @@ +package kr.kro.photoliner.domain.album.dto.request; + +import jakarta.validation.constraints.NotNull; +import java.util.List; + +public record AlbumItemCreateRequest( + @NotNull + List ids +) { +} diff --git a/src/main/java/kr/kro/photoliner/domain/album/dto/request/AlbumItemDeleteRequest.java b/src/main/java/kr/kro/photoliner/domain/album/dto/request/AlbumItemDeleteRequest.java new file mode 100644 index 0000000..1eada1d --- /dev/null +++ b/src/main/java/kr/kro/photoliner/domain/album/dto/request/AlbumItemDeleteRequest.java @@ -0,0 +1,10 @@ +package kr.kro.photoliner.domain.album.dto.request; + +import jakarta.validation.constraints.NotNull; +import java.util.List; + +public record AlbumItemDeleteRequest( + @NotNull + List ids +) { +} diff --git a/src/main/java/kr/kro/photoliner/domain/album/dto/response/AlbumPhotoItemsResponse.java b/src/main/java/kr/kro/photoliner/domain/album/dto/response/AlbumPhotoItemsResponse.java new file mode 100644 index 0000000..6b28cfd --- /dev/null +++ b/src/main/java/kr/kro/photoliner/domain/album/dto/response/AlbumPhotoItemsResponse.java @@ -0,0 +1,40 @@ +package kr.kro.photoliner.domain.album.dto.response; + +import java.time.LocalDateTime; +import java.util.List; +import kr.kro.photoliner.domain.album.model.view.AlbumPhotoView; +import org.springframework.data.domain.Page; + +public record AlbumPhotoItemsResponse( + List items +) { + + public static AlbumPhotoItemsResponse from(Page albumPhotoViews) { + return new AlbumPhotoItemsResponse( + albumPhotoViews.stream() + .map(InnerAlbumPhotoItem::from) + .toList() + ); + } + + public record InnerAlbumPhotoItem( + Long id, + Long photoId, + String fileName, + String filePath, + String thumbnailPath, + LocalDateTime capturedDt + ) { + + public static InnerAlbumPhotoItem from(AlbumPhotoView albumPhotoView) { + return new InnerAlbumPhotoItem( + albumPhotoView.getId(), + albumPhotoView.getPhotoId(), + albumPhotoView.getFileName(), + albumPhotoView.getFilePath(), + albumPhotoView.getThumbnailPath(), + albumPhotoView.getCapturedDt() + ); + } + } +} diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/Album.java b/src/main/java/kr/kro/photoliner/domain/album/model/Album.java index 4401f15..fc6bbc5 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/model/Album.java +++ b/src/main/java/kr/kro/photoliner/domain/album/model/Album.java @@ -44,15 +44,17 @@ public class Album extends BaseEntity { @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "album_id") - private List items = new ArrayList<>(); + private List items = new ArrayList<>(); + + public void addItems(List items) { + this.items.addAll(items); + } public void updateTitle(String title) { this.title = title; } - public List getPhotoIds() { - return items.stream() - .map(AlbumItem::getPhotoId) - .toList(); + public void removeItems(List items) { + this.items.removeAll(items); } } diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/AlbumItem.java b/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java similarity index 91% rename from src/main/java/kr/kro/photoliner/domain/album/model/AlbumItem.java rename to src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java index 97cb47f..f82069c 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/model/AlbumItem.java +++ b/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java @@ -8,6 +8,7 @@ import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -16,11 +17,13 @@ @AllArgsConstructor @Table(name = "albums_photos") @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class AlbumItem { +@Builder +public class PhotoItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "photo_id", nullable = false) private Long photoId; + } diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/view/AlbumPhotoView.java b/src/main/java/kr/kro/photoliner/domain/album/model/view/AlbumPhotoView.java new file mode 100644 index 0000000..66fbc82 --- /dev/null +++ b/src/main/java/kr/kro/photoliner/domain/album/model/view/AlbumPhotoView.java @@ -0,0 +1,60 @@ +package kr.kro.photoliner.domain.album.model.view; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.time.LocalDateTime; +import java.util.Objects; +import lombok.Getter; +import org.hibernate.annotations.Immutable; +import org.locationtech.jts.geom.Point; + +@Entity +@Immutable +@Getter +@Table(name = "vw_album_photos") +public class AlbumPhotoView { + @Id + @Column(name = "id") + private Long id; + + @Column(name = "photo_id", nullable = false) + private Long photoId; + + @Column(name = "file_name") + private String fileName; + + @Column(name = "file_path") + private String filePath; + + @Column(name = "thumbnail_path") + private String thumbnailPath; + + @Column(name = "captured_dt") + private LocalDateTime capturedDt; + + @Column(name = "location") + private Point location; + + @Column(name = "album_id") + private Long albumId; + + @Column(name = "user_id") + private Long userId; + + public Double getLatitude() { + if (Objects.isNull(location)) { + return null; + } + return location.getX(); + } + + public Double getLongitude() { + if (Objects.isNull(location)) { + return null; + } + return location.getY(); + } + +} diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/view/AlbumPhotoViews.java b/src/main/java/kr/kro/photoliner/domain/album/model/view/AlbumPhotoViews.java new file mode 100644 index 0000000..c9883ba --- /dev/null +++ b/src/main/java/kr/kro/photoliner/domain/album/model/view/AlbumPhotoViews.java @@ -0,0 +1,21 @@ +package kr.kro.photoliner.domain.album.model.view; + +import java.util.List; +import java.util.Objects; + +public record AlbumPhotoViews( + List albumPhotoViews +) { + + public List filterAlbumIncludedIn(Long albumId) { + return albumPhotoViews.stream() + .filter(albumPhotoView -> Objects.equals(albumPhotoView.getAlbumId(), albumId)) + .toList(); + } + + public List filterAlbumExcludedFrom(Long albumId) { + return albumPhotoViews.stream() + .filter(albumPhotoView -> !Objects.equals(albumPhotoView.getAlbumId(), albumId)) + .toList(); + } +} diff --git a/src/main/java/kr/kro/photoliner/domain/album/repository/AlbumPhotoRepository.java b/src/main/java/kr/kro/photoliner/domain/album/repository/AlbumPhotoRepository.java new file mode 100644 index 0000000..09158a8 --- /dev/null +++ b/src/main/java/kr/kro/photoliner/domain/album/repository/AlbumPhotoRepository.java @@ -0,0 +1,30 @@ +package kr.kro.photoliner.domain.album.repository; + +import java.util.List; +import kr.kro.photoliner.domain.album.model.view.AlbumPhotoView; +import kr.kro.photoliner.domain.album.model.view.AlbumPhotoViews; +import org.locationtech.jts.geom.Point; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface AlbumPhotoRepository extends JpaRepository { + Page findByAlbumId(Long albumId, Pageable pageable); + + @Query(""" + select apv + from AlbumPhotoView apv + where apv.userId = :userId + and function('st_x', apv.location) between function('st_x', :sw) and function('st_x', :ne) + and function('st_y', apv.location) between function('st_y', :sw) and function('st_y', :ne) + order by apv.capturedDt desc + """) + List findByUserIdInBox(Long userId, Point sw, Point ne); + + default AlbumPhotoViews getByUserIdInBox(Long userId, Point sw, Point ne) { + return new AlbumPhotoViews( + findByUserIdInBox(userId, sw, ne) + ); + } +} diff --git a/src/main/java/kr/kro/photoliner/domain/album/repository/AlbumRepository.java b/src/main/java/kr/kro/photoliner/domain/album/repository/AlbumRepository.java index b760a10..ff82907 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/repository/AlbumRepository.java +++ b/src/main/java/kr/kro/photoliner/domain/album/repository/AlbumRepository.java @@ -1,6 +1,5 @@ package kr.kro.photoliner.domain.album.repository; -import java.util.Optional; import kr.kro.photoliner.domain.album.model.Album; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -12,5 +11,4 @@ public interface AlbumRepository extends JpaRepository { Page findByUserId(Long userId, Pageable pageable); - Optional findByIdAndUserId(Long albumId, Long userId); } diff --git a/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java b/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java index d3b241c..5284807 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java +++ b/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java @@ -1,13 +1,19 @@ package kr.kro.photoliner.domain.album.service; +import java.util.List; import kr.kro.photoliner.domain.album.dto.request.AlbumCreateRequest; import kr.kro.photoliner.domain.album.dto.request.AlbumDeleteRequest; +import kr.kro.photoliner.domain.album.dto.request.AlbumItemCreateRequest; +import kr.kro.photoliner.domain.album.dto.request.AlbumItemDeleteRequest; import kr.kro.photoliner.domain.album.dto.request.AlbumTitleUpdateRequest; import kr.kro.photoliner.domain.album.dto.response.AlbumCreateResponse; +import kr.kro.photoliner.domain.album.dto.response.AlbumPhotoItemsResponse; import kr.kro.photoliner.domain.album.dto.response.AlbumsResponse; import kr.kro.photoliner.domain.album.model.Album; +import kr.kro.photoliner.domain.album.model.PhotoItem; +import kr.kro.photoliner.domain.album.model.view.AlbumPhotoView; +import kr.kro.photoliner.domain.album.repository.AlbumPhotoRepository; import kr.kro.photoliner.domain.album.repository.AlbumRepository; -import kr.kro.photoliner.domain.photo.dto.response.PhotosResponse; import kr.kro.photoliner.domain.photo.service.PhotoService; import kr.kro.photoliner.domain.user.model.User; import kr.kro.photoliner.domain.user.repository.UserRepository; @@ -25,6 +31,7 @@ public class AlbumService { private final UserRepository userRepository; private final AlbumRepository albumRepository; + private final AlbumPhotoRepository albumPhotoRepository; private final PhotoService photoService; @Transactional @@ -46,10 +53,9 @@ public AlbumsResponse getAlbums(Long userId, Pageable pageable) { } @Transactional(readOnly = true) - public PhotosResponse getAlbumItems(Long userId, Long albumId, Pageable pageable) { - Album album = albumRepository.findByIdAndUserId(albumId, userId) - .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_ALBUM, "album id: " + albumId)); - return photoService.getPhotosByIds(album.getPhotoIds(), pageable); + public AlbumPhotoItemsResponse getAlbumPhotoItems(Long albumId, Pageable pageable) { + Page albumPhotoViews = albumPhotoRepository.findByAlbumId(albumId, pageable); + return AlbumPhotoItemsResponse.from(albumPhotoViews); } @Transactional @@ -63,4 +69,24 @@ public void updateAlbumTitle(Long albumId, AlbumTitleUpdateRequest request) { public void deleteAlbums(AlbumDeleteRequest request) { albumRepository.deleteAllByIdInBatch(request.ids()); } + + @Transactional + public void createAlbumItems(Long albumId, AlbumItemCreateRequest request) { + Album album = albumRepository.findById(albumId) + .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_ALBUM, "album id: " + albumId)); + List photoItems = request.ids().stream() + .map(id -> PhotoItem.builder().photoId(id).build()) + .toList(); + album.addItems(photoItems); + } + + @Transactional + public void deleteAlbumItems(Long albumId, AlbumItemDeleteRequest request) { + Album album = albumRepository.findById(albumId) + .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_ALBUM, "album id: " + albumId)); + List photoItems = request.ids().stream() + .map(id -> PhotoItem.builder().photoId(id).build()) + .toList(); + album.removeItems(photoItems); + } } diff --git a/src/main/java/kr/kro/photoliner/domain/photo/dto/response/MapMarkersResponse.java b/src/main/java/kr/kro/photoliner/domain/photo/dto/response/MapMarkersResponse.java index 2b1778a..6c23ace 100644 --- a/src/main/java/kr/kro/photoliner/domain/photo/dto/response/MapMarkersResponse.java +++ b/src/main/java/kr/kro/photoliner/domain/photo/dto/response/MapMarkersResponse.java @@ -2,17 +2,18 @@ import java.time.LocalDateTime; import java.util.List; -import kr.kro.photoliner.domain.photo.model.Photo; +import kr.kro.photoliner.domain.album.model.view.AlbumPhotoView; public record MapMarkersResponse( InnerPhotoMarkers innerPhotoMarkers, InnerPoiMarkers innerPoiMarkers ) { - public static MapMarkersResponse of(List photosInDate, List photosOutOfDate) { + public static MapMarkersResponse of(List photosIncludedAlbum, + List photosExcludedAlbum) { return new MapMarkersResponse( - InnerPhotoMarkers.from(photosInDate), - InnerPoiMarkers.from(photosOutOfDate) + InnerPhotoMarkers.from(photosIncludedAlbum), + InnerPoiMarkers.from(photosExcludedAlbum) ); } @@ -21,7 +22,7 @@ public record InnerPhotoMarkers( List photoMarkers ) { - public static InnerPhotoMarkers from(List photos) { + public static InnerPhotoMarkers from(List photos) { return new InnerPhotoMarkers( photos.size(), photos.stream() @@ -39,7 +40,7 @@ public record InnerPhotoMarker( Double lng ) { - public static InnerPhotoMarker from(Photo photo) { + public static InnerPhotoMarker from(AlbumPhotoView photo) { return new InnerPhotoMarker( photo.getId(), photo.getCapturedDt(), @@ -57,7 +58,7 @@ public record InnerPoiMarkers( List markers ) { - public static InnerPoiMarkers from(List photos) { + public static InnerPoiMarkers from(List photos) { return new InnerPoiMarkers( photos.size(), photos.stream() @@ -75,7 +76,7 @@ public record InnerPoiMarker( Double lng ) { - public static InnerPoiMarker from(Photo photo) { + public static InnerPoiMarker from(AlbumPhotoView photo) { return new InnerPoiMarker( photo.getId(), photo.getCapturedDt(), diff --git a/src/main/java/kr/kro/photoliner/domain/photo/model/Photo.java b/src/main/java/kr/kro/photoliner/domain/photo/model/Photo.java index 6a2a366..8ba6830 100644 --- a/src/main/java/kr/kro/photoliner/domain/photo/model/Photo.java +++ b/src/main/java/kr/kro/photoliner/domain/photo/model/Photo.java @@ -13,7 +13,6 @@ import java.time.LocalDateTime; import java.util.Objects; import kr.kro.photoliner.common.model.BaseEntity; -import kr.kro.photoliner.domain.album.model.Album; import kr.kro.photoliner.domain.user.model.User; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -77,9 +76,4 @@ public Double getLongitude() { } return location.getY(); } - - public boolean isIncludedInAlbum(Album album) { - return album.getItems().stream() - .anyMatch(albumItem -> Objects.equals(albumItem.getPhotoId(), id)); - } } diff --git a/src/main/java/kr/kro/photoliner/domain/photo/model/Photos.java b/src/main/java/kr/kro/photoliner/domain/photo/model/Photos.java index 1ddf76f..19ae167 100644 --- a/src/main/java/kr/kro/photoliner/domain/photo/model/Photos.java +++ b/src/main/java/kr/kro/photoliner/domain/photo/model/Photos.java @@ -1,7 +1,6 @@ package kr.kro.photoliner.domain.photo.model; import java.util.List; -import kr.kro.photoliner.domain.album.model.Album; public record Photos( List photos @@ -11,16 +10,4 @@ public int count() { return photos.size(); } - public List filterInAlbum(Album album) { - return photos.stream() - .filter(photo -> photo.isIncludedInAlbum(album)) - .toList(); - } - - public List filterOutOfAlbum(Album album) { - return photos.stream() - .filter(photo -> !photo.isIncludedInAlbum(album)) - .toList(); - } - } diff --git a/src/main/java/kr/kro/photoliner/domain/photo/repository/PhotoRepository.java b/src/main/java/kr/kro/photoliner/domain/photo/repository/PhotoRepository.java index 0970545..21107eb 100644 --- a/src/main/java/kr/kro/photoliner/domain/photo/repository/PhotoRepository.java +++ b/src/main/java/kr/kro/photoliner/domain/photo/repository/PhotoRepository.java @@ -17,7 +17,7 @@ Page findByUserId( Pageable pageable ); - Page findByIdIn(List ids, Pageable pageable); + List findByIdIn(List ids); @Query(""" select p diff --git a/src/main/java/kr/kro/photoliner/domain/photo/service/PhotoService.java b/src/main/java/kr/kro/photoliner/domain/photo/service/PhotoService.java index 20b2511..d5ff982 100644 --- a/src/main/java/kr/kro/photoliner/domain/photo/service/PhotoService.java +++ b/src/main/java/kr/kro/photoliner/domain/photo/service/PhotoService.java @@ -1,8 +1,8 @@ package kr.kro.photoliner.domain.photo.service; import java.util.List; -import kr.kro.photoliner.domain.album.model.Album; -import kr.kro.photoliner.domain.album.repository.AlbumRepository; +import kr.kro.photoliner.domain.album.model.view.AlbumPhotoViews; +import kr.kro.photoliner.domain.album.repository.AlbumPhotoRepository; import kr.kro.photoliner.domain.photo.dto.DeletePhotosRequest; import kr.kro.photoliner.domain.photo.dto.request.MapMarkersRequest; import kr.kro.photoliner.domain.photo.dto.request.PhotoCapturedDateUpdateRequest; @@ -11,7 +11,6 @@ import kr.kro.photoliner.domain.photo.dto.response.PhotosResponse; import kr.kro.photoliner.domain.photo.infra.FileStorage; import kr.kro.photoliner.domain.photo.model.Photo; -import kr.kro.photoliner.domain.photo.model.Photos; import kr.kro.photoliner.domain.photo.repository.PhotoRepository; import kr.kro.photoliner.global.code.ApiResponseCode; import kr.kro.photoliner.global.exception.CustomException; @@ -19,7 +18,6 @@ import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.Point; -import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -28,8 +26,8 @@ @RequiredArgsConstructor public class PhotoService { - private final AlbumRepository albumRepository; private final PhotoRepository photoRepository; + private final AlbumPhotoRepository albumPhotoRepository; private final GeometryFactory geometryFactory; private final FileStorage fileStorage; @@ -43,14 +41,11 @@ public MapMarkersResponse getMarkersInViewport(MapMarkersRequest request) { Point sw = geometryFactory.createPoint(request.getSouthWestCoordinate()); Point ne = geometryFactory.createPoint(request.getNorthEastCoordinate()); - Photos photos = photoRepository.getPhotosByUserIdInBox(request.userId(), sw, ne); - Album album = albumRepository.findById(request.albumId()) - .orElseThrow( - () -> CustomException.of(ApiResponseCode.NOT_FOUND_ALBUM, "album id: " + request.albumId())); + AlbumPhotoViews albumPhotoViews = albumPhotoRepository.getByUserIdInBox(request.userId(), sw, ne); return MapMarkersResponse.of( - photos.filterInAlbum(album), - photos.filterOutOfAlbum(album) + albumPhotoViews.filterAlbumIncludedIn(request.albumId()), + albumPhotoViews.filterAlbumExcludedFrom(request.albumId()) ); } @@ -78,10 +73,4 @@ public void deletePhotos(DeletePhotosRequest request) { photos.forEach(photo -> fileStorage.deleteThumbnailImage(photo.getFilePath())); photoRepository.deleteAllByIdInBatch(request.ids()); } - - @Transactional(readOnly = true) - public PhotosResponse getPhotosByIds(List ids, Pageable pageable) { - Page photos = photoRepository.findByIdIn(ids, pageable); - return PhotosResponse.from(photos); - } } diff --git a/src/main/resources/db/migration/V7__create_album_photos_view.sql b/src/main/resources/db/migration/V7__create_album_photos_view.sql new file mode 100644 index 0000000..c7fe49a --- /dev/null +++ b/src/main/resources/db/migration/V7__create_album_photos_view.sql @@ -0,0 +1,13 @@ +create view vw_album_photos +as +select ap.id, + p.id as photo_id, + p.file_name, + p.file_path, + p.thumbnail_path, + p.captured_dt, + p.location, + ap.album_id, + p.user_id +from photos p + left outer join albums_photos ap on ap.photo_id = p.id From 704cce3fdb333820a7b74f42a0dd88b6cd4046f7 Mon Sep 17 00:00:00 2001 From: chanrhan Date: Fri, 21 Nov 2025 21:15:21 +0900 Subject: [PATCH 04/11] =?UTF-8?q?refactor:=20=EB=AF=B8=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/album/service/AlbumService.java | 2 -- .../photo/repository/PhotoRepository.java | 25 ------------------- 2 files changed, 27 deletions(-) diff --git a/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java b/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java index 5284807..af0c831 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java +++ b/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java @@ -14,7 +14,6 @@ import kr.kro.photoliner.domain.album.model.view.AlbumPhotoView; import kr.kro.photoliner.domain.album.repository.AlbumPhotoRepository; import kr.kro.photoliner.domain.album.repository.AlbumRepository; -import kr.kro.photoliner.domain.photo.service.PhotoService; import kr.kro.photoliner.domain.user.model.User; import kr.kro.photoliner.domain.user.repository.UserRepository; import kr.kro.photoliner.global.code.ApiResponseCode; @@ -32,7 +31,6 @@ public class AlbumService { private final UserRepository userRepository; private final AlbumRepository albumRepository; private final AlbumPhotoRepository albumPhotoRepository; - private final PhotoService photoService; @Transactional public AlbumCreateResponse createAlbum(AlbumCreateRequest request) { diff --git a/src/main/java/kr/kro/photoliner/domain/photo/repository/PhotoRepository.java b/src/main/java/kr/kro/photoliner/domain/photo/repository/PhotoRepository.java index 21107eb..c7123c9 100644 --- a/src/main/java/kr/kro/photoliner/domain/photo/repository/PhotoRepository.java +++ b/src/main/java/kr/kro/photoliner/domain/photo/repository/PhotoRepository.java @@ -1,14 +1,10 @@ package kr.kro.photoliner.domain.photo.repository; -import java.util.List; import java.util.Optional; import kr.kro.photoliner.domain.photo.model.Photo; -import kr.kro.photoliner.domain.photo.model.Photos; -import org.locationtech.jts.geom.Point; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; public interface PhotoRepository extends JpaRepository { @@ -17,29 +13,8 @@ Page findByUserId( Pageable pageable ); - List findByIdIn(List ids); - - @Query(""" - select p - from Photo p - where p.user.id = :userId - and function('st_x', p.location) between function('st_x', :sw) and function('st_x', :ne) - and function('st_y', p.location) between function('st_y', :sw) and function('st_y', :ne) - order by p.capturedDt desc - """) - List findByUserIdInBox( - Long userId, - Point sw, - Point ne - ); - - default Photos getPhotosByUserIdInBox(Long userId, Point sw, Point ne) { - return new Photos(findByUserIdInBox(userId, sw, ne)); - } - Photo save(Photo photo); Optional findById(Long photoId); - } From 3931139941615694a8f29a67bba460dce0383a9e Mon Sep 17 00:00:00 2001 From: chanrhan Date: Fri, 21 Nov 2025 22:10:39 +0900 Subject: [PATCH 05/11] =?UTF-8?q?build:=20swagger-ui=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EB=B2=84=EC=A0=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index fee0b8a..7d21ce8 100644 --- a/build.gradle +++ b/build.gradle @@ -37,7 +37,7 @@ dependencies { implementation 'org.locationtech.jts:jts-core:1.19.0' // Swagger-ui - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.3' // EXIF metadata extraction implementation 'com.drewnoakes:metadata-extractor:2.19.0' From 2df4e4b3c4049f947f8d5629fe57ae6ee4991ea5 Mon Sep 17 00:00:00 2001 From: chanrhan Date: Sat, 22 Nov 2025 14:49:10 +0900 Subject: [PATCH 06/11] =?UTF-8?q?style:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EA=B3=B5=EB=B0=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../java/kr/kro/photoliner/domain/album/model/PhotoItem.java | 1 - .../kro/photoliner/domain/album/model/view/AlbumPhotoView.java | 3 +-- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 94d228a..0173ac6 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ out/ **/application.properties /lib/ +/url/ diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java b/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java index f82069c..25a3371 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java +++ b/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java @@ -25,5 +25,4 @@ public class PhotoItem { @Column(name = "photo_id", nullable = false) private Long photoId; - } diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/view/AlbumPhotoView.java b/src/main/java/kr/kro/photoliner/domain/album/model/view/AlbumPhotoView.java index 66fbc82..8028d98 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/model/view/AlbumPhotoView.java +++ b/src/main/java/kr/kro/photoliner/domain/album/model/view/AlbumPhotoView.java @@ -42,7 +42,7 @@ public class AlbumPhotoView { @Column(name = "user_id") private Long userId; - + public Double getLatitude() { if (Objects.isNull(location)) { return null; @@ -56,5 +56,4 @@ public Double getLongitude() { } return location.getY(); } - } From e7895bc3a671817b59b1c7d1d10b234f35c23678 Mon Sep 17 00:00:00 2001 From: chanrhan Date: Sat, 22 Nov 2025 15:04:18 +0900 Subject: [PATCH 07/11] =?UTF-8?q?style:=20=ED=95=A8=EC=88=98=EB=AA=85=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../photoliner/domain/album/model/view/AlbumPhotoViews.java | 4 ++-- .../kr/kro/photoliner/domain/photo/service/PhotoService.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/view/AlbumPhotoViews.java b/src/main/java/kr/kro/photoliner/domain/album/model/view/AlbumPhotoViews.java index c9883ba..812e03b 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/model/view/AlbumPhotoViews.java +++ b/src/main/java/kr/kro/photoliner/domain/album/model/view/AlbumPhotoViews.java @@ -7,13 +7,13 @@ public record AlbumPhotoViews( List albumPhotoViews ) { - public List filterAlbumIncludedIn(Long albumId) { + public List filterIncludedInAlbum(Long albumId) { return albumPhotoViews.stream() .filter(albumPhotoView -> Objects.equals(albumPhotoView.getAlbumId(), albumId)) .toList(); } - public List filterAlbumExcludedFrom(Long albumId) { + public List filterExcludedFromAlbum(Long albumId) { return albumPhotoViews.stream() .filter(albumPhotoView -> !Objects.equals(albumPhotoView.getAlbumId(), albumId)) .toList(); diff --git a/src/main/java/kr/kro/photoliner/domain/photo/service/PhotoService.java b/src/main/java/kr/kro/photoliner/domain/photo/service/PhotoService.java index d5ff982..07021e1 100644 --- a/src/main/java/kr/kro/photoliner/domain/photo/service/PhotoService.java +++ b/src/main/java/kr/kro/photoliner/domain/photo/service/PhotoService.java @@ -44,8 +44,8 @@ public MapMarkersResponse getMarkersInViewport(MapMarkersRequest request) { AlbumPhotoViews albumPhotoViews = albumPhotoRepository.getByUserIdInBox(request.userId(), sw, ne); return MapMarkersResponse.of( - albumPhotoViews.filterAlbumIncludedIn(request.albumId()), - albumPhotoViews.filterAlbumExcludedFrom(request.albumId()) + albumPhotoViews.filterIncludedInAlbum(request.albumId()), + albumPhotoViews.filterExcludedFromAlbum(request.albumId()) ); } From 1adf27fc5b3caf291981a8b3fafb47133ba0b248 Mon Sep 17 00:00:00 2001 From: chanrhan Date: Sat, 22 Nov 2025 15:58:01 +0900 Subject: [PATCH 08/11] =?UTF-8?q?refactor:=20PhotoItem=20=EC=9D=84=20?= =?UTF-8?q?=EC=96=91=EB=B0=A9=ED=96=A5=20=EC=97=B0=EA=B4=80=EA=B4=80?= =?UTF-8?q?=EA=B3=84=EB=A1=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../photoliner/domain/album/model/Album.java | 9 ++++---- .../domain/album/model/PhotoItem.java | 21 ++++++++++++++----- .../domain/album/model/PhotoItems.java | 15 +++++++++++++ .../domain/album/service/AlbumService.java | 13 +++++------- .../photoliner/domain/photo/model/Photo.java | 7 +++++++ 5 files changed, 48 insertions(+), 17 deletions(-) create mode 100644 src/main/java/kr/kro/photoliner/domain/album/model/PhotoItems.java diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/Album.java b/src/main/java/kr/kro/photoliner/domain/album/model/Album.java index fc6bbc5..69456e3 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/model/Album.java +++ b/src/main/java/kr/kro/photoliner/domain/album/model/Album.java @@ -42,19 +42,20 @@ public class Album extends BaseEntity { @JoinColumn(name = "user_id", referencedColumnName = "id") private User user; - @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) - @JoinColumn(name = "album_id") + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "album") private List items = new ArrayList<>(); - public void addItems(List items) { + public void addPhotoItems(List items) { this.items.addAll(items); + items.forEach(item -> item.setAlbum(this)); } public void updateTitle(String title) { this.title = title; } - public void removeItems(List items) { + public void removePhotoItems(List items) { this.items.removeAll(items); + items.forEach(item -> item.setAlbum(null)); } } diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java b/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java index 25a3371..e852835 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java +++ b/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java @@ -1,28 +1,39 @@ package kr.kro.photoliner.domain.album.model; -import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import kr.kro.photoliner.domain.photo.model.Photo; import lombok.AccessLevel; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; @Entity @Getter @AllArgsConstructor @Table(name = "albums_photos") @NoArgsConstructor(access = AccessLevel.PROTECTED) -@Builder public class PhotoItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Column(name = "photo_id", nullable = false) - private Long photoId; + @Setter + @ManyToOne + @JoinColumn(name = "album_id", nullable = false) + private Album album; + + @ManyToOne + @JoinColumn(name = "photo_id", nullable = false) + private Photo photo; + + public PhotoItem(Photo photo) { + this.photo = photo; + } } diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItems.java b/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItems.java new file mode 100644 index 0000000..6e033f1 --- /dev/null +++ b/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItems.java @@ -0,0 +1,15 @@ +package kr.kro.photoliner.domain.album.model; + +import java.util.List; +import kr.kro.photoliner.domain.photo.model.Photo; + +public record PhotoItems( + List items +) { + + public static List of(List photoIds) { + return photoIds.stream() + .map(id -> new PhotoItem(Photo.builder().id(id).build())) + .toList(); + } +} diff --git a/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java b/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java index af0c831..83419b6 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java +++ b/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java @@ -11,6 +11,7 @@ import kr.kro.photoliner.domain.album.dto.response.AlbumsResponse; import kr.kro.photoliner.domain.album.model.Album; import kr.kro.photoliner.domain.album.model.PhotoItem; +import kr.kro.photoliner.domain.album.model.PhotoItems; import kr.kro.photoliner.domain.album.model.view.AlbumPhotoView; import kr.kro.photoliner.domain.album.repository.AlbumPhotoRepository; import kr.kro.photoliner.domain.album.repository.AlbumRepository; @@ -72,19 +73,15 @@ public void deleteAlbums(AlbumDeleteRequest request) { public void createAlbumItems(Long albumId, AlbumItemCreateRequest request) { Album album = albumRepository.findById(albumId) .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_ALBUM, "album id: " + albumId)); - List photoItems = request.ids().stream() - .map(id -> PhotoItem.builder().photoId(id).build()) - .toList(); - album.addItems(photoItems); + List photoItems = PhotoItems.of(request.ids()); + album.addPhotoItems(photoItems); } @Transactional public void deleteAlbumItems(Long albumId, AlbumItemDeleteRequest request) { Album album = albumRepository.findById(albumId) .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_ALBUM, "album id: " + albumId)); - List photoItems = request.ids().stream() - .map(id -> PhotoItem.builder().photoId(id).build()) - .toList(); - album.removeItems(photoItems); + List photoItems = PhotoItems.of(request.ids()); + album.removePhotoItems(photoItems); } } diff --git a/src/main/java/kr/kro/photoliner/domain/photo/model/Photo.java b/src/main/java/kr/kro/photoliner/domain/photo/model/Photo.java index 8ba6830..fb68ea4 100644 --- a/src/main/java/kr/kro/photoliner/domain/photo/model/Photo.java +++ b/src/main/java/kr/kro/photoliner/domain/photo/model/Photo.java @@ -8,11 +8,15 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import kr.kro.photoliner.common.model.BaseEntity; +import kr.kro.photoliner.domain.album.model.PhotoItem; import kr.kro.photoliner.domain.user.model.User; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -55,6 +59,9 @@ public class Photo extends BaseEntity { @JoinColumn(name = "user_id", referencedColumnName = "id") private User user; + @OneToMany(mappedBy = "photo") + private List items = new ArrayList<>(); + public void updateCapturedDate(LocalDateTime capturedDt) { this.capturedDt = capturedDt; } From f9fa82d67d7cd6fb28f7accbbaa723ff323fd034 Mon Sep 17 00:00:00 2001 From: chanrhan Date: Sat, 22 Nov 2025 16:07:24 +0900 Subject: [PATCH 09/11] =?UTF-8?q?refactor:=20Photo-PhotoItem=20=EC=96=91?= =?UTF-8?q?=EB=B0=A9=ED=96=A5=20=EA=B4=80=EA=B3=84=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kro/photoliner/domain/album/model/PhotoItem.java | 10 +++------- .../kro/photoliner/domain/album/model/PhotoItems.java | 3 +-- .../kr/kro/photoliner/domain/photo/model/Photo.java | 7 ------- 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java b/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java index e852835..1feb3d9 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java +++ b/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java @@ -7,9 +7,9 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; -import kr.kro.photoliner.domain.photo.model.Photo; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -19,6 +19,7 @@ @AllArgsConstructor @Table(name = "albums_photos") @NoArgsConstructor(access = AccessLevel.PROTECTED) +@Builder public class PhotoItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -29,11 +30,6 @@ public class PhotoItem { @JoinColumn(name = "album_id", nullable = false) private Album album; - @ManyToOne @JoinColumn(name = "photo_id", nullable = false) - private Photo photo; - - public PhotoItem(Photo photo) { - this.photo = photo; - } + private Long photoId; } diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItems.java b/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItems.java index 6e033f1..5786796 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItems.java +++ b/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItems.java @@ -1,7 +1,6 @@ package kr.kro.photoliner.domain.album.model; import java.util.List; -import kr.kro.photoliner.domain.photo.model.Photo; public record PhotoItems( List items @@ -9,7 +8,7 @@ public record PhotoItems( public static List of(List photoIds) { return photoIds.stream() - .map(id -> new PhotoItem(Photo.builder().id(id).build())) + .map(id -> PhotoItem.builder().photoId(id).build()) .toList(); } } diff --git a/src/main/java/kr/kro/photoliner/domain/photo/model/Photo.java b/src/main/java/kr/kro/photoliner/domain/photo/model/Photo.java index fb68ea4..8ba6830 100644 --- a/src/main/java/kr/kro/photoliner/domain/photo/model/Photo.java +++ b/src/main/java/kr/kro/photoliner/domain/photo/model/Photo.java @@ -8,15 +8,11 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; -import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; import java.util.Objects; import kr.kro.photoliner.common.model.BaseEntity; -import kr.kro.photoliner.domain.album.model.PhotoItem; import kr.kro.photoliner.domain.user.model.User; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -59,9 +55,6 @@ public class Photo extends BaseEntity { @JoinColumn(name = "user_id", referencedColumnName = "id") private User user; - @OneToMany(mappedBy = "photo") - private List items = new ArrayList<>(); - public void updateCapturedDate(LocalDateTime capturedDt) { this.capturedDt = capturedDt; } From 57ec041e4e10f934d6119d8961597feedcabb2ae Mon Sep 17 00:00:00 2001 From: chanrhan Date: Sat, 22 Nov 2025 16:15:47 +0900 Subject: [PATCH 10/11] =?UTF-8?q?refactor(PhotoItem):=20@setter=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20setter=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EB=B3=84=EB=8F=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/kr/kro/photoliner/domain/album/model/Album.java | 4 ++-- .../kr/kro/photoliner/domain/album/model/PhotoItem.java | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/Album.java b/src/main/java/kr/kro/photoliner/domain/album/model/Album.java index 69456e3..523b3b1 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/model/Album.java +++ b/src/main/java/kr/kro/photoliner/domain/album/model/Album.java @@ -47,7 +47,7 @@ public class Album extends BaseEntity { public void addPhotoItems(List items) { this.items.addAll(items); - items.forEach(item -> item.setAlbum(this)); + items.forEach(item -> item.changeAlbum(this)); } public void updateTitle(String title) { @@ -56,6 +56,6 @@ public void updateTitle(String title) { public void removePhotoItems(List items) { this.items.removeAll(items); - items.forEach(item -> item.setAlbum(null)); + items.forEach(item -> item.changeAlbum(null)); } } diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java b/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java index 1feb3d9..0462959 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java +++ b/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java @@ -12,7 +12,6 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.Setter; @Entity @Getter @@ -25,11 +24,14 @@ public class PhotoItem { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @Setter @ManyToOne @JoinColumn(name = "album_id", nullable = false) private Album album; @JoinColumn(name = "photo_id", nullable = false) private Long photoId; + + public void changeAlbum(Album album) { + this.album = album; + } } From 017afcd5a13b8b5390c1d8093384783acfb24bdd Mon Sep 17 00:00:00 2001 From: chanrhan Date: Sat, 22 Nov 2025 16:29:42 +0900 Subject: [PATCH 11/11] =?UTF-8?q?refactor:=20service=EB=8B=A8=EC=97=90?= =?UTF-8?q?=EC=84=9C=20PhotoItem=EC=9D=84=20=EB=AA=A8=EB=A5=B4=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../photoliner/domain/album/model/Album.java | 25 ++++++++++++++----- .../domain/album/model/PhotoItem.java | 11 +++++++- .../domain/album/model/PhotoItems.java | 14 ----------- .../domain/album/service/AlbumService.java | 9 ++----- 4 files changed, 31 insertions(+), 28 deletions(-) delete mode 100644 src/main/java/kr/kro/photoliner/domain/album/model/PhotoItems.java diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/Album.java b/src/main/java/kr/kro/photoliner/domain/album/model/Album.java index 523b3b1..bc4a613 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/model/Album.java +++ b/src/main/java/kr/kro/photoliner/domain/album/model/Album.java @@ -14,6 +14,7 @@ import jakarta.validation.constraints.NotNull; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import kr.kro.photoliner.common.model.BaseEntity; import kr.kro.photoliner.domain.user.model.User; import lombok.AccessLevel; @@ -45,17 +46,29 @@ public class Album extends BaseEntity { @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "album") private List items = new ArrayList<>(); - public void addPhotoItems(List items) { - this.items.addAll(items); - items.forEach(item -> item.changeAlbum(this)); + public void addPhotos(List photoIds) { + photoIds.forEach(this::addPhoto); + } + + private void addPhoto(Long photoId) { + items.add(PhotoItem.of(this, photoId)); } public void updateTitle(String title) { this.title = title; } - public void removePhotoItems(List items) { - this.items.removeAll(items); - items.forEach(item -> item.changeAlbum(null)); + public void removePhotos(List photoIds) { + photoIds.forEach(this::removePhoto); + } + + private void removePhoto(Long photoId) { + items.removeIf(item -> { + if (Objects.equals(item.getPhotoId(), photoId)) { + item.removeAlbum(); + return true; + } + return false; + }); } } diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java b/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java index 0462959..a8f4a9b 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java +++ b/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItem.java @@ -31,7 +31,16 @@ public class PhotoItem { @JoinColumn(name = "photo_id", nullable = false) private Long photoId; - public void changeAlbum(Album album) { + public PhotoItem(Album album, Long photoId) { this.album = album; + this.photoId = photoId; + } + + public static PhotoItem of(Album album, Long photoId) { + return new PhotoItem(album, photoId); + } + + public void removeAlbum() { + this.album = null; } } diff --git a/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItems.java b/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItems.java deleted file mode 100644 index 5786796..0000000 --- a/src/main/java/kr/kro/photoliner/domain/album/model/PhotoItems.java +++ /dev/null @@ -1,14 +0,0 @@ -package kr.kro.photoliner.domain.album.model; - -import java.util.List; - -public record PhotoItems( - List items -) { - - public static List of(List photoIds) { - return photoIds.stream() - .map(id -> PhotoItem.builder().photoId(id).build()) - .toList(); - } -} diff --git a/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java b/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java index 83419b6..ed2b411 100644 --- a/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java +++ b/src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java @@ -1,6 +1,5 @@ package kr.kro.photoliner.domain.album.service; -import java.util.List; import kr.kro.photoliner.domain.album.dto.request.AlbumCreateRequest; import kr.kro.photoliner.domain.album.dto.request.AlbumDeleteRequest; import kr.kro.photoliner.domain.album.dto.request.AlbumItemCreateRequest; @@ -10,8 +9,6 @@ import kr.kro.photoliner.domain.album.dto.response.AlbumPhotoItemsResponse; import kr.kro.photoliner.domain.album.dto.response.AlbumsResponse; import kr.kro.photoliner.domain.album.model.Album; -import kr.kro.photoliner.domain.album.model.PhotoItem; -import kr.kro.photoliner.domain.album.model.PhotoItems; import kr.kro.photoliner.domain.album.model.view.AlbumPhotoView; import kr.kro.photoliner.domain.album.repository.AlbumPhotoRepository; import kr.kro.photoliner.domain.album.repository.AlbumRepository; @@ -73,15 +70,13 @@ public void deleteAlbums(AlbumDeleteRequest request) { public void createAlbumItems(Long albumId, AlbumItemCreateRequest request) { Album album = albumRepository.findById(albumId) .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_ALBUM, "album id: " + albumId)); - List photoItems = PhotoItems.of(request.ids()); - album.addPhotoItems(photoItems); + album.addPhotos(request.ids()); } @Transactional public void deleteAlbumItems(Long albumId, AlbumItemDeleteRequest request) { Album album = albumRepository.findById(albumId) .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_ALBUM, "album id: " + albumId)); - List photoItems = PhotoItems.of(request.ids()); - album.removePhotoItems(photoItems); + album.removePhotos(request.ids()); } }