diff --git a/.gitignore b/.gitignore index a63c0a1..3e5495e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,6 @@ build/ .idea/workspace.xml .idea/libraries/ .idea/** -.idea/vcs.xml .idea .idea/ *.iws diff --git a/build.gradle b/build.gradle index 4f66c9f..8ec9292 100644 --- a/build.gradle +++ b/build.gradle @@ -18,12 +18,14 @@ java { } dependencies { + implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-test' implementation 'org.junit.platform:junit-platform-launcher' - + implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + runtimeOnly 'com.h2database:h2' runtimeOnly 'mysql:mysql-connector-java:8.0.32' @@ -34,4 +36,8 @@ dependencies { test { useJUnitPlatform() +} + +tasks.withType(JavaCompile) { + options.compilerArgs << "-parameters" } \ No newline at end of file diff --git a/src/main/java/org/sopt/Diary/api/DiaryController.java b/src/main/java/org/sopt/Diary/api/DiaryController.java deleted file mode 100644 index 6543605..0000000 --- a/src/main/java/org/sopt/Diary/api/DiaryController.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.sopt.Diary.api; - -import org.sopt.Diary.dto.req.DiaryUpdateRequest; -import org.sopt.Diary.dto.res.DiariesResponse; -import org.sopt.Diary.dto.req.DiaryRequest; -import org.sopt.Diary.dto.res.DiaryResponse; -import org.sopt.Diary.repository.Category; -import org.sopt.Diary.service.Diary; -import org.sopt.Diary.service.DiaryService; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - - -import java.util.List; - -@RestController -public class DiaryController { - - private final DiaryService diaryService; - - public DiaryController(DiaryService diaryService) { - this.diaryService = diaryService; - } - - private final static int LENGTH_LIMIT = 30; - - @PostMapping("/diary") - ResponseEntity postDiary(@RequestBody final DiaryRequest diaryRequest) { - //final 을 붙여서 매개변수의 재할당을 막음!! - - if(diaryRequest.content().length()>LENGTH_LIMIT){ - return ResponseEntity.badRequest().body("글자 수는 30자를 넘을 수 없습니다"); - } - diaryService.createDiary(diaryRequest); - return ResponseEntity.ok("일기가 생성되었습니다."); - } - - @GetMapping("/diaries") - public ResponseEntity> getDiaries( - @RequestParam(name = "category", required = false) Category category, - @RequestParam(name = "sortByContentLength",required = false, defaultValue = "false")final Boolean sortByContentLength) { - - - final List diariesResponses; - // 각 조건에 따라 한번만 할당되고 추가로 변경되지 않음을 명확하게 하기 위해 final 사용 - - if (category != null) { - // 카테고리로 정렬 - diariesResponses = diaryService.getDiaryListSortByCategory(category); - } else if (sortByContentLength) { - - // 글자수로 정렬 - diariesResponses = diaryService.getDiaryListSortByContent(); - } else { - //최신순으로 정렬 - diariesResponses = diaryService.getDiaryList(); - } - - return ResponseEntity.ok(diariesResponses); - } - - @GetMapping("/diary/{id}") - ResponseEntity getDiary(@PathVariable(name = "id") final Long id) { - final Diary savedDiary = diaryService.getDiary(id); - - // new 가 아닌 factory 메소드를 사용하면 불변 객체를 만들 수 있는데... 이 부분에 대해서 고민중.. - final DiaryResponse diaryResponse = new DiaryResponse( - savedDiary.getId(), - savedDiary.getTitle(), - savedDiary.getContent(), - savedDiary.getCreatedAt(), - savedDiary.getCategory() - ); - - return ResponseEntity.ok(diaryResponse); - } - - @PatchMapping("/diary/{id}") - ResponseEntity updateDiary(@PathVariable(name = "id") final Long id, @RequestBody DiaryUpdateRequest diaryRequest){ - - if (diaryRequest.content().length() > LENGTH_LIMIT) { - return ResponseEntity.badRequest().body("글자 수는 30자를 넘을 수 없습니다"); - } - diaryService.patchDiary(id,diaryRequest.content(),diaryRequest.category()); - return ResponseEntity.ok("일기가 수정되었습니다."); - } - - @DeleteMapping("/diary/{id}") - ResponseEntity deleteDiary(@PathVariable(name = "id") final Long id){ - diaryService.deleteDiary(id); - return ResponseEntity.ok("일기가 삭제되었습니다."); - } -} diff --git a/src/main/java/org/sopt/Diary/controller/DiariesController.java b/src/main/java/org/sopt/Diary/controller/DiariesController.java new file mode 100644 index 0000000..9babc32 --- /dev/null +++ b/src/main/java/org/sopt/Diary/controller/DiariesController.java @@ -0,0 +1,41 @@ +package org.sopt.Diary.controller; + +import jakarta.validation.Valid; +import org.sopt.Diary.dto.res.DiaryListRes; +import org.sopt.Diary.entity.Category; +import org.sopt.Diary.entity.SortType; +import org.sopt.Diary.service.DiariesService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + + +@RequestMapping("/diaries") +@RestController +public class DiariesController { + + private final DiariesService diariesService; + + public DiariesController(DiariesService diariesService) { + this.diariesService = diariesService; + } + + @GetMapping() + public ResponseEntity getDiaries( + @RequestHeader(name="userId" , required = false) Long userId, + @RequestParam(name = "category" , required = false, defaultValue = "ALL") final Category category, + @RequestParam(name = "sort",required = false, defaultValue = "LATEST") final SortType sortType) { + + DiaryListRes diaryListRes = diariesService.getDiariesResponse( category,sortType, false, userId); + return ResponseEntity.ok(diaryListRes); + } + + @GetMapping("/my") + public ResponseEntity getMyDiaries( + @Valid @RequestHeader("userId") long userId, + @Valid @RequestParam(name = "category",required = false, defaultValue = "ALL") final Category category, + @RequestParam(name = "sort",required = false, defaultValue = "LATEST")final SortType sortType) { + + DiaryListRes diaryListRes = diariesService.getDiariesResponse(category, sortType,true,userId); + return ResponseEntity.ok(diaryListRes); + } +} diff --git a/src/main/java/org/sopt/Diary/controller/DiaryController.java b/src/main/java/org/sopt/Diary/controller/DiaryController.java new file mode 100644 index 0000000..04c16af --- /dev/null +++ b/src/main/java/org/sopt/Diary/controller/DiaryController.java @@ -0,0 +1,88 @@ +package org.sopt.Diary.controller; + +import jakarta.validation.Valid; +import org.sopt.Diary.dto.req.DiaryUpdateReq; +import org.sopt.Diary.dto.req.DiaryReq; +import org.sopt.Diary.dto.res.DiaryRes; +import org.sopt.Diary.service.DiaryService; +import org.sopt.Diary.service.UserService; +import org.sopt.Diary.validator.DiaryValidator; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RequestMapping("/diary") +@RestController +public class DiaryController { + + private final DiaryService diaryService; + private final UserService userService; + + public DiaryController(DiaryService diaryService,UserService userService) { + this.diaryService = diaryService; + this.userService=userService; + } + + /** + * 일기 작성하기 + * @param userId 유저 Id(Header) + * @param diaryRequest title,content,category,isPrivate + * @return 200 + */ + @PostMapping() + ResponseEntity postDiary(@RequestHeader("userId") long userId, @Valid @RequestBody final DiaryReq diaryRequest) { + + //UserId, 내용 글자수 검증 + userService.findByUserId(userId); + DiaryValidator.checkContent(diaryRequest.content()); + DiaryValidator.checkTitle(diaryRequest.title()); + + diaryService.createDiary(userId, diaryRequest); + return ResponseEntity.ok("일기가 생성되었습니다."); + } + + /** + * 일기 상세 조회 + * @param diaryId 다이어리 아이디 + * @return 200 + */ + @GetMapping("/{diaryId}") + ResponseEntity getDiary(@RequestHeader(name="userId",required = false) Long userId, @PathVariable("diaryId") final long diaryId) { + DiaryRes diaryRes = diaryService.getDiary(userId, diaryId); + + return ResponseEntity.ok(diaryRes); + } + + /** + * 일기 수정 + * @param userId 유저 아이디 + * @param diaryId 다이어리 아이디 + * @param diaryRequest content,category + * @return 200 + */ + @PatchMapping("/{diaryId}") + ResponseEntity updateDiary( @RequestHeader("userId") long userId, + @PathVariable("diaryId") final long diaryId, + @Valid @RequestBody DiaryUpdateReq diaryRequest){ + + //UserId, 내용 글자수 검증 + userService.findByUserId(userId); + DiaryValidator.checkContent(diaryRequest.content()); + + diaryService.patchDiary(userId, diaryId,diaryRequest.content(),diaryRequest.category()); + return ResponseEntity.ok("일기가 수정되었습니다."); + } + + /** + * 일기 삭제하기 + * @param userId 유저 아이디 + * @param diaryId 다이어리 아이디 + * @return 200 + */ + @DeleteMapping("/{diaryId}") + ResponseEntity deleteDiary(@RequestHeader("userId") long userId, + @PathVariable("diaryId") final long diaryId){ + userService.findByUserId(userId); + diaryService.deleteDiary(userId,diaryId); + return ResponseEntity.ok("일기가 삭제되었습니다."); + } +} diff --git a/src/main/java/org/sopt/Diary/controller/UserController.java b/src/main/java/org/sopt/Diary/controller/UserController.java new file mode 100644 index 0000000..536cba5 --- /dev/null +++ b/src/main/java/org/sopt/Diary/controller/UserController.java @@ -0,0 +1,45 @@ +package org.sopt.Diary.controller; + +import jakarta.validation.Valid; +import org.sopt.Diary.dto.req.SignInReq; +import org.sopt.Diary.dto.req.SignUpReq; +import org.sopt.Diary.dto.res.UserRes; +import org.sopt.Diary.service.UserService; +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 +@RequestMapping("/users") +public class UserController { + + private final UserService userService; + + public UserController(final UserService userService){this.userService=userService;} + + /** + * 회원가입 + * @param signUpRequest Id, Pwd, Nickname + */ + @PostMapping("/signup") + private void signUp(@Valid @RequestBody SignUpReq signUpRequest){ + // 제약사항 따로 없음 + //@Valid 를 통해 RequestBody @NotNull 체크해줌 + userService.join(signUpRequest); + } + + /** + * 로그인 + * @param signInReq Id, Pwd + * @return 200 + */ + @PostMapping("/signin") + private UserRes signIn(@Valid @RequestBody SignInReq signInReq){ + Long userId= userService.login(signInReq); + return new UserRes(userId); + + } + + +} diff --git a/src/main/java/org/sopt/Diary/dto/req/DiaryReq.java b/src/main/java/org/sopt/Diary/dto/req/DiaryReq.java new file mode 100644 index 0000000..c89b32d --- /dev/null +++ b/src/main/java/org/sopt/Diary/dto/req/DiaryReq.java @@ -0,0 +1,12 @@ +package org.sopt.Diary.dto.req; + + +import jakarta.validation.constraints.NotNull; +import org.sopt.Diary.entity.Category; + + +public record DiaryReq(@NotNull String title, + @NotNull String content, + @NotNull Category category, + boolean isPrivate){ +} diff --git a/src/main/java/org/sopt/Diary/dto/req/DiaryRequest.java b/src/main/java/org/sopt/Diary/dto/req/DiaryRequest.java deleted file mode 100644 index 14206b5..0000000 --- a/src/main/java/org/sopt/Diary/dto/req/DiaryRequest.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.sopt.Diary.dto.req; - - -import org.sopt.Diary.repository.Category; - -public record DiaryRequest ( String title, - String content, - Category category){ -} diff --git a/src/main/java/org/sopt/Diary/dto/req/DiaryUpdateReq.java b/src/main/java/org/sopt/Diary/dto/req/DiaryUpdateReq.java new file mode 100644 index 0000000..4832412 --- /dev/null +++ b/src/main/java/org/sopt/Diary/dto/req/DiaryUpdateReq.java @@ -0,0 +1,8 @@ +package org.sopt.Diary.dto.req; + + +import jakarta.validation.constraints.NotNull; +import org.sopt.Diary.entity.Category; + +public record DiaryUpdateReq(@NotNull String content , @NotNull Category category) { +} diff --git a/src/main/java/org/sopt/Diary/dto/req/DiaryUpdateRequest.java b/src/main/java/org/sopt/Diary/dto/req/DiaryUpdateRequest.java deleted file mode 100644 index 088e3b6..0000000 --- a/src/main/java/org/sopt/Diary/dto/req/DiaryUpdateRequest.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.sopt.Diary.dto.req; - - -import org.sopt.Diary.repository.Category; - -public record DiaryUpdateRequest( String content , Category category) { -} diff --git a/src/main/java/org/sopt/Diary/dto/req/SignInReq.java b/src/main/java/org/sopt/Diary/dto/req/SignInReq.java new file mode 100644 index 0000000..80ca080 --- /dev/null +++ b/src/main/java/org/sopt/Diary/dto/req/SignInReq.java @@ -0,0 +1,6 @@ +package org.sopt.Diary.dto.req; + +import jakarta.validation.constraints.NotNull; + +public record SignInReq (@NotNull String loginId, @NotNull String password){ +} diff --git a/src/main/java/org/sopt/Diary/dto/req/SignUpReq.java b/src/main/java/org/sopt/Diary/dto/req/SignUpReq.java new file mode 100644 index 0000000..d3bedc5 --- /dev/null +++ b/src/main/java/org/sopt/Diary/dto/req/SignUpReq.java @@ -0,0 +1,9 @@ +package org.sopt.Diary.dto.req; + +import jakarta.validation.constraints.NotNull; + +//입력값의 Null 검증을 위한 @Valid, @NotNull +public record SignUpReq ( @NotNull String loginId, @NotNull String password, @NotNull String nickname ) +{ + +} \ No newline at end of file diff --git a/src/main/java/org/sopt/Diary/dto/res/DiariesResponse.java b/src/main/java/org/sopt/Diary/dto/res/DiariesRes.java similarity index 78% rename from src/main/java/org/sopt/Diary/dto/res/DiariesResponse.java rename to src/main/java/org/sopt/Diary/dto/res/DiariesRes.java index 5ad7cf8..2b5e56f 100644 --- a/src/main/java/org/sopt/Diary/dto/res/DiariesResponse.java +++ b/src/main/java/org/sopt/Diary/dto/res/DiariesRes.java @@ -1,6 +1,6 @@ package org.sopt.Diary.dto.res; -public record DiariesResponse(Long id, String title){ +public record DiariesRes(Long id, String username, String title, String cratedAt){ } //레코드를 이용하도록 수정- // - 레코드를 사용하면 모든 필드가 자동으로 final이 되고 diff --git a/src/main/java/org/sopt/Diary/dto/res/DiaryListRes.java b/src/main/java/org/sopt/Diary/dto/res/DiaryListRes.java new file mode 100644 index 0000000..5b20f85 --- /dev/null +++ b/src/main/java/org/sopt/Diary/dto/res/DiaryListRes.java @@ -0,0 +1,20 @@ +package org.sopt.Diary.dto.res; + + +import java.util.List; + +public class DiaryListRes { + private List diaries; + + public DiaryListRes(List diaries) { + this.diaries = diaries; + } + + public List getDiaries() { + return diaries; + } + + public void setDiaries(List diaries) { + this.diaries = diaries; + } +} \ No newline at end of file diff --git a/src/main/java/org/sopt/Diary/dto/res/DiaryListResponse.java b/src/main/java/org/sopt/Diary/dto/res/DiaryListResponse.java deleted file mode 100644 index 2669a4f..0000000 --- a/src/main/java/org/sopt/Diary/dto/res/DiaryListResponse.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.sopt.Diary.dto.res; - -import java.util.List; - -public record DiaryListResponse(List diaries) { } -// 레코드 생성자 변경 \ No newline at end of file diff --git a/src/main/java/org/sopt/Diary/dto/res/DiaryRes.java b/src/main/java/org/sopt/Diary/dto/res/DiaryRes.java new file mode 100644 index 0000000..ec6bcc0 --- /dev/null +++ b/src/main/java/org/sopt/Diary/dto/res/DiaryRes.java @@ -0,0 +1,6 @@ +package org.sopt.Diary.dto.res; + +import org.sopt.Diary.entity.Category; + +public record DiaryRes(long id, String title, String content, String createdAt, Category category) { +} \ No newline at end of file diff --git a/src/main/java/org/sopt/Diary/dto/res/DiaryResponse.java b/src/main/java/org/sopt/Diary/dto/res/DiaryResponse.java deleted file mode 100644 index fde65d0..0000000 --- a/src/main/java/org/sopt/Diary/dto/res/DiaryResponse.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.sopt.Diary.dto.res; - -import org.sopt.Diary.repository.Category; - -public record DiaryResponse(long id, String title, String content, String createdAt, Category category) { -} \ No newline at end of file diff --git a/src/main/java/org/sopt/Diary/dto/res/UserRes.java b/src/main/java/org/sopt/Diary/dto/res/UserRes.java new file mode 100644 index 0000000..df6b034 --- /dev/null +++ b/src/main/java/org/sopt/Diary/dto/res/UserRes.java @@ -0,0 +1,16 @@ +package org.sopt.Diary.dto.res; + +public class UserRes { + + private long userId; + + public UserRes(long userId){ + this.userId = userId; + } + + public long getUserId(){ + return userId; + } + +} + diff --git a/src/main/java/org/sopt/Diary/entity/Category.java b/src/main/java/org/sopt/Diary/entity/Category.java new file mode 100644 index 0000000..7354e14 --- /dev/null +++ b/src/main/java/org/sopt/Diary/entity/Category.java @@ -0,0 +1,6 @@ +package org.sopt.Diary.entity; + +public enum Category { + FOOD, SCHOOL, MOVIE, EXERCISE, ALL +} + diff --git a/src/main/java/org/sopt/Diary/entity/DiaryEntity.java b/src/main/java/org/sopt/Diary/entity/DiaryEntity.java new file mode 100644 index 0000000..ac853e6 --- /dev/null +++ b/src/main/java/org/sopt/Diary/entity/DiaryEntity.java @@ -0,0 +1,88 @@ +package org.sopt.Diary.entity; + +import jakarta.persistence.*; + +import java.time.LocalDateTime; + +@Entity +@Table(name="diary") +public class DiaryEntity { + + @Id + @Column(name="id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name="title",unique = true) + private String title; + + @Column(name="content") + private String content; + + //Column의 기본값은 nullabel = true 이므로 기본 타입을 사용할 경우 nullable = false를 하는게 안전하다 + @Column(name="is_private", nullable = false) + private boolean isPrivate; + + @Column(name="created_at") + public LocalDateTime createdAt = LocalDateTime.now(); + + @Column(name="category") + @Enumerated(EnumType.STRING) //enum 이름 그대로 DB에 저장된다 + private Category category; + + @Column(name="user_id") + private Long userId; + + //JPA 는 엔티티 객체를 생성할때 기본 생성자를 사용하므로 반드시 있어야 한다! + public DiaryEntity() {} + + public DiaryEntity(final String title,final String content,final Category category,final boolean isPrivate,final Long userId) { + this.title = title; + this.content = content; + this.category = category; + this.isPrivate =isPrivate; + this.userId = userId; + } + + public static DiaryEntity of (final String title,final String content,final Category category,final boolean isPrivate,final long userId) { + return new DiaryEntity(title, content, category, isPrivate, userId); + } + + + + public DiaryEntity(final long id, final String title, final String content, final LocalDateTime createdAt, final Category category,final long userId) { + this. id = id; + this.title = title; + this.content = content; + this.createdAt = createdAt; + this.category = category; + this.userId= userId; + } + + public Long getDiaryId(){ + return id; + } + + public String getTitle(){ + return title; + } + + public String getContent(){ + return content;} + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public Category getCategory(){ + return category; + } + public Long getUserId(){ + return userId; + } + + public boolean getIsPrivate() { + return isPrivate; + } +} + diff --git a/src/main/java/org/sopt/Diary/entity/SortType.java b/src/main/java/org/sopt/Diary/entity/SortType.java new file mode 100644 index 0000000..1b2c68b --- /dev/null +++ b/src/main/java/org/sopt/Diary/entity/SortType.java @@ -0,0 +1,5 @@ +package org.sopt.Diary.entity; + +public enum SortType { + LATEST,QUANTITY +} diff --git a/src/main/java/org/sopt/Diary/entity/UserEntity.java b/src/main/java/org/sopt/Diary/entity/UserEntity.java new file mode 100644 index 0000000..f250e6f --- /dev/null +++ b/src/main/java/org/sopt/Diary/entity/UserEntity.java @@ -0,0 +1,44 @@ +package org.sopt.Diary.entity; + +import jakarta.persistence.*; + +@Entity +@Table(name="user") +public class UserEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @Column(name="login_id") + private String loginId; + + @Column(name="password") + private String password; + + @Column(name="nickname") + private String nickname; + + //기본 생성자 + public UserEntity(){}; + + public UserEntity(final String loginId,final String password, final String nickname){ + this.loginId =loginId; + this.password=password; + this.nickname=nickname; + } + + public static UserEntity createUser(final String loginId,final String password, final String nickname){ + return new UserEntity(loginId, password, nickname); + } + + public long getId(){ + return this.id; + } + public String getNickname(){ + return this.nickname; + } + + + +} diff --git a/src/main/java/org/sopt/Diary/error/CustomException.java b/src/main/java/org/sopt/Diary/error/CustomException.java new file mode 100644 index 0000000..d943732 --- /dev/null +++ b/src/main/java/org/sopt/Diary/error/CustomException.java @@ -0,0 +1,17 @@ +package org.sopt.Diary.error; + +public class CustomException extends RuntimeException{ + // 서비스 레이어에서 주로 사용하기 위한 예외 + // HTTP 응답에 직접적으로 영향을 미치지 않기 때문에, 구체적인 예외 처리 정의 후 처리 + + private ErrorCode errorCode; + + public CustomException(ErrorCode errorCode){ + super(errorCode.getMessage()); + this.errorCode = errorCode; + } + + public ErrorCode getErrorCode() { + return errorCode; + } +} diff --git a/src/main/java/org/sopt/Diary/error/ErrorCode.java b/src/main/java/org/sopt/Diary/error/ErrorCode.java new file mode 100644 index 0000000..9e0c5a2 --- /dev/null +++ b/src/main/java/org/sopt/Diary/error/ErrorCode.java @@ -0,0 +1,42 @@ +package org.sopt.Diary.error; + +import org.springframework.http.HttpStatus; + + +public enum ErrorCode { + + //user + ALREADY_EXIST_USER(HttpStatus.CONFLICT, "USER001", "이미 존재하는 사용자입니다."), + INVALID_USER(HttpStatus.NOT_FOUND, "USER002", "존재하지 않는 사용자입니다."), + BAD_REQUEST(HttpStatus.BAD_REQUEST, "USER004", "로그인 정보가 올바르지 않습니다."), + + //diary + INVALID_INPUT_LENGTH(HttpStatus.LENGTH_REQUIRED, "diary001", "글자수를 다시 확인해주세요"), + ACCESS_DENIED(HttpStatus.FORBIDDEN, "diary002", "접근 권한이 없습니다"), + DIARY_NOT_FOUND(HttpStatus.NOT_FOUND, "diary003", "일기를 찾을 수 없습니다."), + DUPLICATE_TITLE(HttpStatus.NOT_FOUND, "diary004", "이미 있는 제목입니다."); + + + + private HttpStatus status; + private final String code; + private final String message; + + ErrorCode(HttpStatus status, String code, String message) { + this.status = status; + this.code = code; + this.message = message; + } + + public HttpStatus getStatus() { + return status; + } + + public String getCode() { + return code; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/org/sopt/Diary/error/ErrorResponse.java b/src/main/java/org/sopt/Diary/error/ErrorResponse.java new file mode 100644 index 0000000..53a5dc2 --- /dev/null +++ b/src/main/java/org/sopt/Diary/error/ErrorResponse.java @@ -0,0 +1,27 @@ +package org.sopt.Diary.error; + +import org.springframework.http.HttpStatus; + +public class ErrorResponse { + private final HttpStatus httpStatus; + private final String code; + private final String error; + + public HttpStatus getHttpStatus() { + return httpStatus; + } + + public String getCode() { + return code; + } + + public String getError() { + return error; + } + + public ErrorResponse(HttpStatus httpStatus, String code, String error) { + this.httpStatus = httpStatus; + this.code = code; + this.error = error; + } +} diff --git a/src/main/java/org/sopt/Diary/error/GlobalExceptionHandler.java b/src/main/java/org/sopt/Diary/error/GlobalExceptionHandler.java new file mode 100644 index 0000000..462313f --- /dev/null +++ b/src/main/java/org/sopt/Diary/error/GlobalExceptionHandler.java @@ -0,0 +1,24 @@ +package org.sopt.Diary.error; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/* +전역적으로 에러를 처리해준다. +@ControllerAdvice 에 @ResponseBody 추가 된 형태 +@ControllerAdvice는 @ExceptionHandler 에 기능을 부여해주는 역할 +*/ +@RestControllerAdvice +public class GlobalExceptionHandler { + + //특정 에러 발생 시 Controller에 발생하였을 경우 해당 에러를 캐치하여 클라이언트로 오류를 반환하도록 처리하는 기능을 수행 + @ExceptionHandler(CustomException.class) + public ResponseEntity handleCustomException(CustomException ex){ + ErrorResponse response = new ErrorResponse( + ex.getErrorCode().getStatus(), + ex.getErrorCode().getCode(), + ex.getErrorCode().getMessage()); + return new ResponseEntity<>(response,ex.getErrorCode().getStatus()); + } +} diff --git a/src/main/java/org/sopt/Diary/formatter/DiaryFormatter.java b/src/main/java/org/sopt/Diary/formatter/DiaryFormatter.java new file mode 100644 index 0000000..056e8aa --- /dev/null +++ b/src/main/java/org/sopt/Diary/formatter/DiaryFormatter.java @@ -0,0 +1,14 @@ +package org.sopt.Diary.formatter; + +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +@Component +public class DiaryFormatter { + public static String dateFormatter(LocalDateTime createdDate){ + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); //ForMat 을 하는 부분이 + return createdDate.format(formatter); + } +} diff --git a/src/main/java/org/sopt/Diary/repository/Category.java b/src/main/java/org/sopt/Diary/repository/Category.java deleted file mode 100644 index b8dcae7..0000000 --- a/src/main/java/org/sopt/Diary/repository/Category.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.sopt.Diary.repository; - -public enum Category { - 일상,여행,운동,기념일,독서 -} - diff --git a/src/main/java/org/sopt/Diary/repository/DiaryEntity.java b/src/main/java/org/sopt/Diary/repository/DiaryEntity.java deleted file mode 100644 index c7176c1..0000000 --- a/src/main/java/org/sopt/Diary/repository/DiaryEntity.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.sopt.Diary.repository; - -import jakarta.persistence.*; - -import java.time.LocalDateTime; - -@Entity -public class DiaryEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - public Long id; - - @Column - public String title; - - @Column - public String content; - - @Column - public LocalDateTime createdAt = LocalDateTime.now(); - - @Enumerated(EnumType.STRING) - private Category category; - - public DiaryEntity(final Long id, final String title, final String content, final LocalDateTime createdAt, final Category category) { - this. id = id; - this.title = title; - this.content = content; - this.createdAt = createdAt; - this.category = category; - } - - public DiaryEntity() { - - } - - public DiaryEntity(String title, String content,Category category) { - this.title = title; - this.content = content; - this.category = category; - - } - - public long getId(){ - return id; - } - - public String getTitle(){ - return title; - } - - public String getContent(){ - return content;} - - public LocalDateTime getCreatedAt() { - return createdAt; - } - - public Category getCategory(){ - return category; - } - -} diff --git a/src/main/java/org/sopt/Diary/repository/DiaryRepository.java b/src/main/java/org/sopt/Diary/repository/DiaryRepository.java index a6cc5e3..dfdeeb1 100644 --- a/src/main/java/org/sopt/Diary/repository/DiaryRepository.java +++ b/src/main/java/org/sopt/Diary/repository/DiaryRepository.java @@ -1,25 +1,20 @@ package org.sopt.Diary.repository; -import org.sopt.Diary.service.Diary; -import org.springframework.data.domain.Sort; +import org.sopt.Diary.entity.DiaryEntity; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.stereotype.Component; +import org.springframework.stereotype.Repository; import java.util.List; -@Component -public interface DiaryRepository extends JpaRepository { - - - List findTop10ByOrderByCreatedAtDesc(); - - DiaryEntity findTop1ByOrderByCreatedAtDesc(); +@Repository +public interface DiaryRepository extends JpaRepository { Boolean existsByTitle(String title); - @Query("SELECT d FROM DiaryEntity d ORDER BY LENGTH(d.content) DESC") - List findByContentLength(); + List findAllByIsPrivateFalse(); + + // 필수적으로 UserId 필요 + List findByUserId(long userId); - List findByCategory(Category category); + List findByUserIdOrIsPrivateFalse(Long userId); } diff --git a/src/main/java/org/sopt/Diary/repository/UserRepository.java b/src/main/java/org/sopt/Diary/repository/UserRepository.java new file mode 100644 index 0000000..c885d91 --- /dev/null +++ b/src/main/java/org/sopt/Diary/repository/UserRepository.java @@ -0,0 +1,11 @@ +package org.sopt.Diary.repository; + +import org.sopt.Diary.entity.UserEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface UserRepository extends JpaRepository { + Optional findByLoginIdAndPassword(String loginId, String password); + // Null 이 올 수 있는 객체는 Optional 을 통해 감싸줌 +} diff --git a/src/main/java/org/sopt/Diary/service/DiariesService.java b/src/main/java/org/sopt/Diary/service/DiariesService.java new file mode 100644 index 0000000..5350360 --- /dev/null +++ b/src/main/java/org/sopt/Diary/service/DiariesService.java @@ -0,0 +1,82 @@ +package org.sopt.Diary.service; + +import org.sopt.Diary.dto.res.DiaryListRes; +import org.sopt.Diary.formatter.DiaryFormatter; +import org.sopt.Diary.dto.res.DiariesRes; +import org.sopt.Diary.entity.Category; +import org.sopt.Diary.entity.DiaryEntity; +import org.sopt.Diary.entity.SortType; +import org.sopt.Diary.entity.UserEntity; +import org.sopt.Diary.repository.DiaryRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +@Transactional(readOnly=true) +@Service +public class DiariesService { + + private final static int LIMIT_DIARY = 10; + private final DiaryRepository diaryRepository; + private final UserService userService; + + public DiariesService(DiaryRepository diaryRepository, UserService userService) { + this.diaryRepository = diaryRepository; + this.userService = userService; + } + + + public DiaryListRes getDiariesResponse(Category category, SortType sortType, boolean isMine, Long userId) { + List diaryEntities; + + if (isMine) { + // /my 로 들어온 경우 - 내가 쓴 일기만 조회 + diaryEntities = diaryRepository.findByUserId(userId); + } else if (userId == null) { + // userId 없는 경우 - 공개된 일기만 조회 + diaryEntities = diaryRepository.findAllByIsPrivateFalse(); + } else { + // userId가 null 이 아닌 경우, 해당 user 의 일기와 isPrivateFalse 인 일기가 보이도록 + diaryEntities = diaryRepository.findByUserIdOrIsPrivateFalse(userId); + } + + // 선택된 카테고리에 따라 필터링 + if (category != Category.ALL) { + diaryEntities = diaryEntities.stream() + .filter(diary -> diary.getCategory() == category) + .collect(Collectors.toList()); + } + + // sortType 에 따른 필터링 + Comparator comparator = getComparator(sortType); + diaryEntities = diaryEntities.stream() + .sorted(comparator) + .limit(LIMIT_DIARY) // 10개 제한 + .collect(Collectors.toList()); + + return new DiaryListRes(getDiaryResponse(diaryEntities)); + } + + private Comparator getComparator(SortType sortType) { + return switch (sortType) { + case LATEST -> Comparator.comparing(DiaryEntity::getCreatedAt).reversed(); // 최신 순 정렬 + case QUANTITY -> Comparator.comparingInt((DiaryEntity diary) -> diary.getContent().length()).reversed(); // 내용 길이 기준 내림차순 정렬 + }; + } + + // userNickName, date 형식 변환을 위한 메소드 + private List getDiaryResponse(List diaryEntities) { + return diaryEntities.stream() + .map(diary -> { + final UserEntity user = userService.findByUserId(diary.getUserId()); + final String createdAt = DiaryFormatter.dateFormatter(diary.getCreatedAt()); + return new DiariesRes(diary.getDiaryId(), user.getNickname(), diary.getTitle(), createdAt); + }) + .collect(Collectors.toList()); + } + +} + diff --git a/src/main/java/org/sopt/Diary/service/Diary.java b/src/main/java/org/sopt/Diary/service/Diary.java deleted file mode 100644 index 5354e84..0000000 --- a/src/main/java/org/sopt/Diary/service/Diary.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.sopt.Diary.service; - -import org.sopt.Diary.repository.Category; - -public class Diary { - private long id; - private final String title; - private final String content; - private String createdAt; - private Category category; - - public Diary(long id, String title, String content, String date,Category category){ - this.id = id; - this.title = title; - this.content = content; - this.createdAt= date; - this.category = category; - } - - - public Diary(String title, String content,Category category){ - this.title = title; - this.content = content; - this.category = category; - } - - public Diary(long id, String content){ - this.id= id; - this.title = null; - this.content = content; - } - - public long getId(){ - return id; - } - - public String getTitle(){ - return title; - } - - public String getContent() { - return content; - } - - public String getCreatedAt() { - return createdAt; - } - - public Category getCategory(){ - return category; - } -} diff --git a/src/main/java/org/sopt/Diary/service/DiaryService.java b/src/main/java/org/sopt/Diary/service/DiaryService.java index 0c61d36..626df9f 100644 --- a/src/main/java/org/sopt/Diary/service/DiaryService.java +++ b/src/main/java/org/sopt/Diary/service/DiaryService.java @@ -1,109 +1,96 @@ package org.sopt.Diary.service; -import org.sopt.Diary.dto.res.DiariesResponse; -import org.sopt.Diary.dto.req.DiaryRequest; -import org.sopt.Diary.repository.Category; -import org.sopt.Diary.repository.DiaryEntity; -import org.sopt.Diary.repository.DiaryRepository; -import org.springframework.http.HttpStatus; -import org.springframework.stereotype.Component; -import org.springframework.web.server.ResponseStatusException; -import java.time.Duration; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.List; +import org.sopt.Diary.dto.req.DiaryReq; +import org.sopt.Diary.dto.res.DiaryRes; +import org.sopt.Diary.entity.Category; +import org.sopt.Diary.entity.DiaryEntity; +import org.sopt.Diary.error.CustomException; +import org.sopt.Diary.error.ErrorCode; +import org.sopt.Diary.repository.DiaryRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -@Component +@Transactional(readOnly=true) +@Service public class DiaryService { - private final static int LIMIT_MINUTE = 5; - private final static int LIMIT_DIARY = 10; + private final DiaryRepository diaryRepository; public DiaryService(DiaryRepository diaryRepository) { this.diaryRepository = diaryRepository; } - public void validateTitle(String title){ - if(diaryRepository.existsByTitle(title)){ - throw new ResponseStatusException(HttpStatus.NOT_ACCEPTABLE,"중복왼 제목은 북자능 합니다."); - } - } + @Transactional + final public void createDiary(final long userId, final DiaryReq diaryRequest) { - public void createDiary(DiaryRequest diaryRequest) { validateTitle(diaryRequest.title()); - - int minutesSinceLastDiary = calculateMinutesSinceLastDiary(); - if (minutesSinceLastDiary < LIMIT_MINUTE) { - throw new ResponseStatusException(HttpStatus.TOO_MANY_REQUESTS, "5분 뒤에 요청해주세요"); - } - - Diary diary = new Diary(diaryRequest.title(), diaryRequest.content(), diaryRequest.category()); - diaryRepository.save(new DiaryEntity(diary.getTitle(), diary.getContent(), diary.getCategory())); + diaryRepository.save( + new DiaryEntity(diaryRequest.title(), + diaryRequest.content(), + diaryRequest.category(), + diaryRequest.isPrivate(), + userId) + ); } - private int calculateMinutesSinceLastDiary() { - DiaryEntity latestDiary = diaryRepository.findTop1ByOrderByCreatedAtDesc(); + public DiaryRes getDiary(final Long userId, final long diaryId) { + + DiaryEntity diaryEntity = findByDiaryId(diaryId); - if (latestDiary != null) { - LocalDateTime now = LocalDateTime.now(); - LocalDateTime latest = latestDiary.getCreatedAt(); - Duration duration = Duration.between(latest, now); - return duration.toMinutesPart(); + //비공개 일기일 경우 = userId 검증 + if(diaryEntity.getIsPrivate()) { + if( userId != diaryEntity.getUserId()){ + throw new CustomException(ErrorCode.ACCESS_DENIED); + } } - return Integer.MAX_VALUE; // 다이어리가 없을 경우, 제한 시간이 없도록 큰 값 반환 + + return new DiaryRes(diaryEntity.getDiaryId(), + diaryEntity.getTitle(), + diaryEntity.getContent(), + diaryEntity.getContent(), + diaryEntity.getCategory()); } - private List toDiariesResponse(List diaryEntities) { - List diariesResponses = new ArrayList<>(); - int count = 0; + @Transactional + public void patchDiary(final long userId, final long diaryId, final String content, final Category category) { + + DiaryEntity diaryEntity = findByDiaryId(diaryId); - for (DiaryEntity diary : diaryEntities) { - if (count >= LIMIT_DIARY) break; - DiariesResponse diariesResponse = new DiariesResponse(diary.getId(), diary.getTitle()); - diariesResponses.add(diariesResponse); - count++; + if(diaryEntity.getUserId()!=userId){ + throw new CustomException(ErrorCode.ACCESS_DENIED); } - return diariesResponses; + diaryRepository.save(new DiaryEntity(diaryEntity.getDiaryId(), + diaryEntity.getTitle(), + content, + diaryEntity.getCreatedAt(), + category, + diaryEntity.getUserId())); } - public List getDiaryList() { - List diaryEntities = diaryRepository.findTop10ByOrderByCreatedAtDesc(); - return toDiariesResponse(diaryEntities); - } + @Transactional + public void deleteDiary(final long userId, final long diaryId) { - public List getDiaryListSortByContent() { - List diaryEntities = diaryRepository.findByContentLength(); - return toDiariesResponse(diaryEntities); - } + DiaryEntity diaryEntity= findByDiaryId(diaryId); - public List getDiaryListSortByCategory(Category category) { - List diaryEntities = diaryRepository.findByCategory(category); - return toDiariesResponse(diaryEntities); + if(diaryEntity.getUserId()!=userId){ + throw new CustomException(ErrorCode.ACCESS_DENIED); + } + diaryRepository.delete(diaryEntity); } - public Diary getDiary(Long id) { - DiaryEntity diaryEntity = findById(id); - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - String formattedDate = diaryEntity.getCreatedAt().format(formatter); - - return new Diary(diaryEntity.getId(), diaryEntity.getTitle(), diaryEntity.getContent(), formattedDate, diaryEntity.getCategory()); + public DiaryEntity findByDiaryId(final long id){ + return diaryRepository.findById(id) + .orElseThrow(() -> new CustomException(ErrorCode.DIARY_NOT_FOUND)); } - public void patchDiary(Long id, String content, Category category) { - DiaryEntity diaryEntity= findById(id); - diaryRepository.save(new DiaryEntity(diaryEntity.getId(), diaryEntity.getTitle(), content, diaryEntity.getCreatedAt(), category)); - } - public void deleteDiary(Long id) { - DiaryEntity diaryEntity= findById(id); - diaryRepository.delete(diaryEntity); + public void validateTitle(final String title){ + if(diaryRepository.existsByTitle(title)){ + throw new CustomException(ErrorCode.DUPLICATE_TITLE); + } } - public DiaryEntity findById(Long id){ - return diaryRepository.findById(id) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - } + } \ No newline at end of file diff --git a/src/main/java/org/sopt/Diary/service/UserService.java b/src/main/java/org/sopt/Diary/service/UserService.java new file mode 100644 index 0000000..3439c0b --- /dev/null +++ b/src/main/java/org/sopt/Diary/service/UserService.java @@ -0,0 +1,55 @@ +package org.sopt.Diary.service; + + +import jakarta.validation.Valid; +import org.sopt.Diary.dto.req.SignInReq; +import org.sopt.Diary.dto.req.SignUpReq; +import org.springframework.transaction.annotation.Transactional; +import org.sopt.Diary.entity.UserEntity; +import org.sopt.Diary.error.CustomException; +import org.sopt.Diary.error.ErrorCode; +import org.sopt.Diary.repository.UserRepository; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +@Transactional // 클래스의 메소드 호출시 트랜잭션 시작, 메소드 종료시 커밋, 예외 발생시 롤백 +public class UserService { + + private final UserRepository userRepository; + + public UserService(UserRepository userRepository){ + this.userRepository = userRepository;} + + public void join(@Valid final SignUpReq signUpRequest) { + //중복회원 검증 + validateDuplicateMember(signUpRequest.loginId(), signUpRequest.password()); + // SignUpReq -> UserEntity + final UserEntity newUserEntity = UserEntity.createUser(signUpRequest.loginId(), signUpRequest.password(), signUpRequest.nickname()); + userRepository.save(newUserEntity); + } + + public Long login(@Valid final SignInReq signInReq) { + + // 1. userRepository 에서 Id 찾기 -> NPE 방지 위해 Optional 사용 + UserEntity findUser = userRepository.findByLoginIdAndPassword(signInReq.loginId(), signInReq.password()) + .orElseThrow(()-> new CustomException(ErrorCode.BAD_REQUEST)); + // 2. UserID 반환 + return findUser.getId(); + } + + // UserService.java + public UserEntity findByUserId(final long userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.INVALID_USER)); + } + + private void validateDuplicateMember(final String loginId, final String password) { + Optional findUser = userRepository.findByLoginIdAndPassword(loginId, password); + if(findUser.isPresent()){ + throw new CustomException(ErrorCode.ALREADY_EXIST_USER); + } + } + +} diff --git a/src/main/java/org/sopt/Diary/validator/DiaryValidator.java b/src/main/java/org/sopt/Diary/validator/DiaryValidator.java new file mode 100644 index 0000000..245a917 --- /dev/null +++ b/src/main/java/org/sopt/Diary/validator/DiaryValidator.java @@ -0,0 +1,24 @@ +package org.sopt.Diary.validator; + +import org.sopt.Diary.error.CustomException; +import org.sopt.Diary.error.ErrorCode; +import org.springframework.stereotype.Component; + +@Component +public class DiaryValidator { + + private final static int CONTENT_LIMIT = 30; + private final static int TITLE_LIMIT = 10; + + public static void checkContent(String content) { + if(content.length() > CONTENT_LIMIT || content.isEmpty()){ + throw new CustomException(ErrorCode.INVALID_INPUT_LENGTH); + } + } + + public static void checkTitle(String title) { + if(title.length() > TITLE_LIMIT || title.isEmpty()){ + throw new CustomException(ErrorCode.INVALID_INPUT_LENGTH); + } + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml deleted file mode 100644 index 012ab38..0000000 --- a/src/main/resources/application.yml +++ /dev/null @@ -1,17 +0,0 @@ -spring: - h2: - console: - enabled: true - datasource: - driver-class-name: org.h2.Driver - url: jdbc:h2:mem:testdb - username: root - password: - jpa: - show-sql: true - hibernate: - ddl-auto: create - dialect: org.hibernate.dialect.H2Dialect - properties: - format_sql: true - show_sql: true \ No newline at end of file