diff --git a/src/main/java/org/sopt/seminar2/diary/api/DiaryController.java b/src/main/java/org/sopt/seminar2/diary/api/DiaryController.java deleted file mode 100644 index a982243..0000000 --- a/src/main/java/org/sopt/seminar2/diary/api/DiaryController.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.sopt.seminar2.diary.api; - -import jakarta.validation.Valid; -import lombok.RequiredArgsConstructor; - -import org.sopt.seminar2.diary.api.dto.request.DiaryCreateRequest; -import org.sopt.seminar2.diary.api.dto.request.DiaryUpdateRequest; -import org.sopt.seminar2.diary.api.dto.response.DiaryDetailResponse; -import org.sopt.seminar2.diary.api.dto.response.DiaryListResponse; -import org.sopt.seminar2.diary.service.DiaryService; - -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; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api") -public class DiaryController { - - private final DiaryService diaryService; - - @PostMapping("/diary") - public ResponseEntity postDiary( - @Valid @RequestBody DiaryCreateRequest diaryCreateRequest - ) { - diaryService.postDiary(diaryCreateRequest); - return ResponseEntity.ok().build(); - } - - @GetMapping("/diary/{diaryId}") - public ResponseEntity getDiary( - @PathVariable long diaryId - ) { - DiaryDetailResponse diaryDetailResponse = diaryService.getDiary(diaryId); - return ResponseEntity.ok().body(diaryDetailResponse); - } - - @GetMapping("/diary") - public ResponseEntity getDiaryList() { - DiaryListResponse diaryListResponse = diaryService.getDiaryList(); - return ResponseEntity.ok().body(diaryListResponse); - } - - @DeleteMapping("/diary/{diaryId}") - public ResponseEntity deleteDiary( - @PathVariable long diaryId - ) { - diaryService.deleteDiary(diaryId); - return ResponseEntity.ok().build(); - } - - @PatchMapping("/diary/{diaryId}") - public ResponseEntity updateDiary( - @PathVariable long diaryId, - @RequestBody DiaryUpdateRequest diaryUpdateRequest - ) { - diaryService.updateDiary(diaryId, diaryUpdateRequest); - return ResponseEntity.ok().build(); - } - -} diff --git a/src/main/java/org/sopt/seminar2/diary/api/annotation/CheckUserAuth.java b/src/main/java/org/sopt/seminar2/diary/api/annotation/CheckUserAuth.java new file mode 100644 index 0000000..bb56758 --- /dev/null +++ b/src/main/java/org/sopt/seminar2/diary/api/annotation/CheckUserAuth.java @@ -0,0 +1,12 @@ +package org.sopt.seminar2.diary.api.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.METHOD}) +public @interface CheckUserAuth { +} diff --git a/src/main/java/org/sopt/seminar2/diary/api/controller/DiaryController.java b/src/main/java/org/sopt/seminar2/diary/api/controller/DiaryController.java new file mode 100644 index 0000000..8d34886 --- /dev/null +++ b/src/main/java/org/sopt/seminar2/diary/api/controller/DiaryController.java @@ -0,0 +1,113 @@ +package org.sopt.seminar2.diary.api.controller; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +import org.sopt.seminar2.diary.api.annotation.CheckUserAuth; +import org.sopt.seminar2.diary.api.dto.request.DiaryCreateRequest; +import org.sopt.seminar2.diary.api.dto.request.DiaryUpdateRequest; +import org.sopt.seminar2.diary.api.dto.response.DiaryDetailResponse; +import org.sopt.seminar2.diary.api.dto.response.DiaryListResponse; +import org.sopt.seminar2.diary.common.dto.SuccessResponse; +import org.sopt.seminar2.diary.domain.enums.Category; +import org.sopt.seminar2.diary.service.DiaryService; + +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.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import static org.sopt.seminar2.diary.common.code.SuccessCode.SUCCESS_CREATE_DIARY; +import static org.sopt.seminar2.diary.common.code.SuccessCode.SUCCESS_GET_DIARY_DETAIL; +import static org.sopt.seminar2.diary.common.code.SuccessCode.SUCCESS_DELETE_DIARY; +import static org.sopt.seminar2.diary.common.code.SuccessCode.SUCCESS_UPDATE_DIARY; + + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api") +public class DiaryController { + + private final String USERNAME_HEADER = "username"; + private final String PASSWORD_HEADER = "password"; + + private final DiaryService diaryService; + + @PostMapping("/diary") + public ResponseEntity postDiary( + @Valid @RequestBody DiaryCreateRequest diaryCreateRequest, + @RequestHeader(USERNAME_HEADER) String username, + @RequestHeader(PASSWORD_HEADER) String password + ) { + diaryService.postDiary( + diaryCreateRequest.title(), + diaryCreateRequest.content(), + Category.findCategory(diaryCreateRequest.category()), + username, + password + ); + return ResponseEntity.ok(SuccessResponse.of(SUCCESS_CREATE_DIARY)); + } + + // 일기 상세 조회 + @GetMapping("/diary/{diaryId}") + public ResponseEntity> getDiary( + @PathVariable long diaryId + ) { + DiaryDetailResponse diaryDetailResponse = diaryService.getDiary(diaryId); + return ResponseEntity.ok(SuccessResponse.of(SUCCESS_GET_DIARY_DETAIL, diaryDetailResponse)); + } + + // 메인 홈 + @GetMapping("/diary") + public ResponseEntity getDiaryList( + @RequestParam(required = false) String category + ) { + DiaryListResponse diaryListResponse = diaryService.getDiaryList(Category.findCategory(category)); + return ResponseEntity.ok().body(diaryListResponse); + } + + // 내 일기 모아보기 + @GetMapping("/mypage/diary") + public ResponseEntity getMyDairyList( + @RequestHeader(USERNAME_HEADER) String username, + @RequestHeader(PASSWORD_HEADER) String password, + @RequestParam(required = false) String category + ) { + DiaryListResponse diaryListResponse = diaryService.getMyDiaryList(Category.findCategory(category), username, password); + return ResponseEntity.ok().body(diaryListResponse); + } + + // 일기 삭제 + @DeleteMapping("/diary/{diaryId}") + @CheckUserAuth + public ResponseEntity deleteDiary( + @PathVariable long diaryId, + @RequestHeader(USERNAME_HEADER) String username, + @RequestHeader(PASSWORD_HEADER) String password + ) { + diaryService.deleteDiary(diaryId, username, password); + return ResponseEntity.ok(SuccessResponse.of(SUCCESS_DELETE_DIARY)); + } + + // 일기 수정 + @PatchMapping("/diary/{diaryId}") + @CheckUserAuth + public ResponseEntity updateDiary( + @PathVariable long diaryId, + @RequestHeader(USERNAME_HEADER) String username, + @RequestHeader(PASSWORD_HEADER) String password, + @RequestBody DiaryUpdateRequest diaryUpdateRequest + ) { + diaryService.updateDiary(diaryId, diaryUpdateRequest.content(), username, password); + return ResponseEntity.ok(SuccessResponse.of(SUCCESS_UPDATE_DIARY)); + } + +} diff --git a/src/main/java/org/sopt/seminar2/diary/api/dto/request/DiaryCreateRequest.java b/src/main/java/org/sopt/seminar2/diary/api/dto/request/DiaryCreateRequest.java index dc4028a..dae5078 100644 --- a/src/main/java/org/sopt/seminar2/diary/api/dto/request/DiaryCreateRequest.java +++ b/src/main/java/org/sopt/seminar2/diary/api/dto/request/DiaryCreateRequest.java @@ -3,9 +3,13 @@ import jakarta.validation.constraints.Size; public record DiaryCreateRequest( + + @Size(min = 1, max = 10, message = "제목은 10자 이하여야 합니다.") String title, - @Size(max = 30, message = "글자수는 30자 이하여야 합니다.") - String content + @Size(min = 1, max = 30, message = "본문은 30자 이하여야 합니다.") + String content, + + String category ) { } diff --git a/src/main/java/org/sopt/seminar2/diary/api/interceptor/CheckUserAuthInterceptor.java b/src/main/java/org/sopt/seminar2/diary/api/interceptor/CheckUserAuthInterceptor.java new file mode 100644 index 0000000..c1a05ad --- /dev/null +++ b/src/main/java/org/sopt/seminar2/diary/api/interceptor/CheckUserAuthInterceptor.java @@ -0,0 +1,78 @@ +package org.sopt.seminar2.diary.api.interceptor; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import lombok.RequiredArgsConstructor; + +import org.sopt.seminar2.diary.api.annotation.CheckUserAuth; +import org.sopt.seminar2.diary.common.exception.DiaryException; +import org.sopt.seminar2.diary.common.exception.UserException; +import org.sopt.seminar2.diary.domain.Diary; +import org.sopt.seminar2.diary.domain.User; +import org.sopt.seminar2.diary.domain.repository.DiaryRepository; +import org.sopt.seminar2.diary.domain.repository.UserRepository; + + +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import org.springframework.web.servlet.HandlerMapping; + +import java.util.Map; + +import static org.sopt.seminar2.diary.common.code.FailureCode.*; + + +@Component +@RequiredArgsConstructor +public class CheckUserAuthInterceptor implements HandlerInterceptor { + + private final String USERNAME_HEADER = "username"; + private final String PASSWORD_HEADER = "password"; + + private final DiaryRepository diaryRepository; + private final UserRepository userRepository; + + @Override + @Transactional + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + + // Handler가 메소드인 경우에만 진행 + if (handler instanceof HandlerMethod) { + HandlerMethod handlerMethod = (HandlerMethod) handler; + + // 핸들러 메서드에 @CheckUserAuth 어노테이션이 있는지 확인 + if (handlerMethod.getMethod().isAnnotationPresent(CheckUserAuth.class)) { + String username = request.getHeader(USERNAME_HEADER); + String password = request.getHeader(PASSWORD_HEADER); + + Map pathVariables = (Map) request + .getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + + Long diaryId = Long.valueOf(pathVariables.get("diaryId")); + + User loginUser = getLoginUser(username); + Diary diary = getDiary(diaryId); + + boolean isOwner = loginUser.equals(diary.getUser()); + + if (!isOwner) { + throw new UserException(NO_PERMISSON_FOR_DAIRY); + } + } + } + return true; + } + + private User getLoginUser(String username) { + return userRepository.findByUsername(username) + .orElseThrow(() -> new UserException(NOT_EXISTS_USER)); + } + + private Diary getDiary(long diaryId) { + return diaryRepository.findById(diaryId) + .orElseThrow(() -> new DiaryException(NOT_EXISTS_DIARY_WITH_ID)); + } +} diff --git a/src/main/java/org/sopt/seminar2/diary/common/GlobalExceptionHandler.java b/src/main/java/org/sopt/seminar2/diary/common/GlobalExceptionHandler.java new file mode 100644 index 0000000..97cbcb0 --- /dev/null +++ b/src/main/java/org/sopt/seminar2/diary/common/GlobalExceptionHandler.java @@ -0,0 +1,55 @@ +package org.sopt.seminar2.diary.common; + +import lombok.extern.slf4j.Slf4j; + +import org.sopt.seminar2.diary.common.code.FailureCode; +import org.sopt.seminar2.diary.common.dto.ErrorResponse; +import org.sopt.seminar2.diary.common.exception.ApiException; + +import org.springframework.http.ResponseEntity; +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; + +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(ApiException.class) + public ResponseEntity handleDiaryException(ApiException e) { + log.error(e.getMessage()); + + FailureCode failureCode = e.getFailureCode(); + return ResponseEntity.status(failureCode.getHttpStatus()) + .body(ErrorResponse.of( + failureCode.getHttpStatus().value(), + failureCode.getMessage() + )); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { + log.error("[MethodArgumentNotValidException] 발생 : {}", e.getMessage()); + + FailureCode failureCode = FailureCode.INVALID_VALUE; + return ResponseEntity.status(failureCode.getHttpStatus()) + .body(ErrorResponse.of( + failureCode.getHttpStatus().value(), + failureCode.getMessage(), + e.getBindingResult() + )); + } + + @ExceptionHandler(MissingServletRequestParameterException.class) + public ResponseEntity handleValidationFailure(MissingServletRequestParameterException e) { + log.error("[MissingParameterException] 발생 : {}", e.getMessage()); + + FailureCode failureCode = FailureCode.MISSING_PARAMETER; + return ResponseEntity.status(failureCode.getHttpStatus()) + .body(ErrorResponse.of( + failureCode.getHttpStatus().value(), + failureCode.getMessage() + )); + } +} diff --git a/src/main/java/org/sopt/seminar2/diary/common/code/FailureCode.java b/src/main/java/org/sopt/seminar2/diary/common/code/FailureCode.java new file mode 100644 index 0000000..4f086af --- /dev/null +++ b/src/main/java/org/sopt/seminar2/diary/common/code/FailureCode.java @@ -0,0 +1,33 @@ +package org.sopt.seminar2.diary.common.code; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum FailureCode { + + INVALID_VALUE(HttpStatus.BAD_REQUEST, "잘못된 값입니다."), + MISSING_PARAMETER(HttpStatus.BAD_REQUEST, "필수 파라미터가 없습니다."), + NOT_EXISTS_CATEGORY(HttpStatus.BAD_REQUEST, "잘못된 카테고리 값입니다."), + + // User + NOT_EXISTS_USER(HttpStatus.NOT_FOUND, "해당 이름을 가진 유저가 존재하지 않습니다."), + NOT_MATCH_PASSWORD(HttpStatus.BAD_REQUEST, "비밀번호가 일치하지 않습니다."), + NO_PERMISSON_FOR_DAIRY(HttpStatus.FORBIDDEN, "해당 일기에 대한 권한이 없습니다."), + + // Diary + NOT_EXISTS_DIARY_WITH_ID(HttpStatus.NOT_FOUND, "id에 해당하는 일기가 없습니다."), + NOT_EXISTS_DIARY_WITH_ID_AND_USER(HttpStatus.NOT_FOUND, "유저와 id에 해당하는 일기가 없습니다."); + + + private final HttpStatus httpStatus; + private final String message; + + public String getMessage() { + return message; + } + + public HttpStatus getHttpStatus() { + return httpStatus; + } +} diff --git a/src/main/java/org/sopt/seminar2/diary/common/code/SuccessCode.java b/src/main/java/org/sopt/seminar2/diary/common/code/SuccessCode.java new file mode 100644 index 0000000..18d0908 --- /dev/null +++ b/src/main/java/org/sopt/seminar2/diary/common/code/SuccessCode.java @@ -0,0 +1,28 @@ +package org.sopt.seminar2.diary.common.code; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + + +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public enum SuccessCode { + + SUCCESS_CREATE_DIARY(HttpStatus.CREATED, "일기 작성에 성공했습니다."), + SUCCESS_UPDATE_DIARY(HttpStatus.OK, "일기 수정에 성공했습니다."), + SUCCESS_DELETE_DIARY(HttpStatus.OK, "일기 삭제에 성공했습니다."), + SUCCESS_GET_DIARY_DETAIL(HttpStatus.OK, "일기 상세 조회에 성공했습니다."); + + private final HttpStatus httpStatus; + private final String message; + + public HttpStatus getHttpStatus() { + return httpStatus; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/org/sopt/seminar2/diary/common/dto/ErrorResponse.java b/src/main/java/org/sopt/seminar2/diary/common/dto/ErrorResponse.java new file mode 100644 index 0000000..164df29 --- /dev/null +++ b/src/main/java/org/sopt/seminar2/diary/common/dto/ErrorResponse.java @@ -0,0 +1,49 @@ +package org.sopt.seminar2.diary.common.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Builder; +import lombok.Getter; +import org.springframework.validation.BindingResult; +import org.springframework.validation.FieldError; + +import java.util.List; + +@Builder +public record ErrorResponse( + int status, + String message, + @JsonInclude(JsonInclude.Include.NON_NULL) + List errors +) { + public static ErrorResponse of(int status, String message){ + return ErrorResponse.builder() + .status(status) + .message(message) + .build(); + } + + public static ErrorResponse of(int status, String message, BindingResult bindingResult){ + return ErrorResponse.builder() + .status(status) + .message(message) + .errors(ValidationError.of(bindingResult)) + .build(); + } + + @Getter + public static class ValidationError { + private final String field; + private final String message; + + private ValidationError(FieldError fieldError){ + this.field = fieldError.getField(); + this.message = fieldError.getDefaultMessage(); + } + + public static List of(final BindingResult bindingResult){ + return bindingResult.getFieldErrors().stream() + .map(ValidationError::new) + .toList(); + } + } +} diff --git a/src/main/java/org/sopt/seminar2/diary/common/dto/SuccessResponse.java b/src/main/java/org/sopt/seminar2/diary/common/dto/SuccessResponse.java new file mode 100644 index 0000000..d7d4015 --- /dev/null +++ b/src/main/java/org/sopt/seminar2/diary/common/dto/SuccessResponse.java @@ -0,0 +1,21 @@ +package org.sopt.seminar2.diary.common.dto; + +import org.sopt.seminar2.diary.common.code.SuccessCode; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +@JsonPropertyOrder({"status", "message", "result"}) +public record SuccessResponse( + int status, + String message, + @JsonInclude(JsonInclude.Include.NON_NULL) + T result +) { + public static SuccessResponse of(SuccessCode successCode){ + return new SuccessResponse(successCode.getHttpStatus().value(), successCode.getMessage(), null); + } + + public static SuccessResponse of(SuccessCode successCode, T result){ + return new SuccessResponse(successCode.getHttpStatus().value(), successCode.getMessage(), result); + } +} diff --git a/src/main/java/org/sopt/seminar2/diary/common/exception/ApiException.java b/src/main/java/org/sopt/seminar2/diary/common/exception/ApiException.java new file mode 100644 index 0000000..8ec969f --- /dev/null +++ b/src/main/java/org/sopt/seminar2/diary/common/exception/ApiException.java @@ -0,0 +1,23 @@ +package org.sopt.seminar2.diary.common.exception; + +import org.sopt.seminar2.diary.common.code.FailureCode; + +public class ApiException extends RuntimeException { + + private final FailureCode failureCode; + private final String errorMessage; + + public FailureCode getFailureCode() { + return failureCode; + } + + public String getErrorMessage() { + return errorMessage; + } + + public ApiException(FailureCode failureCode, String errorMessage) { + super(errorMessage + ": " + failureCode.getMessage()); + this.failureCode = failureCode; + this.errorMessage = errorMessage; + } +} diff --git a/src/main/java/org/sopt/seminar2/diary/common/exception/DiaryException.java b/src/main/java/org/sopt/seminar2/diary/common/exception/DiaryException.java new file mode 100644 index 0000000..6df1882 --- /dev/null +++ b/src/main/java/org/sopt/seminar2/diary/common/exception/DiaryException.java @@ -0,0 +1,18 @@ +package org.sopt.seminar2.diary.common.exception; + +import org.sopt.seminar2.diary.common.code.FailureCode; + +public class DiaryException extends ApiException { + + private final FailureCode failureCode; + + public FailureCode getFailureCode() { + return failureCode; + } + + public DiaryException(FailureCode failureCode) { + super(failureCode, "[DiaryException]"); + this.failureCode = failureCode; + } + +} diff --git a/src/main/java/org/sopt/seminar2/diary/common/exception/UserException.java b/src/main/java/org/sopt/seminar2/diary/common/exception/UserException.java new file mode 100644 index 0000000..fc5222d --- /dev/null +++ b/src/main/java/org/sopt/seminar2/diary/common/exception/UserException.java @@ -0,0 +1,17 @@ +package org.sopt.seminar2.diary.common.exception; + +import org.sopt.seminar2.diary.common.code.FailureCode; + +public class UserException extends ApiException { + + private final FailureCode failureCode; + + public FailureCode getFailureCode() { + return failureCode; + } + + public UserException(FailureCode failureCode) { + super(failureCode, "[UserException]"); + this.failureCode = failureCode; + } +} diff --git a/src/main/java/org/sopt/seminar2/diary/domain/Diary.java b/src/main/java/org/sopt/seminar2/diary/domain/Diary.java index e08211a..a1fdedd 100644 --- a/src/main/java/org/sopt/seminar2/diary/domain/Diary.java +++ b/src/main/java/org/sopt/seminar2/diary/domain/Diary.java @@ -1,7 +1,13 @@ package org.sopt.seminar2.diary.domain; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import org.sopt.seminar2.diary.domain.enums.Category; + import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -12,6 +18,7 @@ import java.time.LocalDateTime; +import static jakarta.persistence.FetchType.LAZY; import static lombok.AccessLevel.PROTECTED; @Entity @@ -23,23 +30,46 @@ public class Diary { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @ManyToOne(fetch = LAZY) + @JoinColumn(name = "user_id", nullable = false) + private User user; + + @Column(unique = true, nullable = false) private String title; + @Column(nullable = false) private String content; + @Enumerated(EnumType.STRING) + private Category category; + private LocalDateTime createdAt; @Builder - private Diary(final String title, final String content) { + private Diary( + final User user, + final String title, + final String content, + final Category category + ) { + this.user = user; this.title = title; this.content = content; this.createdAt = LocalDateTime.now(); + this.category = category; } - public static Diary create(final String title, final String content) { + public static Diary create( + final User user, + final String title, + final String content, + final Category category + ) { return Diary.builder() + .user(user) .title(title) .content(content) + .category(category) .build(); } diff --git a/src/main/java/org/sopt/seminar2/diary/domain/User.java b/src/main/java/org/sopt/seminar2/diary/domain/User.java new file mode 100644 index 0000000..d82e343 --- /dev/null +++ b/src/main/java/org/sopt/seminar2/diary/domain/User.java @@ -0,0 +1,40 @@ +package org.sopt.seminar2.diary.domain; + + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.NoArgsConstructor; + +import static jakarta.persistence.GenerationType.IDENTITY; +import static lombok.AccessLevel.PROTECTED; + +@Entity +@NoArgsConstructor(access = PROTECTED) +@Table(name = "Users") +public class User { + + @Id + @GeneratedValue(strategy = IDENTITY) + private Long id; + + private String username; + + private String password; + + private String nickname; + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getNickname() { + return nickname; + } +} diff --git a/src/main/java/org/sopt/seminar2/diary/domain/enums/Category.java b/src/main/java/org/sopt/seminar2/diary/domain/enums/Category.java new file mode 100644 index 0000000..f588a13 --- /dev/null +++ b/src/main/java/org/sopt/seminar2/diary/domain/enums/Category.java @@ -0,0 +1,27 @@ +package org.sopt.seminar2.diary.domain.enums; + +import lombok.RequiredArgsConstructor; +import org.sopt.seminar2.diary.common.exception.DiaryException; + +import java.util.Arrays; + +import static org.sopt.seminar2.diary.common.code.FailureCode.NOT_EXISTS_CATEGORY; + +@RequiredArgsConstructor +public enum Category { + + FOOD("food"), + SCHOOL("school"), + MOVIE("movie"), + WORK_OUT("workout"); + + private final String name; + + public static Category findCategory(String name) { + if (name == null) { + return null; + } + return Arrays.stream(Category.values()).filter(category -> category.name.equals(name)).findAny() + .orElseThrow(() -> new DiaryException(NOT_EXISTS_CATEGORY)); + } +} diff --git a/src/main/java/org/sopt/seminar2/diary/domain/repository/DiaryRepository.java b/src/main/java/org/sopt/seminar2/diary/domain/repository/DiaryRepository.java index ab203f7..c296230 100644 --- a/src/main/java/org/sopt/seminar2/diary/domain/repository/DiaryRepository.java +++ b/src/main/java/org/sopt/seminar2/diary/domain/repository/DiaryRepository.java @@ -1,12 +1,27 @@ package org.sopt.seminar2.diary.domain.repository; import org.sopt.seminar2.diary.domain.Diary; +import org.sopt.seminar2.diary.domain.User; + +import org.sopt.seminar2.diary.domain.enums.Category; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Component; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.List; +import java.util.Optional; public interface DiaryRepository extends JpaRepository { - List findAllByOrderByCreatedAtDesc(); + @Query("SELECT d FROM Diary d " + + "WHERE (:category IS NULL OR d.category = :category) " + + "ORDER BY d.createdAt DESC") + List findTop10ByCategory(@Param("category") Category category); + + @Query("SELECT d FROM Diary d " + + "WHERE (:category IS NULL OR d.category = :category) AND (d.user = :user)" + + "ORDER BY d.createdAt DESC") + List findTop10ByCategoryAndUser(@Param("category") Category category, @Param("user") User user); + + Optional findByIdAndUser(Long id, User user); } diff --git a/src/main/java/org/sopt/seminar2/diary/domain/repository/UserRepository.java b/src/main/java/org/sopt/seminar2/diary/domain/repository/UserRepository.java new file mode 100644 index 0000000..64f3c92 --- /dev/null +++ b/src/main/java/org/sopt/seminar2/diary/domain/repository/UserRepository.java @@ -0,0 +1,12 @@ +package org.sopt.seminar2.diary.domain.repository; + +import org.sopt.seminar2.diary.domain.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface UserRepository extends JpaRepository { + + Optional findByUsername(String username); + +} diff --git a/src/main/java/org/sopt/seminar2/diary/global/config/WebConfig.java b/src/main/java/org/sopt/seminar2/diary/global/config/WebConfig.java new file mode 100644 index 0000000..2d1eee4 --- /dev/null +++ b/src/main/java/org/sopt/seminar2/diary/global/config/WebConfig.java @@ -0,0 +1,20 @@ +package org.sopt.seminar2.diary.global.config; + +import lombok.RequiredArgsConstructor; + +import org.sopt.seminar2.diary.api.interceptor.CheckUserAuthInterceptor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@RequiredArgsConstructor +public class WebConfig implements WebMvcConfigurer { + + private final CheckUserAuthInterceptor checkUserAuthInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(checkUserAuthInterceptor); + } +} diff --git a/src/main/java/org/sopt/seminar2/diary/service/DiaryService.java b/src/main/java/org/sopt/seminar2/diary/service/DiaryService.java index 915046b..0692e59 100644 --- a/src/main/java/org/sopt/seminar2/diary/service/DiaryService.java +++ b/src/main/java/org/sopt/seminar2/diary/service/DiaryService.java @@ -1,33 +1,44 @@ package org.sopt.seminar2.diary.service; -import jakarta.persistence.EntityNotFoundException; -import org.sopt.seminar2.diary.api.dto.request.DiaryCreateRequest; -import org.sopt.seminar2.diary.api.dto.request.DiaryUpdateRequest; import org.sopt.seminar2.diary.api.dto.response.DiaryDetailResponse; import org.sopt.seminar2.diary.api.dto.response.DiaryListResponse; +import org.sopt.seminar2.diary.common.exception.DiaryException; +import org.sopt.seminar2.diary.common.exception.UserException; +import org.sopt.seminar2.diary.domain.enums.Category; +import org.sopt.seminar2.diary.domain.repository.DiaryRepository; +import org.sopt.seminar2.diary.domain.repository.UserRepository; + import org.sopt.seminar2.diary.domain.Diary; +import org.sopt.seminar2.diary.domain.User; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.sopt.seminar2.diary.domain.repository.DiaryRepository; - import lombok.RequiredArgsConstructor; import java.util.List; +import static org.sopt.seminar2.diary.common.code.FailureCode.*; + @Service @RequiredArgsConstructor public class DiaryService { private final DiaryRepository diaryRepository; - - public void postDiary(final DiaryCreateRequest diaryCreateRequest) { - Diary diary = Diary.create(diaryCreateRequest.title(), diaryCreateRequest.content()); + private final UserRepository userRepository; + + public void postDiary( + final String title, + final String content, + final Category category, + final String username, + final String password + ) { + User user = findUser(username, password); + Diary diary = Diary.create(user, title, content, category); diaryRepository.save(diary); } - public DiaryDetailResponse getDiary(final long id) { Diary diary = findDiary(id); @@ -39,26 +50,62 @@ public DiaryDetailResponse getDiary(final long id) { ); } - public DiaryListResponse getDiaryList() { - List diaries = diaryRepository.findAllByOrderByCreatedAtDesc(); - List diaryDetailResponses = diaries.stream() - .map(diary -> DiaryListResponse.DiaryResponse.of(diary.getId(), diary.getTitle())) - .toList(); + public DiaryListResponse getDiaryList(final Category category) { + return DiaryListResponse.of(getDiaries(category, null)); + } - return DiaryListResponse.of(diaryDetailResponses); + public DiaryListResponse getMyDiaryList(final Category category, final String username, final String password) { + User user = findUser(username, password); + return DiaryListResponse.of(getDiaries(category, user)); } @Transactional - public void updateDiary(final long id, final DiaryUpdateRequest diaryUpdateRequest) { - findDiary(id).updateDiary(diaryUpdateRequest.content()); + public void updateDiary( + final long id, + final String content, + final String username, + final String password + ) { + User user = findUser(username, password); + findDiaryWithUser(id, user).updateDiary(content); } - public void deleteDiary(final long id) { - diaryRepository.delete(findDiary(id)); + public void deleteDiary(final long id, final String username, final String password) { + User user = findUser(username, password); + diaryRepository.delete(findDiaryWithUser(id, user)); } - public Diary findDiary(final long id) { - return diaryRepository.findById(id) - .orElseThrow(() -> new EntityNotFoundException("id에 해당하는 다이어리가 없습니다.")); + private Diary findDiaryWithUser(final long id, final User user) { + return diaryRepository.findByIdAndUser(id, user) + .orElseThrow(() -> new DiaryException(NOT_EXISTS_DIARY_WITH_ID_AND_USER)); } + + private Diary findDiary(final long diaryId) { + return diaryRepository.findById(diaryId) + .orElseThrow(() -> new DiaryException(NOT_EXISTS_DIARY_WITH_ID)); + } + + private User findUser(final String username, final String password) { + User user = userRepository.findByUsername(username) + .orElseThrow(() -> new UserException(NOT_EXISTS_USER)); + + if (!user.getPassword().equals(password)) { + throw new UserException(NOT_MATCH_PASSWORD); + } + + return user; + } + + private List getDiaries(final Category category, final User user) { + List diaries; + if (user == null) { + diaries = diaryRepository.findTop10ByCategory(category); + } else { + diaries = diaryRepository.findTop10ByCategoryAndUser(category, user); + } + return diaries.stream() + .map(diary -> DiaryListResponse.DiaryResponse.of(diary.getId(), diary.getTitle())) + .toList(); + } + }