Skip to content

Commit

Permalink
Merge pull request #76 from jisung-in/feature/73-user-library-crud-api
Browse files Browse the repository at this point in the history
[Feature] UserLibrary CRUD 기능 구현
  • Loading branch information
jwooo authored Apr 15, 2024
2 parents 1c9c692 + 07c5b0c commit b08f886
Show file tree
Hide file tree
Showing 17 changed files with 1,057 additions and 2 deletions.
4 changes: 4 additions & 0 deletions src/main/java/com/jisungin/api/ApiResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@ public static <T> ApiResponse<T> ok(T data) {
return new ApiResponse<>(HttpStatus.OK, HttpStatus.OK.name(), data);
}

public static ApiResponse<Void> ok() {
return new ApiResponse<>(HttpStatus.OK, HttpStatus.OK.name(), null);
}

}
11 changes: 11 additions & 0 deletions src/main/java/com/jisungin/api/GlobalExceptionHandler.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.jisungin.api;

import com.jisungin.exception.BusinessException;
import com.jisungin.exception.ErrorCode;
import com.jisungin.exception.ErrorResponse;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -9,6 +10,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

Expand All @@ -31,6 +33,15 @@ public ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(Metho
.body(ErrorResponse.of(HttpStatus.BAD_REQUEST.value(), firstErrorMessage));
}

@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseEntity<ErrorResponse> handleMissionServletRequestParamException(
MissingServletRequestParameterException e
) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ErrorResponse.of(ErrorCode.INVALID_PARAMS_VALUE.getCode(),
ErrorCode.INVALID_PARAMS_VALUE.getMessage()));
}

@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException e) {
log.info("BusinessException: {}", e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.jisungin.api.userlibrary;

import com.jisungin.api.ApiResponse;
import com.jisungin.api.oauth.Auth;
import com.jisungin.api.userlibrary.request.UserLibraryCreateRequest;
import com.jisungin.api.userlibrary.request.UserLibraryEditRequest;
import com.jisungin.application.userlibrary.UserLibraryService;
import com.jisungin.application.userlibrary.response.UserLibraryResponse;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/v1")
public class UserLibraryController {

private final UserLibraryService userLibraryService;

@GetMapping("/user-libraries")
public ApiResponse<UserLibraryResponse> getUserLibrary(@RequestParam String isbn,
@Auth Long userId
) {
return ApiResponse.ok(userLibraryService.getUserLibrary(userId, isbn));
}

@PostMapping("/user-libraries")
public ApiResponse<UserLibraryResponse> createUserLibrary(@Valid @RequestBody UserLibraryCreateRequest request,
@Auth Long userId
) {
return ApiResponse.ok(userLibraryService.createUserLibrary(request.toServiceRequest(), userId));
}

@PatchMapping("/user-libraries/{userLibraryId}")
public ApiResponse<Void> editUserLibrary(@PathVariable("userLibraryId") Long userLibraryId,
@Valid @RequestBody UserLibraryEditRequest request,
@Auth Long userId
) {
userLibraryService.editUserLibrary(userLibraryId, userId, request.toServiceRequest());

return ApiResponse.ok();
}

@DeleteMapping("/user-libraries/{userLibraryId}")
public ApiResponse<Void> deleteUserLibrary(@PathVariable("userLibraryId") Long userLibraryId,
@RequestParam String isbn,
@Auth Long userId
) {
userLibraryService.deleteUserLibrary(userLibraryId, userId, isbn);

return ApiResponse.ok();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.jisungin.api.userlibrary.request;

import com.jisungin.application.userlibrary.request.UserLibraryCreateServiceRequest;
import jakarta.validation.constraints.NotBlank;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class UserLibraryCreateRequest {

@NotBlank(message = "책 isbn 입력은 필수 입니다.")
private String isbn;

@NotBlank(message = "독서 상태 정보 입력은 필수 입니다.")
private String readingStatus;

@Builder
private UserLibraryCreateRequest(String isbn, String readingStatus) {
this.isbn = isbn;
this.readingStatus = readingStatus;
}

public UserLibraryCreateServiceRequest toServiceRequest() {
return UserLibraryCreateServiceRequest.builder()
.isbn(isbn)
.readingStatus(readingStatus)
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.jisungin.api.userlibrary.request;

import com.jisungin.application.userlibrary.request.UserLibraryEditServiceRequest;
import jakarta.validation.constraints.NotBlank;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class UserLibraryEditRequest {

@NotBlank(message = "책 isbn 입력은 필수 입니다.")
private String isbn;

@NotBlank(message = "독서 상태 정보 입력은 필수 입니다.")
private String readingStatus;

@Builder
private UserLibraryEditRequest(String isbn, String readingStatus) {
this.isbn = isbn;
this.readingStatus = readingStatus;
}

public UserLibraryEditServiceRequest toServiceRequest() {
return UserLibraryEditServiceRequest.builder()
.isbn(isbn)
.readingStatus(readingStatus)
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.jisungin.application.userlibrary;

import com.jisungin.application.userlibrary.request.UserLibraryCreateServiceRequest;
import com.jisungin.application.userlibrary.request.UserLibraryEditServiceRequest;
import com.jisungin.application.userlibrary.response.UserLibraryResponse;
import com.jisungin.domain.ReadingStatus;
import com.jisungin.domain.book.Book;
import com.jisungin.domain.book.repository.BookRepository;
import com.jisungin.domain.mylibrary.UserLibrary;
import com.jisungin.domain.mylibrary.repository.UserLibraryRepository;
import com.jisungin.domain.user.User;
import com.jisungin.domain.user.repository.UserRepository;
import com.jisungin.exception.BusinessException;
import com.jisungin.exception.ErrorCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserLibraryService {

private final UserRepository userRepository;
private final BookRepository bookRepository;
private final UserLibraryRepository userLibraryRepository;

public UserLibraryResponse getUserLibrary(Long userId, String isbn) {
if (userId == null || isbn == null) {
return UserLibraryResponse.empty();
}

User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));

Book book = bookRepository.findById(isbn)
.orElseThrow(() -> new BusinessException(ErrorCode.BOOK_NOT_FOUND));

UserLibrary userLibrary = userLibraryRepository.findByUserAndBook(user, book);

return UserLibraryResponse.of(userLibrary);
}

@Transactional
public UserLibraryResponse createUserLibrary(UserLibraryCreateServiceRequest request, Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));

Book book = bookRepository.findById(request.getIsbn())
.orElseThrow(() -> new BusinessException(ErrorCode.BOOK_NOT_FOUND));

UserLibrary savedUserLibrary = userLibraryRepository.save(request.toEntity(user, book));

return UserLibraryResponse.of(savedUserLibrary);
}

@Transactional
public void editUserLibrary(Long userLibraryId, Long userId, UserLibraryEditServiceRequest request) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));

Book book = bookRepository.findById(request.getIsbn())
.orElseThrow(() -> new BusinessException(ErrorCode.BOOK_NOT_FOUND));

UserLibrary userLibrary = userLibraryRepository.findByIdWithBookAndUser(userLibraryId)
.orElseThrow(() -> new BusinessException(ErrorCode.USER_LIBRARY_NOT_FOUND));

if (!userLibrary.isUserLibraryOwner(user.getId())) {
throw new BusinessException(ErrorCode.UNAUTHORIZED_REQUEST);
}

if (!userLibrary.isSameBook(book.getIsbn())) {
throw new BusinessException(ErrorCode.BOOK_INVALID_INFO);
}

userLibrary.editReadingStatus(ReadingStatus.createReadingStatus(request.getReadingStatus()));
}

@Transactional
public void deleteUserLibrary(Long userLibraryId, Long userId, String isbn) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND));

Book book = bookRepository.findById(isbn)
.orElseThrow(() -> new BusinessException(ErrorCode.BOOK_NOT_FOUND));

UserLibrary userLibrary = userLibraryRepository.findByIdWithBookAndUser(userLibraryId)
.orElseThrow(() -> new BusinessException(ErrorCode.USER_LIBRARY_NOT_FOUND));

if (!userLibrary.isUserLibraryOwner(user.getId())) {
throw new BusinessException(ErrorCode.UNAUTHORIZED_REQUEST);
}

if (!userLibrary.isSameBook(book.getIsbn())) {
throw new BusinessException(ErrorCode.BOOK_INVALID_INFO);
}

userLibraryRepository.deleteById(userLibrary.getId());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.jisungin.application.userlibrary.request;

import com.jisungin.domain.ReadingStatus;
import com.jisungin.domain.book.Book;
import com.jisungin.domain.mylibrary.UserLibrary;
import com.jisungin.domain.user.User;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class UserLibraryCreateServiceRequest {

private String isbn;
private String readingStatus;

@Builder
private UserLibraryCreateServiceRequest(String isbn, String readingStatus) {
this.isbn = isbn;
this.readingStatus = readingStatus;
}

public UserLibrary toEntity(User user, Book book) {
return UserLibrary.builder()
.user(user)
.book(book)
.status(ReadingStatus.createReadingStatus(readingStatus))
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.jisungin.application.userlibrary.request;

import com.jisungin.api.userlibrary.request.UserLibraryCreateRequest;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class UserLibraryEditServiceRequest {

private String isbn;
private String readingStatus;

@Builder
private UserLibraryEditServiceRequest(String isbn, String readingStatus) {
this.isbn = isbn;
this.readingStatus = readingStatus;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.jisungin.application.userlibrary.response;


import com.jisungin.domain.mylibrary.UserLibrary;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class UserLibraryResponse {

private Long id;
private String status;
private Boolean hasReadingStatus;

@Builder
private UserLibraryResponse(Long id, String status, Boolean hasReadingStatus) {
this.id = id;
this.status = status;
this.hasReadingStatus = hasReadingStatus;
}

public static UserLibraryResponse of(UserLibrary userLibrary) {
if (userLibrary == null) {
return empty();
}

return UserLibraryResponse.builder()
.id(userLibrary.getId())
.status(userLibrary.getStatus().getText())
.hasReadingStatus(true)
.build();
}

public static UserLibraryResponse empty() {
return UserLibraryResponse.builder()
.hasReadingStatus(false)
.build();
}

}
5 changes: 5 additions & 0 deletions src/main/java/com/jisungin/domain/ReadingStatus.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.jisungin.exception.BusinessException;
import com.jisungin.exception.ErrorCode;
import java.util.List;
import java.util.Locale;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

Expand All @@ -18,6 +19,10 @@ public enum ReadingStatus {

private final String text;

public static ReadingStatus createReadingStatus(String status) {
return ReadingStatus.valueOf(status.toUpperCase(Locale.ENGLISH));
}

public static List<ReadingStatus> createReadingStatus(List<String> statusList) {
if (statusList == null) {
throw new BusinessException(ErrorCode.PARTICIPATION_CONDITION_ERROR);
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/jisungin/domain/book/Book.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,8 @@ private Book(String isbn, String title, String content, String authors, String p
this.dateTime = dateTime;
}

public boolean isSame(String isbn) {
return this.isbn.equals(isbn);
}

}
Loading

0 comments on commit b08f886

Please sign in to comment.