diff --git a/src/main/java/melting_pot/ewha_sinmungo/global/BaseTimeEntity.java b/src/main/java/melting_pot/ewha_sinmungo/global/BaseTimeEntity.java index feb7493..bfec3ae 100644 --- a/src/main/java/melting_pot/ewha_sinmungo/global/BaseTimeEntity.java +++ b/src/main/java/melting_pot/ewha_sinmungo/global/BaseTimeEntity.java @@ -15,8 +15,4 @@ public abstract class BaseTimeEntity { @CreatedDate @Column(updatable = false) private LocalDateTime createdDate; - - @LastModifiedDate - @Column(insertable=false) - private LocalDateTime updatedDate; } \ No newline at end of file diff --git a/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/ApiResponse.java b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/ApiResponse.java new file mode 100644 index 0000000..1300cc4 --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/ApiResponse.java @@ -0,0 +1,41 @@ +package melting_pot.ewha_sinmungo.global.apiResponse; + + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import lombok.AllArgsConstructor; +import lombok.Getter; +import melting_pot.ewha_sinmungo.global.apiResponse.code.status.SuccessStatus; + +@Getter +@AllArgsConstructor +@JsonPropertyOrder({"isSuccess", "code", "message", "result"}) +public class ApiResponse { + + @JsonProperty("isSuccess") + private final Boolean isSuccess; + private final String code; + private final String message; + @JsonInclude(JsonInclude.Include.NON_NULL) + private T result; + + + // 성공한 경우 응답 생성 + + public static ApiResponse onSuccess(T result){ + return new ApiResponse<>(true, SuccessStatus._OK.getCode() , SuccessStatus._OK.getMessage(), result); + } +// +// public static ApiResponse of(BaseCode code, T result){ +// return new ApiResponse<>(true, code.getReasonHttpStatus().getCode() , code.getReasonHttpStatus().getMessage(), result); +// } + + + // 실패한 경우 응답 생성 + public static ApiResponse onFailure(String code, String message, T data){ + return new ApiResponse<>(false, code, message, data); + } + + +} diff --git a/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/Message.java b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/Message.java new file mode 100644 index 0000000..1e62098 --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/Message.java @@ -0,0 +1,17 @@ +package melting_pot.ewha_sinmungo.global.apiResponse; + +import lombok.Builder; +import lombok.Data; +import lombok.ToString; + +@ToString +@Data +public class Message { + + private String message; + @Builder + public Message(String message) { + this.message = message; + } + +} \ No newline at end of file diff --git a/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/code/BaseCode.java b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/code/BaseCode.java new file mode 100644 index 0000000..25ec483 --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/code/BaseCode.java @@ -0,0 +1,8 @@ +package melting_pot.ewha_sinmungo.global.apiResponse.code; + +public interface BaseCode { + + public ReasonDTO getReason(); + + public ReasonDTO getReasonHttpStatus(); +} \ No newline at end of file diff --git a/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/code/BaseErrorCode.java b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/code/BaseErrorCode.java new file mode 100644 index 0000000..7f315ff --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/code/BaseErrorCode.java @@ -0,0 +1,8 @@ +package melting_pot.ewha_sinmungo.global.apiResponse.code; + +public interface BaseErrorCode { + + public ErrorReasonDTO getReason(); + + public ErrorReasonDTO getReasonHttpStatus(); +} \ No newline at end of file diff --git a/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/code/ErrorReasonDTO.java b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/code/ErrorReasonDTO.java new file mode 100644 index 0000000..608ab96 --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/code/ErrorReasonDTO.java @@ -0,0 +1,18 @@ +package melting_pot.ewha_sinmungo.global.apiResponse.code; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@Builder +public class ErrorReasonDTO { + + private HttpStatus httpStatus; + + private final boolean isSuccess; + private final String code; + private final String message; + + public boolean getIsSuccess(){return isSuccess;} +} diff --git a/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/code/ReasonDTO.java b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/code/ReasonDTO.java new file mode 100644 index 0000000..7d97a98 --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/code/ReasonDTO.java @@ -0,0 +1,18 @@ +package melting_pot.ewha_sinmungo.global.apiResponse.code; + +import lombok.Builder; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@Builder +public class ReasonDTO { + + private HttpStatus httpStatus; + + private final boolean isSuccess; + private final String code; + private final String message; + + public boolean getIsSuccess(){return isSuccess;} +} \ No newline at end of file diff --git a/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/code/status/ErrorStatus.java b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/code/status/ErrorStatus.java new file mode 100644 index 0000000..4e6cfeb --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/code/status/ErrorStatus.java @@ -0,0 +1,52 @@ +package melting_pot.ewha_sinmungo.global.apiResponse.code.status; + + +import lombok.AllArgsConstructor; +import lombok.Getter; +import melting_pot.ewha_sinmungo.global.apiResponse.code.BaseErrorCode; +import melting_pot.ewha_sinmungo.global.apiResponse.code.ErrorReasonDTO; +import org.springframework.http.HttpStatus; + + +@Getter +@AllArgsConstructor +public enum ErrorStatus implements BaseErrorCode { + + // 가장 일반적인 응답 + _INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "COMMON500", "서버 에러, 관리자에게 문의 바랍니다."), + _BAD_REQUEST(HttpStatus.BAD_REQUEST,"COMMON400","잘못된 요청입니다."), + _UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"COMMON401","인증이 필요합니다."), + _FORBIDDEN(HttpStatus.FORBIDDEN, "COMMON403", "금지된 요청입니다."), + + + // 멤버 관련 에러 + MEMBER_NOT_FOUND(HttpStatus.BAD_REQUEST, "MEMBER4001", "사용자가 없습니다."), + INVALID_REFRESH_TOKEN(HttpStatus.BAD_REQUEST, "MEMBER4005", "유효하지 않은 리프레시 토큰 입니다."), + // 게시물 관련 에러 + ARTICLE_NOT_FOUND(HttpStatus.NOT_FOUND, "ARTICLE4001", "게시글이 없습니다."), + PAGE_FORMAT_BAD_REQUEST(HttpStatus.BAD_REQUEST,"PAGE4001","잘못된 페이지 번호 형식입니다"); + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public ErrorReasonDTO getReason() { + return ErrorReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(false) + .build(); + } + + @Override + public ErrorReasonDTO getReasonHttpStatus() { + return ErrorReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(false) + .httpStatus(httpStatus) + .build() + ; + } +} \ No newline at end of file diff --git a/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/code/status/SuccessStatus.java b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/code/status/SuccessStatus.java new file mode 100644 index 0000000..94c55d2 --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/code/status/SuccessStatus.java @@ -0,0 +1,43 @@ +package melting_pot.ewha_sinmungo.global.apiResponse.code.status; +import lombok.AllArgsConstructor; +import lombok.Getter; +import melting_pot.ewha_sinmungo.global.apiResponse.code.BaseCode; +import melting_pot.ewha_sinmungo.global.apiResponse.code.ReasonDTO; +import org.springframework.http.HttpStatus; + + +@Getter +@AllArgsConstructor +public enum SuccessStatus implements BaseCode { + + // 일반적인 응답 + _OK(HttpStatus.OK, "COMMON200", "성공입니다."); + + // 멤버 관련 응답 + + // ~~~ 관련 응답 + + private final HttpStatus httpStatus; + private final String code; + private final String message; + + @Override + public ReasonDTO getReason() { + return ReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(true) + .build(); + } + + @Override + public ReasonDTO getReasonHttpStatus() { + return ReasonDTO.builder() + .message(message) + .code(code) + .isSuccess(true) + .httpStatus(httpStatus) + .build() + ; + } +} diff --git a/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/exception/GeneralException.java b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/exception/GeneralException.java new file mode 100644 index 0000000..81fe592 --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/exception/GeneralException.java @@ -0,0 +1,45 @@ +package melting_pot.ewha_sinmungo.global.apiResponse.exception; + + +import lombok.Getter; +import melting_pot.ewha_sinmungo.global.apiResponse.code.BaseErrorCode; +import melting_pot.ewha_sinmungo.global.apiResponse.code.ErrorReasonDTO; + +@Getter +public class GeneralException extends RuntimeException { + + private BaseErrorCode code; + private String sourceClass; + private String sourceMethod; + private String sourcePackage; + private String sourceAddress; + + public GeneralException(BaseErrorCode errorCode) { + extractSourceInfo(); + this.code = errorCode; + } + public ErrorReasonDTO getErrorReason() { + return this.code.getReason(); + } + + public ErrorReasonDTO getErrorReasonHttpStatus(){ + return this.code.getReasonHttpStatus(); + } + private void extractSourceInfo() { + StackTraceElement[] stackTrace = this.getStackTrace(); + if (stackTrace.length > 1) { + StackTraceElement element = stackTrace[1]; // [0]는 현재 생성자, [1]은 CustomException을 발생시킨 메서드 + this.sourceClass = element.getClassName(); + this.sourceMethod = element.getMethodName(); + + // 패키지 정보는 클래스 이름에서 추출 + int lastDotIndex = this.sourceClass.lastIndexOf('.'); + if (lastDotIndex > 0) { + this.sourcePackage = this.sourceClass.substring(0, lastDotIndex); + } else { + this.sourcePackage = "Unknown"; + } + } + this.sourceAddress = sourcePackage+"."+sourceClass+"."+sourceMethod; + } +} diff --git a/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/exception/handler/ErrorHandler.java b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/exception/handler/ErrorHandler.java new file mode 100644 index 0000000..55a9826 --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/global/apiResponse/exception/handler/ErrorHandler.java @@ -0,0 +1,12 @@ +package melting_pot.ewha_sinmungo.global.apiResponse.exception.handler; + + +import melting_pot.ewha_sinmungo.global.apiResponse.code.BaseErrorCode; +import melting_pot.ewha_sinmungo.global.apiResponse.exception.GeneralException; + +public class ErrorHandler extends GeneralException { + + public ErrorHandler(BaseErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/melting_pot/ewha_sinmungo/post/controller/PostController.java b/src/main/java/melting_pot/ewha_sinmungo/post/controller/PostController.java new file mode 100644 index 0000000..571bdd8 --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/post/controller/PostController.java @@ -0,0 +1,75 @@ +package melting_pot.ewha_sinmungo.post.controller; + +import lombok.RequiredArgsConstructor; +import melting_pot.ewha_sinmungo.global.apiResponse.ApiResponse; +import melting_pot.ewha_sinmungo.post.dto.requestDto.PostRequestDTO; +import melting_pot.ewha_sinmungo.post.dto.responseDto.PostResponseDTO; +import melting_pot.ewha_sinmungo.post.entity.Category; +import melting_pot.ewha_sinmungo.post.entity.Status; +import melting_pot.ewha_sinmungo.post.service.PostService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; + +@RestController +@RequiredArgsConstructor +public class PostController { + private final PostService postService; + + @PostMapping("/article") + public String postPost (@RequestBody @Valid PostRequestDTO.PostSaveDto request) { + postService.createPost(request); + return "Success"; + } + + @GetMapping("/article/{postId}") + public ApiResponse getPost (@PathVariable Long postId){ + return ApiResponse.onSuccess(postService.getPostInfo(postId)); + } + + @GetMapping("/article/previews/{category}/{status}/newest") + public ApiResponse> getPostPreviewByCategoryNewest(@PathVariable Category category, @PathVariable Status status, Pageable pageable) { + Page previews = postService.getPostPreviewByCategoryNewest(category, status, pageable); + return ApiResponse.onSuccess(previews); + } + @GetMapping("/article/previews/{category}/{status}/oldest") + public ApiResponse> getPostPreviewByCategoryOldest(@PathVariable Category category, @PathVariable Status status, Pageable pageable) { + Page previews = postService.getPostPreviewByCategoryOldest(category, status, pageable); + return ApiResponse.onSuccess(previews); + } + @GetMapping("/article/previews/{category}/{status}/hot") + public ApiResponse> getPostPreviewByCategoryHot(@PathVariable Category category, @PathVariable Status status, Pageable pageable) { + Page previews = postService.getPostPreviewByCategoryNewest(category, status, pageable); + return ApiResponse.onSuccess(previews); + } + @GetMapping("/article/previews/{category}/newest") + public ApiResponse> getPostPreviewNewest(@PathVariable Status status, Pageable pageable) { + Page previews = postService.getPostPreviewNewest(status, pageable); + return ApiResponse.onSuccess(previews); + } + @GetMapping("/article/previews/{category}/oldest") + public ApiResponse> getPostPreviewOldest( @PathVariable Status status, Pageable pageable) { + Page previews = postService.getPostPreviewOldest(status, pageable); + return ApiResponse.onSuccess(previews); + } + @GetMapping("/article/previews/{category}/hot") + public ApiResponse> getPostPreviewHot(@PathVariable Status status, Pageable pageable) { + Page previews = postService.getPostPreviewHot(status, pageable); + return ApiResponse.onSuccess(previews); + } + + @PutMapping("/article/{postId}/vote/enable") + public ApiResponse enablePostLike(@PathVariable Long postId){ + postService.enablePostLike(postId); + return ApiResponse.onSuccess(Boolean.TRUE); + } + + @PutMapping("/article/{postId}/vote/disable") + public ApiResponse disablePostLike(@PathVariable Long postId){ + postService.disablePostLike(postId); + return ApiResponse.onSuccess(Boolean.TRUE); + } + +} diff --git a/src/main/java/melting_pot/ewha_sinmungo/post/converter/PostConverter.java b/src/main/java/melting_pot/ewha_sinmungo/post/converter/PostConverter.java new file mode 100644 index 0000000..51fe9d4 --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/post/converter/PostConverter.java @@ -0,0 +1,78 @@ +package melting_pot.ewha_sinmungo.post.converter; + +import lombok.RequiredArgsConstructor; +import melting_pot.ewha_sinmungo.member.entity.Member; +import melting_pot.ewha_sinmungo.member.service.MemberService; +import melting_pot.ewha_sinmungo.post.dto.requestDto.PostRequestDTO; +import melting_pot.ewha_sinmungo.post.dto.responseDto.PostResponseDTO; +import melting_pot.ewha_sinmungo.post.entity.Post; +import melting_pot.ewha_sinmungo.post.entity.PostUrl; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class PostConverter { + private final MemberService memberService; + public Post toPostEntity (PostRequestDTO.PostSaveDto request) { + Member member = memberService.getCurrentMember(); + LocalDateTime currentDate = LocalDateTime.now(); + LocalDateTime deadline = currentDate.plus(30, ChronoUnit.DAYS); + Post post = Post.builder() + .email(request.getEmail()) + .title(request.getTitle()) + .content(request.getContent()) + .category(request.getCategory()) + .createdDate(currentDate) + .deadline(deadline) + .member(member) + .build(); + post.updatePostUrls(request.getUrlList()); + return post; + } + + public PostResponseDTO.PostEntityDto toPostEntityDto(Post post) { + Member member = memberService.getCurrentMember(); + LocalDateTime currentDate = post.getCreatedDate(); + LocalDateTime deadline = currentDate.plus(30, ChronoUnit.DAYS); + List urlList = post.getPostUrlList().stream() + .map(PostUrl::getUrl) + .collect(Collectors.toList()); + return PostResponseDTO.PostEntityDto.builder() + .postId(post.getPostId()) + .studentName(member.getName()) + .studentNum(member.getStudentNum()) + .email(post.getEmail()) + .title(post.getTitle()) + .content(post.getContent()) + .category(post.getCategory()) + .urlList(urlList) + .deadline(deadline) + .voteCount(post.getVoteCount()) + .status(post.getStatus()) + .createdDate(post.getCreatedDate()) + .build(); + } + + public Page toPreviewListDto(Page posts) { + return posts.map(post -> { + LocalDateTime fromDate = LocalDateTime.now(); + LocalDateTime deadline = post.getDeadline(); + long dDay = ChronoUnit.DAYS.between(fromDate, deadline); + return PostResponseDTO.PostPreviewDto.builder() + .postId(post.getPostId()) + .title(post.getTitle()) + .category(post.getCategory()) + .deadline(deadline) + .dDay(dDay) + .createdDate(post.getCreatedDate()) + .voteCount(post.getVoteCount()) + .build(); + }); + } +} diff --git a/src/main/java/melting_pot/ewha_sinmungo/post/domain/PostMemberReaction.java b/src/main/java/melting_pot/ewha_sinmungo/post/domain/PostMemberReaction.java new file mode 100644 index 0000000..69a3fef --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/post/domain/PostMemberReaction.java @@ -0,0 +1,42 @@ +package melting_pot.ewha_sinmungo.post.domain; + +import lombok.*; +import melting_pot.ewha_sinmungo.member.entity.Member; +import melting_pot.ewha_sinmungo.post.entity.Post; + + +import javax.persistence.*; + +@Entity +@Builder +@Getter +@IdClass(PostMemberReactionId.class) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class PostMemberReaction { + + @Id + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "postId") + private Post post; + + @Id + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "memberId") + private Member member; + + private boolean isLiked = false; + + public void enableLike() { + if(!isLiked){ + post.increaseVoteCount(); + } + this.isLiked = true; + } + public void disableLike(){ + if(isLiked){ + post.decreaseVoteCount(); + } + this.isLiked = false; + } +} diff --git a/src/main/java/melting_pot/ewha_sinmungo/post/domain/PostMemberReactionId.java b/src/main/java/melting_pot/ewha_sinmungo/post/domain/PostMemberReactionId.java new file mode 100644 index 0000000..ea7a91d --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/post/domain/PostMemberReactionId.java @@ -0,0 +1,16 @@ +package melting_pot.ewha_sinmungo.post.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import melting_pot.ewha_sinmungo.member.entity.Member; +import melting_pot.ewha_sinmungo.post.entity.Post; + +import java.io.Serializable; +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class PostMemberReactionId implements Serializable { + private Post post; + private Member member; +} diff --git a/src/main/java/melting_pot/ewha_sinmungo/post/dto/requestDto/PostRequestDTO.java b/src/main/java/melting_pot/ewha_sinmungo/post/dto/requestDto/PostRequestDTO.java new file mode 100644 index 0000000..7258cb0 --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/post/dto/requestDto/PostRequestDTO.java @@ -0,0 +1,20 @@ +package melting_pot.ewha_sinmungo.post.dto.requestDto; + +import lombok.Getter; +import melting_pot.ewha_sinmungo.post.entity.Category; +import melting_pot.ewha_sinmungo.post.entity.Status; + +import java.time.LocalDateTime; +import java.util.List; + +public class PostRequestDTO { + + @Getter + public static class PostSaveDto { + String email; + String title; + String content; + Category category; + List urlList; + } +} \ No newline at end of file diff --git a/src/main/java/melting_pot/ewha_sinmungo/post/dto/responseDto/PostResponseDTO.java b/src/main/java/melting_pot/ewha_sinmungo/post/dto/responseDto/PostResponseDTO.java new file mode 100644 index 0000000..d9c41cc --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/post/dto/responseDto/PostResponseDTO.java @@ -0,0 +1,48 @@ +package melting_pot.ewha_sinmungo.post.dto.responseDto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import melting_pot.ewha_sinmungo.post.entity.Category; +import melting_pot.ewha_sinmungo.post.entity.Status; +import java.time.LocalDateTime; +import java.util.List; + + +public class PostResponseDTO { + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PostEntityDto { + Long postId; + String studentName; + String studentNum; + String title; + String content; + Category category; + LocalDateTime deadline; + String email; + int voteCount; + Status status; + List urlList; + LocalDateTime createdDate; + } + + @Builder + @Getter + @NoArgsConstructor + @AllArgsConstructor + public static class PostPreviewDto { + Long postId; + String title; + Category category; + LocalDateTime deadline; + LocalDateTime createdDate; + int voteCount; + long dDay; + } + +} diff --git a/src/main/java/melting_pot/ewha_sinmungo/post/entity/Post.java b/src/main/java/melting_pot/ewha_sinmungo/post/entity/Post.java index ef717d5..f9fa14d 100644 --- a/src/main/java/melting_pot/ewha_sinmungo/post/entity/Post.java +++ b/src/main/java/melting_pot/ewha_sinmungo/post/entity/Post.java @@ -1,20 +1,31 @@ package melting_pot.ewha_sinmungo.post.entity; import javax.persistence.*; -import lombok.NoArgsConstructor; + +import lombok.*; +import melting_pot.ewha_sinmungo.global.BaseTimeEntity; +import melting_pot.ewha_sinmungo.global.apiResponse.exception.GeneralException; import melting_pot.ewha_sinmungo.member.entity.Member; import melting_pot.ewha_sinmungo.notice.entity.Notice; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; @Entity -@NoArgsConstructor +@Builder +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor @Table(name = "post") -public class Post { +public class Post extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "post_id") - private Long id; + @Column(name = "postId") + private Long postId; @Column(nullable = false) private String title; @@ -26,21 +37,59 @@ public class Post { @Column(nullable = false) private Category category; + @Builder.Default @Column(nullable = false, columnDefinition = "integer default 0") - private int voteCount; + private int voteCount = 0; @Column(nullable = false) private LocalDateTime deadline; + @Builder.Default @Enumerated(value = EnumType.STRING) @Column(nullable = false) - private Status status; + private Status status = Status.ONGOING; + + @Column(nullable = false) + private String email; + + @Column(nullable = false) + private LocalDateTime createdDate; + + @Builder.Default + @Column(nullable = false) + private Boolean isHot = false; @ManyToOne - @JoinColumn(name = "member_id",nullable = false) + @JoinColumn(name = "memberId",nullable = false) private Member member; - @ManyToOne - @JoinColumn(name = "notice_id",nullable = false) - private Notice notice; +// @ManyToOne +// @JoinColumn(name = "noticeId",nullable = false) +// private Notice notice; + + @OneToMany (mappedBy = "post", cascade = CascadeType.ALL) + private List postUrlList = new ArrayList<>(); + + + public void increaseVoteCount() { + this.voteCount += 1; + if(voteCount >= 90){ + isHot = Boolean.TRUE; + } + } + public void decreaseVoteCount() { + this.voteCount -= 1; + } + + public void updatePostUrls(List postUrls){ + Set uniqueUrls = new HashSet<>(postUrls); + postUrlList = uniqueUrls.stream() + .map(postUrl -> PostUrl.builder() + .url(postUrl) + .post(this) + .build()) + .collect(Collectors.toList()); + } + + } diff --git a/src/main/java/melting_pot/ewha_sinmungo/post/entity/PostUrl.java b/src/main/java/melting_pot/ewha_sinmungo/post/entity/PostUrl.java new file mode 100644 index 0000000..8f7e585 --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/post/entity/PostUrl.java @@ -0,0 +1,21 @@ +package melting_pot.ewha_sinmungo.post.entity; +import lombok.*; + +import javax.persistence.*; + +@Entity +@Builder +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class PostUrl { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long postUrlId; + + private String url; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "postId") + private Post post; +} diff --git a/src/main/java/melting_pot/ewha_sinmungo/post/repository/PostMemberReactionRepository.java b/src/main/java/melting_pot/ewha_sinmungo/post/repository/PostMemberReactionRepository.java new file mode 100644 index 0000000..0cee329 --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/post/repository/PostMemberReactionRepository.java @@ -0,0 +1,8 @@ +package melting_pot.ewha_sinmungo.post.repository; + +import melting_pot.ewha_sinmungo.post.domain.PostMemberReaction; +import melting_pot.ewha_sinmungo.post.domain.PostMemberReactionId; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PostMemberReactionRepository extends JpaRepository { +} diff --git a/src/main/java/melting_pot/ewha_sinmungo/post/repository/PostRepository.java b/src/main/java/melting_pot/ewha_sinmungo/post/repository/PostRepository.java new file mode 100644 index 0000000..7af7fed --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/post/repository/PostRepository.java @@ -0,0 +1,26 @@ +package melting_pot.ewha_sinmungo.post.repository; + +import melting_pot.ewha_sinmungo.post.dto.responseDto.PostResponseDTO; +import melting_pot.ewha_sinmungo.post.entity.Category; +import melting_pot.ewha_sinmungo.post.entity.Post; +import melting_pot.ewha_sinmungo.post.entity.Status; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; + +public interface PostRepository extends JpaRepository { + Page findAllByCategoryAndStatusOrderByCreatedDateDesc(Category category, Status status, Pageable pageable); + Page findAllByCategoryAndStatusOrderByCreatedDateAsc(Category category, Status status, Pageable pageable); + Page findAllByCategoryAndStatusOrderByVoteCountDesc(Category category, Status status, Pageable pageable); + + Page findAllByStatusOrderByCreatedDateDesc(Status status, Pageable pageable); + Page findAllByStatusOrderByCreatedDateAsc(Status status, Pageable pageable); + Page findAllByStatusOrderByVoteCountDesc(Status status, Pageable pageable); + + Page findByIsHotTrueOrderByCreatedDateDesc(Pageable pageable); + +} + diff --git a/src/main/java/melting_pot/ewha_sinmungo/post/service/PostService.java b/src/main/java/melting_pot/ewha_sinmungo/post/service/PostService.java new file mode 100644 index 0000000..fb283ad --- /dev/null +++ b/src/main/java/melting_pot/ewha_sinmungo/post/service/PostService.java @@ -0,0 +1,113 @@ +package melting_pot.ewha_sinmungo.post.service; + +import lombok.RequiredArgsConstructor; +import melting_pot.ewha_sinmungo.global.apiResponse.code.status.ErrorStatus; +import melting_pot.ewha_sinmungo.global.apiResponse.exception.GeneralException; +import melting_pot.ewha_sinmungo.member.entity.Member; +import melting_pot.ewha_sinmungo.member.service.MemberService; +import melting_pot.ewha_sinmungo.post.converter.PostConverter; +import melting_pot.ewha_sinmungo.post.domain.PostMemberReaction; +import melting_pot.ewha_sinmungo.post.domain.PostMemberReactionId; +import melting_pot.ewha_sinmungo.post.dto.requestDto.PostRequestDTO; +import melting_pot.ewha_sinmungo.post.dto.responseDto.PostResponseDTO; +import melting_pot.ewha_sinmungo.post.entity.Category; +import melting_pot.ewha_sinmungo.post.entity.Post; +import melting_pot.ewha_sinmungo.post.entity.Status; +import melting_pot.ewha_sinmungo.post.repository.PostMemberReactionRepository; +import melting_pot.ewha_sinmungo.post.repository.PostRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Optional; + +import static melting_pot.ewha_sinmungo.global.apiResponse.code.status.ErrorStatus.ARTICLE_NOT_FOUND; + +@Service +@RequiredArgsConstructor +public class PostService { + private final PostRepository postRepository; + private final PostConverter postConverter; + private final MemberService memberService; + private final PostMemberReactionRepository postMemberReactionRepository; + + @Transactional + public void createPost(PostRequestDTO.PostSaveDto request){ + Post post = postConverter.toPostEntity(request); + postRepository.save(post); + } + + public PostResponseDTO.PostEntityDto getPostInfo(Long postId) { + Post post = findById(postId); + return postConverter.toPostEntityDto(post); + } + + public Page getPostPreviewByCategoryNewest(Category category, Status status, Pageable pageable) { + Page posts = postRepository.findAllByCategoryAndStatusOrderByCreatedDateDesc(category, status, pageable); + return postConverter.toPreviewListDto(posts); + } + + public Page getPostPreviewByCategoryOldest(Category category, Status status, Pageable pageable) { + Page posts = postRepository.findAllByCategoryAndStatusOrderByCreatedDateAsc(category, status, pageable); + return postConverter.toPreviewListDto(posts); + } + + public Page getPostPreviewByCategoryHot(Category category, Status status, Pageable pageable) { + Page posts = postRepository.findAllByCategoryAndStatusOrderByVoteCountDesc(category, status, pageable); + return postConverter.toPreviewListDto(posts); + } + public Page getPostPreviewNewest(Status status, Pageable pageable) { + Page posts = postRepository.findAllByStatusOrderByCreatedDateDesc(status, pageable); + return postConverter.toPreviewListDto(posts); + } + public Page getPostPreviewOldest(Status status, Pageable pageable) { + Page posts = postRepository.findAllByStatusOrderByCreatedDateAsc(status, pageable); + return postConverter.toPreviewListDto(posts); + } + public Page getPostPreviewHot(Status status, Pageable pageable) { + Page posts = postRepository.findAllByStatusOrderByVoteCountDesc(status, pageable); + return postConverter.toPreviewListDto(posts); + } + + public Page getHotPostPreview(Pageable pageable) { + Page hotPosts = postRepository.findByIsHotTrueOrderByCreatedDateDesc(pageable); + return postConverter.toPreviewListDto(hotPosts); + } + @Transactional + public void enablePostLike(Long postId) { + Post post = findById(postId); + Member member = memberService.getCurrentMember(); + PostMemberReaction reaction = getPostMemberReaction(post, member); + reaction.enableLike(); + } + + @Transactional + public void disablePostLike(Long postId) { + Post post = findById(postId); + Member member = memberService.getCurrentMember(); + PostMemberReaction reaction = getPostMemberReaction(post, member); + reaction.disableLike(); + } + + private PostMemberReaction getPostMemberReaction(Post post, Member member) { + PostMemberReactionId reactionId = new PostMemberReactionId(post, member); + Optional reactionOptional = postMemberReactionRepository.findById(reactionId); + if (reactionOptional.isPresent()) { + return reactionOptional.get(); + } else { + PostMemberReaction reaction = PostMemberReaction.builder() + .post(reactionId.getPost()) + .member(reactionId.getMember()) + .build(); + return postMemberReactionRepository.save(reaction); + } + } + + + public Post findById(Long postId) { + return postRepository.findById(postId) + .orElseThrow(()-> new GeneralException(ARTICLE_NOT_FOUND)); + } +}