Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: album 생성, 수정, 삭제 API #68

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
@@ -1,11 +1,14 @@
package com.onebyte.life4cut.album.controller;

import com.onebyte.life4cut.album.controller.dto.CreateAlbumRequest;
import com.onebyte.life4cut.album.controller.dto.CreateAlbumResponse;
import com.onebyte.life4cut.album.controller.dto.CreatePictureRequest;
import com.onebyte.life4cut.album.controller.dto.CreatePictureResponse;
import com.onebyte.life4cut.album.controller.dto.GetMyRoleInAlbumResponse;
import com.onebyte.life4cut.album.controller.dto.GetPicturesInSlotResponse;
import com.onebyte.life4cut.album.controller.dto.SearchTagsRequest;
import com.onebyte.life4cut.album.controller.dto.SearchTagsResponse;
import com.onebyte.life4cut.album.controller.dto.UpdateAlbumRequest;
import com.onebyte.life4cut.album.controller.dto.UpdatePictureRequest;
import com.onebyte.life4cut.album.domain.vo.UserAlbumRole;
import com.onebyte.life4cut.album.service.AlbumService;
Expand All @@ -21,10 +24,12 @@
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
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;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -39,6 +44,41 @@ public class AlbumController {
private final PictureTagService pictureTagService;
private final AlbumService albumService;

@PostMapping("")
public ApiResponse<CreateAlbumResponse> createAlbum(
@Valid @RequestBody CreateAlbumRequest request,
@AuthenticationPrincipal CustomUserDetails userDetails) {
Long albumId =
albumService.createAlbum(
request.name(),
userDetails.getUserId(),
request.memberUserIds(),
request.guestUserIds());
return ApiResponse.OK(new CreateAlbumResponse(albumId));
}

@DeleteMapping("/{albumId}")
public ApiResponse<EmptyResponse> deleteAlbum(
@Min(1) @PathVariable("albumId") Long albumId,
@AuthenticationPrincipal CustomUserDetails userDetails) {
albumService.deleteAlbum(albumId, userDetails.getUserId());
return ApiResponse.OK();
}

@PatchMapping("/{albumId}")
public ApiResponse<EmptyResponse> updateAlbum(
@Min(1) @PathVariable("albumId") Long albumId,
@Valid @RequestBody UpdateAlbumRequest request,
@AuthenticationPrincipal CustomUserDetails userDetails) {
albumService.updateAlbum(
albumId,
request.name(),
userDetails.getUserId(),
request.memberUserIds(),
request.guestUserIds());
return ApiResponse.OK();
}

@PostMapping("/{albumId}/pictures")
public ApiResponse<CreatePictureResponse> uploadPicture(
@Min(1) @PathVariable("albumId") Long albumId,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.onebyte.life4cut.album.controller.dto;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import java.util.List;

public record CreateAlbumRequest(
@NotNull(message = "앨범의 이름을 입력해주세요") String name,
List<@Min(value = 1, message = "올바른 memberUserIds를 입력해주세요") Long> memberUserIds,
List<@Min(value = 1, message = "올바른 guestUserIds를 입력해주세요") Long> guestUserIds) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.onebyte.life4cut.album.controller.dto;

public record CreateAlbumResponse(Long id) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.onebyte.life4cut.album.controller.dto;

import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import java.util.List;

public record UpdateAlbumRequest(
@NotBlank(message = "앨범의 이름을 입력해주세요") String name,
List<@Min(value = 1, message = "올바른 memberUserIds를 입력해주세요") Long> memberUserIds,
List<@Min(value = 1, message = "올바른 guestUserIds를 입력해주세요") Long> guestUserIds) {}
12 changes: 12 additions & 0 deletions src/main/java/com/onebyte/life4cut/album/domain/Album.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,25 @@
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import java.time.LocalDateTime;
import lombok.Setter;

@Entity
public class Album extends BaseEntity {

@Setter
@Nonnull
@Column(nullable = false)
private String name;

@Nullable @Column private LocalDateTime deletedAt;

public static Album create(@Nonnull String name) {
Album album = new Album();
album.name = name;
return album;
}

public void softDelete() {
this.deletedAt = LocalDateTime.now();
}
}
29 changes: 29 additions & 0 deletions src/main/java/com/onebyte/life4cut/album/domain/UserAlbum.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,33 @@ public class UserAlbum extends BaseEntity {
public boolean isGuest() {
return role == UserAlbumRole.GUEST;
}

public boolean isHost() {
return role == UserAlbumRole.HOST;
}

public static UserAlbum createHost(@Nonnull Long albumId, @Nonnull Long userId) {
return createUserAlbum(albumId, userId, UserAlbumRole.HOST);
}

public static UserAlbum createMember(@Nonnull Long albumId, @Nonnull Long userId) {
return createUserAlbum(albumId, userId, UserAlbumRole.MEMBER);
}

public static UserAlbum createGuest(@Nonnull Long albumId, @Nonnull Long userId) {
return createUserAlbum(albumId, userId, UserAlbumRole.GUEST);
}

private static UserAlbum createUserAlbum(
@Nonnull Long albumId, @Nonnull Long userId, @Nonnull UserAlbumRole role) {
UserAlbum userAlbum = new UserAlbum();
userAlbum.albumId = albumId;
userAlbum.userId = userId;
userAlbum.role = role;
return userAlbum;
}
Comment on lines +44 to +63
Copy link
Contributor

Choose a reason for hiding this comment

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

👍🏻

Copy link
Member

Choose a reason for hiding this comment

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

image


public void softDelete() {
this.deletedAt = LocalDateTime.now();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
import java.util.Optional;

public interface AlbumRepository {

Optional<Album> findById(Long id);

Album save(Album album);

void deleteById(Long id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,17 @@

import com.onebyte.life4cut.album.domain.Album;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import java.util.Optional;
import org.springframework.stereotype.Repository;

@Repository
public class AlbumRepositoryImpl implements AlbumRepository {

private final JPAQueryFactory jpaQueryFactory;
@PersistenceContext private EntityManager entityManager;

public AlbumRepositoryImpl(JPAQueryFactory jpaQueryFactory) {
this.jpaQueryFactory = jpaQueryFactory;
Expand All @@ -23,4 +27,20 @@ public Optional<Album> findById(Long id) {
.where(album.id.eq(id), album.deletedAt.isNull())
.fetchOne());
}

@Transactional
public Album save(Album album) {
entityManager.persist(album);
return album;
}

@Override
public void deleteById(Long id) {
Album album = entityManager.find(Album.class, id);

if (album != null) {
album.softDelete();
entityManager.merge(album);
}
Comment on lines +41 to +44
Copy link
Member

Choose a reason for hiding this comment

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

  • 혹시 위에서 find를 했는데 entityManager.merge(album)를 꼭 사용해야할 이유가 있을까요?
  • merge를 안 해주더라도 JPA 더티 체킹으로 자동 업데이트 되지 않을까하는 생각이 들어서요!
  • 더티 체킹 참고 자료
  • (제가 모르는 무엇인가) 이유가 있다면 merge를 사용해도 상관 없을 것 같습니다.

Copy link
Member Author

Choose a reason for hiding this comment

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

감사해요. merge 사용한 부분들 체크해보고 불필요한 부분은 제거하겠습니다!

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,10 @@

public interface UserAlbumRepository {
Optional<UserAlbum> findByUserIdAndAlbumId(Long userId, Long albumId);

UserAlbum save(UserAlbum userAlbum);

void delete(UserAlbum userAlbum);

void deleteByAlbumId(Long albumId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@

import com.onebyte.life4cut.album.domain.UserAlbum;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import java.time.LocalDateTime;
import java.util.Optional;
import org.springframework.stereotype.Repository;

@Repository
public class UserAlbumRepositoryImpl implements UserAlbumRepository {

private final JPAQueryFactory jpaQueryFactory;
@PersistenceContext private EntityManager entityManager;

public UserAlbumRepositoryImpl(JPAQueryFactory jpaQueryFactory) {
this.jpaQueryFactory = jpaQueryFactory;
Expand All @@ -26,4 +31,27 @@ public Optional<UserAlbum> findByUserIdAndAlbumId(Long userId, Long albumId) {
userAlbum.deletedAt.isNull())
.fetchOne());
}

@Override
public UserAlbum save(UserAlbum userAlbum) {
entityManager.persist(userAlbum);
return userAlbum;
}

@Transactional
@Override
public void delete(UserAlbum userAlbum) {
userAlbum.softDelete();
entityManager.merge(userAlbum);
}

@Transactional
@Override
public void deleteByAlbumId(Long albumId) {
jpaQueryFactory
.update(userAlbum)
.set(userAlbum.deletedAt, LocalDateTime.now())
.where(userAlbum.albumId.eq(albumId))
.execute();
}
}
98 changes: 98 additions & 0 deletions src/main/java/com/onebyte/life4cut/album/service/AlbumService.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package com.onebyte.life4cut.album.service;

import com.onebyte.life4cut.album.domain.Album;
import com.onebyte.life4cut.album.domain.UserAlbum;
import com.onebyte.life4cut.album.domain.vo.UserAlbumRole;
import com.onebyte.life4cut.album.exception.AlbumNotFoundException;
import com.onebyte.life4cut.album.exception.UserAlbumRolePermissionException;
import com.onebyte.life4cut.album.repository.AlbumRepository;
import com.onebyte.life4cut.album.repository.UserAlbumRepository;
import jakarta.annotation.Nonnull;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand All @@ -28,4 +33,97 @@ public UserAlbumRole getRoleInAlbum(@NonNull Long albumId, @NonNull Long userId)

return userAlbum.getRole();
}

public Long createAlbum(
@Nonnull String name,
@Nonnull Long userId,
List<Long> memberUserIds,
List<Long> guestUserIds) {
Set<Long> userIds = new HashSet<>();
Copy link
Contributor

Choose a reason for hiding this comment

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

Set을 사용한 이유가 중복해서 사용자가 추가되지 않도록 하기위해서지?

밑에서 add 해서 새롭게 추가되었을 때만 디비에 적재하는거구.

Copy link
Member Author

Choose a reason for hiding this comment

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

딱 맞음! 동일 사용자가 여러 권한을 갖지 않도록.


Album album = Album.create(name);
albumRepository.save(album);
Long albumId = album.getId();
UserAlbum hostUserAlbum = UserAlbum.createHost(albumId, userId);

userAlbumRepository.save(hostUserAlbum);
userIds.add(userId);

if (memberUserIds != null) {
for (Long memberId : memberUserIds) {
if (userIds.add(memberId)) {
UserAlbum memberUserAlbum = UserAlbum.createMember(albumId, memberId);
userAlbumRepository.save(memberUserAlbum);
}
}
}
if (guestUserIds != null) {
for (Long guestId : guestUserIds) {
if (userIds.add(guestId)) {
UserAlbum guestUserAlbum = UserAlbum.createGuest(albumId, guestId);
userAlbumRepository.save(guestUserAlbum);
}
}
}

return albumId;
}

public void deleteAlbum(Long albumId, @NonNull Long userId) {
albumRepository.findById(albumId).orElseThrow(AlbumNotFoundException::new);

UserAlbum userAlbum =
userAlbumRepository
.findByUserIdAndAlbumId(userId, albumId)
.orElseThrow(UserAlbumRolePermissionException::new);

if (userAlbum.isHost()) {
albumRepository.deleteById(albumId);
userAlbumRepository.deleteByAlbumId(albumId);
} else {
userAlbumRepository.delete(userAlbum);
}
}

public void updateAlbum(
Long albumId, String name, Long userId, List<Long> memberUserIds, List<Long> guestUserIds) {
Album album = albumRepository.findById(albumId).orElseThrow(() -> new AlbumNotFoundException());
UserAlbum userAlbum =
userAlbumRepository
.findByUserIdAndAlbumId(userId, albumId)
.orElseThrow(UserAlbumRolePermissionException::new);

if (!userAlbum.isHost()) {
throw new UserAlbumRolePermissionException();
}
userAlbumRepository.deleteByAlbumId(albumId);
if (name != null) {
album.setName(name);
}
albumRepository.save(album);

Set<Long> userIds = new HashSet<>();

UserAlbum hostUserAlbum = UserAlbum.createHost(albumId, userId);

userAlbumRepository.save(hostUserAlbum);
userIds.add(userId);

if (memberUserIds != null) {
for (Long memberId : memberUserIds) {
if (userIds.add(memberId)) {
UserAlbum memberUserAlbum = UserAlbum.createMember(albumId, memberId);
userAlbumRepository.save(memberUserAlbum);
}
}
}
if (guestUserIds != null) {
for (Long guestId : guestUserIds) {
if (userIds.add(guestId)) {
UserAlbum guestUserAlbum = UserAlbum.createGuest(albumId, guestId);
userAlbumRepository.save(guestUserAlbum);
}
}
}
}
Comment on lines +105 to +128
Copy link
Contributor

Choose a reason for hiding this comment

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

생성쪽과 동일한 로직인 것 같은데 맞다면 메서드로 빼도 좋을 것 같아

Copy link
Member Author

Choose a reason for hiding this comment

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

맞아요👍 반영하겠습니다.

}
Loading