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
@@ -0,0 +1,53 @@
package kr.kro.photoliner.domain.album.controller;

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.response.AlbumCreateResponse;
import kr.kro.photoliner.domain.album.dto.response.AlbumsResponse;
import kr.kro.photoliner.domain.album.service.AlbumService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.HttpStatus;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/albums")
public class AlbumController {

private final AlbumService albumService;

@PostMapping
public ResponseEntity<AlbumCreateResponse> createAlbum(
@Valid @RequestBody AlbumCreateRequest request
) {
AlbumCreateResponse response = albumService.createAlbum(request);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

@GetMapping
public ResponseEntity<AlbumsResponse> getAlbums(
@RequestParam Long userId,
@PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
) {
return ResponseEntity.ok(albumService.getAlbums(userId, pageable));
}

@DeleteMapping
public ResponseEntity<Void> deletePhoto(
@Valid @RequestBody AlbumDeleteRequest request
) {
albumService.deleteAlbums(request);
return ResponseEntity.noContent().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package kr.kro.photoliner.domain.album.dto.request;

import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;

public record AlbumCreateRequest(
@NotNull
Long userId,
@NotEmpty
String name
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package kr.kro.photoliner.domain.album.dto.request;

import java.util.List;

public record AlbumDeleteRequest(
List<Long> ids
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package kr.kro.photoliner.domain.album.dto.response;

import kr.kro.photoliner.domain.album.model.Album;

public record AlbumCreateResponse(
InnerAlbum album
) {

public static AlbumCreateResponse from(Album album) {
return new AlbumCreateResponse(
new InnerAlbum(
album.getId(),
album.getName()
)
);
}

public record InnerAlbum(
Long id,
String name
) {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package kr.kro.photoliner.domain.album.dto.response;

import java.util.List;
import kr.kro.photoliner.domain.album.model.Album;
import org.springframework.data.domain.Page;

public record AlbumsResponse(
List<InnerAlbum> albums,
InnerPageInfo pageInfo
) {

public static AlbumsResponse from(Page<Album> albumPage) {
return new AlbumsResponse(
albumPage.getContent().stream()
.map(InnerAlbum::from)
.toList(),
InnerPageInfo.from(albumPage)
);
}

public record InnerAlbum(
Long id,
String name
) {

public static InnerAlbum from(Album album) {
return new InnerAlbum(
album.getId(),
album.getName()
);
}
}

public record InnerPageInfo(
long totalElements,
int totalPages,
int currentPage,
int size,
boolean hasNext,
boolean hasPrevious
) {

public static InnerPageInfo from(Page<Album> page) {
return new InnerPageInfo(
page.getTotalElements(),
page.getTotalPages(),
page.getNumber(),
page.getSize(),
page.hasNext(),
page.hasPrevious()
);
}
}
}
40 changes: 40 additions & 0 deletions src/main/java/kr/kro/photoliner/domain/album/model/Album.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package kr.kro.photoliner.domain.album.model;

import jakarta.persistence.Column;
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 jakarta.validation.constraints.NotNull;
import kr.kro.photoliner.common.model.BaseEntity;
import kr.kro.photoliner.domain.user.model.User;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "albums")
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
public class Album extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@NotNull
@Column(name = "name", nullable = false)
private String name;

@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "user_id", referencedColumnName = "id")
private User user;
}
12 changes: 12 additions & 0 deletions src/main/java/kr/kro/photoliner/domain/album/model/Albums.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package kr.kro.photoliner.domain.album.model;

import java.util.List;

public record Albums(
List<Album> albums
) {

public int count() {
return albums.size();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package kr.kro.photoliner.domain.album.repository;

import kr.kro.photoliner.domain.album.model.Album;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

public interface AlbumRepository extends JpaRepository<Album, Long> {

Album save(Album album);

Page<Album> findByUserId(Long userId, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package kr.kro.photoliner.domain.album.service;

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.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.user.model.User;
import kr.kro.photoliner.domain.user.repository.UserRepository;
import kr.kro.photoliner.global.code.ApiResponseCode;
import kr.kro.photoliner.global.exception.CustomException;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class AlbumService {

private final UserRepository userRepository;
private final AlbumRepository albumRepository;

@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())
.user(user)
.build();
Album savedAlbum = albumRepository.save(album);
return AlbumCreateResponse.from(savedAlbum);
}

@Transactional(readOnly = true)
public AlbumsResponse getAlbums(Long userId, Pageable pageable) {
Page<Album> albums = albumRepository.findByUserId(userId, pageable);
return AlbumsResponse.from(albums);
}

@Transactional
public void deleteAlbums(AlbumDeleteRequest request) {
albumRepository.deleteAllByIdInBatch(request.ids());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,20 @@ public class PhotoController {
private final PhotoUploadService photoUploadService;

@GetMapping
public ResponseEntity<PhotosResponse> getPhotoList(
public ResponseEntity<PhotosResponse> getPhotos(
@RequestParam Long userId,
@PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
) {
return ResponseEntity.ok(photoService.getPhotoList(userId, pageable));
return ResponseEntity.ok(photoService.getPhotos(userId, pageable));
}

@GetMapping("/albums/{albumId}")
public ResponseEntity<PhotosResponse> getPhotosByAlbumId(
@PathVariable Long albumId,
@RequestParam Long userId,
@PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
) {
return ResponseEntity.ok(photoService.getPhotosByAlbumId(userId, albumId, pageable));
}

@GetMapping("/markers")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,14 @@
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDate;
import org.locationtech.jts.geom.Coordinate;
import org.springframework.format.annotation.DateTimeFormat;

public record MapMarkersRequest(
@NotNull @Min(0)
Long userId,

@NotNull @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
LocalDate from,

@NotNull @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
LocalDate to,
@NotNull @Min(0)
Long albumId,

@Min(0) @Max(90)
double swLat,
Expand Down
39 changes: 39 additions & 0 deletions src/main/java/kr/kro/photoliner/domain/photo/model/AlbumPhoto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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;
Comment on lines +28 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C

루트 애그리거트를 Album 이라고 생각하는데 어떤가요?
Album에서 리스트형태로 AlbumPhoto를 관리하고
AlbumPhoto는 Embeddable로 보는 것도 괜찮다고 생각합니다.

더불어 앨범이 삭제될경우 AlbumPhoto도 같이 삭제될 수 있도록 Cascade 설정도 할 필요가 있어 보여요.


@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "photo_id", nullable = false)
private Photo photo;
Comment on lines +32 to +34
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C

앨범 애그리거트에서 사진 애그리거트를 직접 참조하는 것에 대해 어떻게 생각하시나요?
다른 애그리거트는 id 형태로 간접 참조해도 괜찮지 않을까 생각합니다.


public boolean isIncludedInAlbum(Long albumId) {
return Objects.equals(album.getId(), albumId);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package kr.kro.photoliner.domain.photo.model;

import java.util.List;

public record AlbumPhotos(
List<AlbumPhoto> albumPhotos
) {
public List<Photo> getPhotoIncludedAlbum(Long albumId) {
return albumPhotos.stream()
.filter(albumPhoto -> albumPhoto.isIncludedInAlbum(albumId))
.map(AlbumPhoto::getPhoto)
.toList();
}

public List<Photo> getPhotoNotIncludedAlbum(Long albumId) {
return albumPhotos.stream()
.filter(albumPhoto -> albumPhoto.isIncludedInAlbum(albumId))
.map(AlbumPhoto::getPhoto)
.toList();
}

}
Loading
Loading