diff --git a/.DS_Store b/.DS_Store index 281e3db..2b11931 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/build.gradle b/build.gradle index 2ff8e85..ad35479 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,21 @@ +// Query DSL 관련 스크립트 및 설정 +buildscript { + ext { + queryDslVersion = "5.0.0" + } +} + plugins { - id 'java' id 'org.springframework.boot' version '2.7.7' id 'io.spring.dependency-management' version '1.0.15.RELEASE' + //QueryDSL + id "com.ewerk.gradle.plugins.querydsl" version "1.0.10" + id 'java' } group = 'Remoa' version = '0.0.1-SNAPSHOT' -sourceCompatibility = '1.8' +sourceCompatibility = '17' configurations { compileOnly { @@ -35,10 +44,43 @@ dependencies { // S3 implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' - // h2 db 추가 - implementation "com.h2database:h2" + + //ModelMapper 라이브러리 추가 + implementation 'org.modelmapper:modelmapper:2.4.2' + + //파일 관련 + implementation 'org.apache.pdfbox:pdfbox:2.0.24' + + //QueryDSL + implementation "com.querydsl:querydsl-jpa:${queryDslVersion}" + implementation "com.querydsl:querydsl-apt:${queryDslVersion}" + } -tasks.named('test') { +test { useJUnitPlatform() -} \ No newline at end of file +} + +//querydsl 추가 시작 +def querydslDir = "$buildDir/generated/querydsl" + +querydsl { + jpa = true + querydslSourcesDir = querydslDir +} + +sourceSets { + main.java.srcDir querydslDir +} + +compileQuerydsl { + options.annotationProcessorPath = configurations.querydsl +} + +configurations { + compileOnly { + extendsFrom annotationProcessor + } + querydsl.extendsFrom compileClasspath +//querydsl 추가 끝 +} diff --git a/build/libs/BE-ver_230228.jar b/build/libs/BE-ver_230228.jar deleted file mode 100644 index cc38cb3..0000000 Binary files a/build/libs/BE-ver_230228.jar and /dev/null differ diff --git a/build/libs/BE-ver_230301.jar b/build/libs/BE-ver_230301.jar deleted file mode 100644 index 980901d..0000000 Binary files a/build/libs/BE-ver_230301.jar and /dev/null differ diff --git a/src/main/java/Remoa/BE/Member/Controller/FollowController.java b/src/main/java/Remoa/BE/Member/Controller/FollowController.java index 9e20bdd..e64720d 100644 --- a/src/main/java/Remoa/BE/Member/Controller/FollowController.java +++ b/src/main/java/Remoa/BE/Member/Controller/FollowController.java @@ -4,18 +4,14 @@ import Remoa.BE.Member.Service.FollowService; import Remoa.BE.Member.Service.MemberService; import Remoa.BE.exception.CustomMessage; -import Remoa.BE.exception.response.ErrorResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; -import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -27,7 +23,6 @@ @RestController @Slf4j @RequiredArgsConstructor -@CrossOrigin(origins = "*") public class FollowController { private final FollowService followService; @@ -43,29 +38,27 @@ public class FollowController { @PostMapping("/follow/{member_id}") public ResponseEntity follow(@PathVariable("member_id") Long memberId, HttpServletRequest request) { - if(authorized(request)){ + if (authorized(request)) { //나 자신을 팔로우 하는 경우 Long myMemberId = getMemberId(); - if(Objects.equals(memberId, myMemberId)){ + if (Objects.equals(memberId, myMemberId)) { return errorResponse(CustomMessage.FOLLOW_ME); - } - else{ + } else { Member member = memberService.findOne(myMemberId); boolean check = followService.followFunction(memberId, member); //팔로우 - if(check){ - return successResponse(CustomMessage.OK_FOLLOW,followService.showFollowId(member)); + if (check) { + return new ResponseEntity<>(HttpStatus.CREATED); } //언팔로우 - else{ - return successResponse(CustomMessage.OK_UNFOLLOW,followService.showFollowId(member)); + else { + return new ResponseEntity<>(HttpStatus.OK); + } } - } - return errorResponse(CustomMessage.UNAUTHORIZED); } diff --git a/src/main/java/Remoa/BE/Member/Controller/KakaoController.java b/src/main/java/Remoa/BE/Member/Controller/KakaoController.java index be74e54..3097c5a 100644 --- a/src/main/java/Remoa/BE/Member/Controller/KakaoController.java +++ b/src/main/java/Remoa/BE/Member/Controller/KakaoController.java @@ -5,21 +5,21 @@ import Remoa.BE.Member.Dto.Res.ResSignupDto; import Remoa.BE.Member.Service.KakaoService; import Remoa.BE.Member.Service.MemberService; +import Remoa.BE.Member.Service.ProfileService; import Remoa.BE.exception.CustomMessage; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.servlet.http.HttpSession; import java.io.IOException; import java.util.Map; import java.util.Optional; +import java.util.Random; import static Remoa.BE.exception.CustomBody.*; import static Remoa.BE.utill.MemberInfo.*; @@ -28,12 +28,13 @@ @RestController @Slf4j @RequiredArgsConstructor -@CrossOrigin(origins = "*") // 프론트에서 추가 요청 public class KakaoController { + + private final KakaoService ks; private final MemberService memberService; - private final HttpSession httpSession; + private final ProfileService profileService; /** * 카카오 로그인을 통해 code를 query string으로 받아오면, 코드를 통해 토큰, 토큰을 통해 사용자 정보를 얻어와 db에 해당 사용자가 존재하는지 여부를 @@ -62,7 +63,7 @@ public ResponseEntity getCI(@RequestParam String code, HttpServletReques if (member.isPresent()) { securityLoginWithoutLoginForm(member.get(), request); //if문에 걸리지 않았다면 이미 회원가입이 진행돼 db에 kakaoId가 있는 유저이므로 kakaoMember가 존재하므로 LoginController처럼 로그인 처리 하면 됩니다. - return successResponse(CustomMessage.OK, userInfo); + return successResponse(CustomMessage.OK, member.get().getNickname()); } else { //kakaoId가 db에 없으므로 kakaoMember가 null이므로 회원가입하지 않은 회원. 따라서 회원가입이 필요하므로 회원가입하는 uri로 redirect 시켜주어야 함. @@ -70,34 +71,31 @@ public ResponseEntity getCI(@RequestParam String code, HttpServletReques } } - /** - * 카카오 로그인을 우회해 테스트 하기 위한 용도로 추가됨. - * @param kakaoId - * @return ResponseEntity - */ - @PostMapping("/login/kakao/test") - public ResponseEntity testLogin(@RequestBody Integer kakaoId, HttpServletRequest request) { - log.warn("kakaoId = {}", kakaoId); - Optional findMember = memberService.findByKakaoId(Long.valueOf(kakaoId)); - if (findMember.isPresent()) { - Member member = findMember.get(); - securityLoginWithoutLoginForm(member, request); - return successResponse(CustomMessage.OK, member); - } - return failResponse(CustomMessage.VALIDATED, "User Not Exist"); - } - /** * front-end에서 회원가입에 필요한 정보를 넘겨주면 KakaoSignupForm으로 받아 회원가입을 진행시켜줌 */ @PostMapping("/signup/kakao") - public ResponseEntity signupKakaoMember(@RequestBody @Validated ReqSignupDto form, HttpServletRequest request) { + public ResponseEntity signupKakaoMember(@RequestBody @Validated ReqSignupDto form, HttpServletRequest request) throws IOException { Member member = new Member(); + Random random = new Random(); + + //닉네임 사용 가능하면 그대로 진행, 불가능하면 임의 닉네임 "유저-{randomInt}로 지정. + String randomNumber = Integer.toString((random.nextInt(900_000) + 100_000)); // 100_000 ~ 999_999 + boolean nicknameDuplicate = memberService.isNicknameDuplicate("유저-" + randomNumber); + while (nicknameDuplicate) { //특수문자는 닉네임에 사용할 수 없으나 임의로 지정하는 닉네임에는 사용 가능하게 해서 또 다른 중복 문제 없게끔. + randomNumber = Integer.toString((random.nextInt(900_000) + 100_000)); // 100_000 ~ 999_999 + nicknameDuplicate = memberService.isNicknameDuplicate("유저-" + randomNumber); + } + member.setNickname("유저-" + randomNumber); + + + //카카오에서 받은 프로필 사진 url 링크를 토대로 s3에 저장 + if (memberService.findByKakaoId(form.getKakaoId()).isPresent()) { + return failResponse(CustomMessage.VALIDATED, "kakaoId가 이미 가입되어 있습니다."); + } member.setKakaoId(form.getKakaoId()); member.setEmail(form.getEmail()); - member.setNickname(form.getNickname()); - member.setProfileImage(form.getProfileImage()); member.setTermConsent(form.getTermConsent()); memberService.join(member); @@ -114,39 +112,22 @@ public ResponseEntity signupKakaoMember(@RequestBody @Validated ReqSignu return successResponse(CustomMessage.OK,result); } - /** - *자동 로그인 추후에 - */ -/* - @GetMapping("/login") - public ResponseEntity autoLogin(){ - Long kaKaoId = getKaKaoId(); - Optional member = memberService.findByKakaoId(kaKaoId); - if(member.isPresent()){ - return successResponse(CustomMessage.OK,member); - } - else{ - return errorResponse(CustomMessage.UNAUTHORIZED); - } - - } -*/ - /** * 로그아웃 기능 세션무효화, jsession쿠키를 제거, */ - @PostMapping("/user/logout") - public ResponseEntity logout(HttpServletResponse response){ + @PostMapping ("/user/logout") + public ResponseEntity logout(HttpServletRequest request){ + if(authorized(request)) { + + SecurityContextHolder.clearContext(); + request.getSession().invalidate(); - SecurityContextHolder.clearContext(); // 현재 SecurityContext를 제거합니다. - httpSession.invalidate(); // HttpSession을 무효화합니다. + return new ResponseEntity<>(HttpStatus.OK); + + } - Cookie myCookie = new Cookie("JSESSIONID", null); - myCookie.setMaxAge(0); // 쿠키의 expiration 타임을 0으로 하여 없앤다. - myCookie.setPath("/"); // 모든 경로에서 삭제 됬음을 알린다. - response.addCookie(myCookie); - return successResponse(CustomMessage.OK,myCookie); + return errorResponse(CustomMessage.UNAUTHORIZED); } diff --git a/src/main/java/Remoa/BE/Member/Controller/MyFollowingController.java b/src/main/java/Remoa/BE/Member/Controller/MyFollowingController.java new file mode 100644 index 0000000..ac913fd --- /dev/null +++ b/src/main/java/Remoa/BE/Member/Controller/MyFollowingController.java @@ -0,0 +1,59 @@ +package Remoa.BE.Member.Controller; + +import Remoa.BE.Member.Domain.Member; +import Remoa.BE.Member.Dto.Res.ResMypageFollowing; +import Remoa.BE.Member.Dto.Res.ResMypageList; +import Remoa.BE.Member.Service.MemberService; +import Remoa.BE.Member.Service.MyFollowingService; +import Remoa.BE.exception.CustomMessage; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpRequest; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; + +import java.util.ArrayList; +import java.util.List; + +import static Remoa.BE.exception.CustomBody.errorResponse; +import static Remoa.BE.exception.CustomBody.successResponse; +import static Remoa.BE.utill.MemberInfo.authorized; +import static Remoa.BE.utill.MemberInfo.getMemberId; + +@RestController +@Slf4j +@RequiredArgsConstructor +public class MyFollowingController { + + private final MemberService memberService; + + private final MyFollowingService myFollowingService; + + @GetMapping("/following") // 마이페이지 팔로잉 관리화면 + public ResponseEntity mypageFollowing(HttpServletRequest request){ + if(authorized(request)){ + Long myMemberId = getMemberId(); + Member member = memberService.findOne(myMemberId); + ResMypageFollowing resMypageFollowing = myFollowingService.mypageFollowing(member); + + return successResponse(CustomMessage.OK, resMypageFollowing); + } + return errorResponse(CustomMessage.UNAUTHORIZED); + } + + @GetMapping("/follower") // 마이페이지 팔로워 관리화면 + public ResponseEntity mypageFollower(HttpServletRequest request){ + if(authorized(request)){ + Long myMemberId = getMemberId(); + Member member = memberService.findOne(myMemberId); + ResMypageFollowing resMypageFollower = myFollowingService.mypageFollower(member); + + return successResponse(CustomMessage.OK, resMypageFollower); + } + return errorResponse(CustomMessage.UNAUTHORIZED); + } + +} diff --git a/src/main/java/Remoa/BE/Member/Controller/ProfileController.java b/src/main/java/Remoa/BE/Member/Controller/ProfileController.java index c8bebb4..78a0899 100644 --- a/src/main/java/Remoa/BE/Member/Controller/ProfileController.java +++ b/src/main/java/Remoa/BE/Member/Controller/ProfileController.java @@ -1,74 +1,159 @@ package Remoa.BE.Member.Controller; +import Remoa.BE.Member.Domain.AwsS3; import Remoa.BE.Member.Domain.Member; import Remoa.BE.Member.Dto.Req.EditProfileForm; +import Remoa.BE.Member.Dto.Res.ResUserInfoDto; +//import Remoa.BE.Member.Service.ImageService; +import Remoa.BE.Member.Service.AwsS3Service; import Remoa.BE.Member.Service.MemberService; import Remoa.BE.Member.Service.ProfileService; import Remoa.BE.exception.CustomMessage; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; + + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.util.Objects; + import static Remoa.BE.exception.CustomBody.*; +import static Remoa.BE.utill.FileExtension.fileExtension; +import static Remoa.BE.utill.MemberInfo.authorized; +import static Remoa.BE.utill.MemberInfo.getMemberId; @Slf4j @RestController @RequiredArgsConstructor -@CrossOrigin(origins = "*") public class ProfileController { private final ProfileService profileService; - private final MemberService memberService; + private final AwsS3Service awsS3Service; // 프로필 수정 범위 : 닉네임(중복확인), 핸드폰번호, 대학교, 한줄소개 @GetMapping("/user") - public ResponseEntity userHome(HttpServletRequest request) { + public ResponseEntity userInfo(HttpServletRequest request) { + + if(authorized(request)) { + Long memberId = getMemberId(); + // 로그인된 사용자의 정보를 db에서 다시 불러와 띄워줌. + Member member = memberService.findOne(memberId); + ResUserInfoDto resUserInfoDto = ResUserInfoDto.builder() + .email(member.getEmail()) + .nickname(member.getNickname()) + .phoneNumber(member.getPhoneNumber()) + .university(member.getUniversity()) + .oneLineIntroduction(member.getOneLineIntroduction()) + .build(); + return successResponse(CustomMessage.OK, resUserInfoDto); + } + + return errorResponse(CustomMessage.UNAUTHORIZED); + + } + + @PutMapping("/user") + public ResponseEntity editProfile(@RequestBody EditProfileForm form, HttpServletRequest request) { + + if(authorized(request)){ + Long memberId = getMemberId(); - HttpSession session = request.getSession(); - // 현재 로그인한 사용자의 세션 가져오기 - Member loginMember = (Member) session.getAttribute("loginMember"); + Member myMember = memberService.findOne(memberId); + log.info(myMember.getNickname()); + if(memberService.isNicknameDuplicate(myMember.getNickname())) { + + // 사용자의 입력 정보를 DTO에 담아 서비스로 전달 + profileService.editProfile(memberId, form); + ResUserInfoDto resUserInfoDto = ResUserInfoDto.builder() + .email(myMember.getEmail()) + .nickname(myMember.getNickname()) + .phoneNumber(myMember.getPhoneNumber()) + .university(myMember.getUniversity()) + .oneLineIntroduction(myMember.getOneLineIntroduction()) + .build(); + + return successResponse(CustomMessage.OK, resUserInfoDto); + } + + return errorResponse(CustomMessage.BAD_DUPLICATE); - // 세션이 없으면 로그인 페이지로 이동 - if (loginMember == null) { - return failResponse(CustomMessage.VALIDATED, "redirect:/login/kakao"); } - // 로그인된 사용자의 정보를 db에서 다시 불러와 띄워줌. - Member member = memberService.findOne(loginMember.getMemberId()); - return successResponse(CustomMessage.OK, member); + // 수정이 완료되면 프로필 페이지로 이동 + return errorResponse(CustomMessage.UNAUTHORIZED); } - // RESTful API에서 PUT 매핑은 수정할 리소스를 명확하게 지정해야 하는데 이 경우에는 URL에 리소스 ID를 명시하는 것이 일반적이다. - // 그런데 우리는 수정할 사용자의 정보를 모두 입력받아 수정하는 형태이기 때문에 - // URL에 리소스 ID를 명시할 필요가 없어서 PUT대신 POST 매핑을 사용하였습니다. - @PostMapping("/user") - public ResponseEntity editProfile(@RequestBody EditProfileForm form, HttpServletRequest request) { - HttpSession session = request.getSession(); - Member loginMember = (Member) session.getAttribute("loginMember"); - if (loginMember == null) { - // 로그인되어 있지 않은 경우 로그인 페이지로 이동 - return failResponse(CustomMessage.VALIDATED, "로그인하지 않은 회원입니다. 로 redirect"); + // 프로필 사진 불러오기 + @GetMapping("/user/img") + public ResponseEntity showImage(HttpServletRequest request) { + if(authorized(request)) { + Long memberId = getMemberId(); + Member myMember = memberService.findOne(memberId); + return successResponse(CustomMessage.OK, myMember.getProfileImage()); } + return errorResponse(CustomMessage.UNAUTHORIZED); + } + + // 프로필 사진 업로드 + @PutMapping("/user/img") + public ResponseEntity upload(@RequestPart("file") MultipartFile multipartFile, HttpServletRequest request) throws IOException { + + if(authorized(request)) { - if (memberService.isNicknameDuplicate(form.getNickname())) { - return failResponse(CustomMessage.VALIDATED, "닉네임이 중복됩니다."); + //jpg랑 png만 가능합니다 + String extension = fileExtension(multipartFile); + if(extension.equals("png") ||extension.equals("jpg")) { + + Long memberId = getMemberId(); + Member myMember = memberService.findOne(memberId); + + + String editProfileImg = awsS3Service.editProfileImg(myMember.getProfileImage(), multipartFile); + myMember.setProfileImage(editProfileImg); + memberService.join(myMember); + return new ResponseEntity<>(HttpStatus.OK); + } + else{ + throw new IOException(); + } } + return errorResponse(CustomMessage.UNAUTHORIZED); + } - // 사용자의 입력 정보를 DTO에 담아 서비스로 전달 - EditProfileForm profileInfo = new EditProfileForm( - form.getNickname(), form.getPhoneNumber(), form.getUniversity(), form.getOneLineIntroduction()); - profileService.editProfile(loginMember.getMemberId(), profileInfo); + @DeleteMapping("/user/img") + public ResponseEntity remove(HttpServletRequest request) throws MalformedURLException { + if(authorized(request)) { - // 수정이 완료되면 프로필 페이지로 이동 - return successResponse(CustomMessage.OK, "redirect:/user"); + Long memberId = getMemberId(); + Member myMember = memberService.findOne(memberId); + + //유저가 기본프로필이 아니라면 + if(!Objects.equals(myMember.getProfileImage(), "https://remoa.s3.ap-northeast-2.amazonaws.com/img/profile_img.png")) { + awsS3Service.removeProfileUrl(myMember.getProfileImage()); + myMember.setProfileImage("https://remoa.s3.ap-northeast-2.amazonaws.com/img/profile_img.png"); + memberService.join(myMember); + } + + return new ResponseEntity<>(HttpStatus.OK); + } + return errorResponse(CustomMessage.UNAUTHORIZED); } + + + /** * 프론트에서 닉네임 중복 검사를 할 때 사용할 메서드 * @param nickname @@ -77,9 +162,9 @@ public ResponseEntity editProfile(@RequestBody EditProfileForm form, Htt @GetMapping("/nickname") public ResponseEntity checkNicknameDuplicate(@RequestParam String nickname) { if (memberService.isNicknameDuplicate(nickname)) { - return successResponse(CustomMessage.OK, nickname + "은(는) 이미 사용중인 닉네임입니다."); + return successResponse(CustomMessage.OK, false); } else { - return successResponse(CustomMessage.OK, nickname + "은(는) 사용 가능한 닉네임입니다."); + return successResponse(CustomMessage.OK, true); } } } \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Member/Controller/WithdrewController.java b/src/main/java/Remoa/BE/Member/Controller/WithdrewController.java index e6922b7..d133a42 100644 --- a/src/main/java/Remoa/BE/Member/Controller/WithdrewController.java +++ b/src/main/java/Remoa/BE/Member/Controller/WithdrewController.java @@ -11,11 +11,11 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; +import java.util.Objects; @RestController @Slf4j @RequiredArgsConstructor -@CrossOrigin(origins = "*") public class WithdrewController { private final WithdrewService withdrewService; @@ -41,7 +41,7 @@ public ResponseEntity withdrewRemoa(@PathVariable("member_id") Long memb //PathVariable의 id와 로그인된 사용자의 id가 같은지 확인하기 위한 용도. Member loginMember = (Member) session.getAttribute("loginMember"); - if (loginMember.getMemberId() != memberId) { + if (!Objects.equals(loginMember.getMemberId(), memberId)) { return new ResponseEntity<>("회원정보가 일치하지 않습니다.", HttpStatus.UNAUTHORIZED); } @@ -71,19 +71,4 @@ public ResponseEntity withdrewRemoaWithoutPathVariable(HttpServletReques return new ResponseEntity<>("회원 탈퇴가 완료되었습니다.", HttpStatus.OK); } - /** - * 탈퇴 처리가 되어 deleted 필드가 true인 member가 조회되는지 확인하기 위한 테스트 uri. - * @param memberId - * @return 조회 결과가 없으면 418(I am a tea pot), 있으면 200(OK) - */ - @GetMapping("/find/{member_id}") - public ResponseEntity findMemberTest(@PathVariable("member_id") Long memberId) { - Member member = memberService.findOne(memberId); - - if (member == null) { - return new ResponseEntity<>("회원 정보가 없습니다.", HttpStatus.I_AM_A_TEAPOT); - } - - return new ResponseEntity<>("memberId 번호 <" + memberId + ">는 " + member.getName() + "입니다.", HttpStatus.OK); - } } diff --git a/src/main/java/Remoa/BE/Member/Domain/AwsS3.java b/src/main/java/Remoa/BE/Member/Domain/AwsS3.java new file mode 100644 index 0000000..64b5fa8 --- /dev/null +++ b/src/main/java/Remoa/BE/Member/Domain/AwsS3.java @@ -0,0 +1,22 @@ +package Remoa.BE.Member.Domain; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class AwsS3 { + private String key; + private String path; + + public AwsS3() { + + } + + @Builder + public AwsS3(String key, String path) { + this.key = key; + this.path = path; + } +} \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Member/Domain/Comment.java b/src/main/java/Remoa/BE/Member/Domain/Comment.java index d9a1e07..5d79ca8 100644 --- a/src/main/java/Remoa/BE/Member/Domain/Comment.java +++ b/src/main/java/Remoa/BE/Member/Domain/Comment.java @@ -3,11 +3,12 @@ import Remoa.BE.Post.Domain.Post; import lombok.Getter; import lombok.Setter; -import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.Where; import javax.persistence.*; +import java.time.LocalDateTime; + import static javax.persistence.FetchType.LAZY; @Entity @@ -17,7 +18,7 @@ public class Comment { @Id - @GeneratedValue + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "comment_id") private Long commentId; @@ -38,19 +39,30 @@ public class Comment { /** * Comment의 내용 */ + @Lob private String comment; + /** + * 대댓글 기능을 위해 부모 댓글과의 연관관계 세팅. 부모댓글인 경우 null. + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parent_comment_id") + private Comment parentComment = null; + /** * Comment가 작성된 시간 */ @Column(name = "commented_time") - private String commentedTime; + private LocalDateTime commentedTime; /** * Comment의 좋아요 숫자 */ @Column(name = "comment_like_count") - private Integer commentLikeCount; + private Integer commentLikeCount = 0; + + @OneToOne + private CommentFeedback commentFeedback; private Boolean deleted = Boolean.FALSE; } \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Member/Domain/CommentBookmark.java b/src/main/java/Remoa/BE/Member/Domain/CommentBookmark.java index b90ffc6..b888e13 100644 --- a/src/main/java/Remoa/BE/Member/Domain/CommentBookmark.java +++ b/src/main/java/Remoa/BE/Member/Domain/CommentBookmark.java @@ -14,6 +14,7 @@ @Setter @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) +@Deprecated public class CommentBookmark { @Id diff --git a/src/main/java/Remoa/BE/Member/Domain/CommentFeedback.java b/src/main/java/Remoa/BE/Member/Domain/CommentFeedback.java new file mode 100644 index 0000000..16fbc9e --- /dev/null +++ b/src/main/java/Remoa/BE/Member/Domain/CommentFeedback.java @@ -0,0 +1,49 @@ +package Remoa.BE.Member.Domain; + +import Remoa.BE.Post.Domain.Post; +import lombok.*; +import org.hibernate.annotations.Where; + +import javax.persistence.*; +import java.time.LocalDateTime; + +import static javax.persistence.FetchType.LAZY; + +/** + * 마이페이지-내 활동 관리에 쓰이는 Comment와 Feedback을 구분 없이 최신순으로 조회하기 위한 entity. + */ +@Builder +@Entity +@Getter +@Setter +@Where(clause = "deleted = false") +@NoArgsConstructor +@AllArgsConstructor +public class CommentFeedback { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "comment_feedback_id") + private Long commentFeedbackId; + + + private ContentType type; + + @ManyToOne(fetch = LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = LAZY) + @JoinColumn(name = "post_id") + private Post post; + + @OneToOne + private Comment comment; + + @OneToOne + private Feedback feedback; + + private LocalDateTime time; + + private Boolean deleted = Boolean.FALSE; +} diff --git a/src/main/java/Remoa/BE/Member/Domain/ContentType.java b/src/main/java/Remoa/BE/Member/Domain/ContentType.java new file mode 100644 index 0000000..687b287 --- /dev/null +++ b/src/main/java/Remoa/BE/Member/Domain/ContentType.java @@ -0,0 +1,14 @@ +package Remoa.BE.Member.Domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * CommentFeedback에 comment와 Feedback 구분을 위한 enum 클래스. + */ +@AllArgsConstructor +@Getter +public enum ContentType { + COMMENT, + FEEDBACK; +} diff --git a/src/main/java/Remoa/BE/Member/Domain/Feedback.java b/src/main/java/Remoa/BE/Member/Domain/Feedback.java index 1bcb965..6961e83 100644 --- a/src/main/java/Remoa/BE/Member/Domain/Feedback.java +++ b/src/main/java/Remoa/BE/Member/Domain/Feedback.java @@ -7,6 +7,8 @@ import javax.persistence.*; +import java.time.LocalDateTime; + import static javax.persistence.FetchType.LAZY; @Entity @@ -16,7 +18,7 @@ public class Feedback { @Id - @GeneratedValue + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "feedback_id") private Long feedbackId; @@ -40,19 +42,31 @@ public class Feedback { /** * Feedback의 내용 */ + @Lob private String feedback; + /** + * 대댓글 기능을 위해 부모 댓글과의 연관관계 세팅. 부모댓글인 경우 null. + */ + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parent_feedback_id") + private Feedback parentFeedback = null; + + /** * Feedback이 작성된 시간 */ @Column(name = "feedback_time") - private String feedbackTime; + private LocalDateTime feedbackTime; /** * Feedback의 좋아요 숫자 */ @Column(name = "feedback_like_count") - private Integer feedbackLikeCount; + private Integer feedbackLikeCount = 0; + + @OneToOne + private CommentFeedback commentFeedback; private Boolean deleted = Boolean.FALSE; } \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Member/Domain/FeedbackBookmark.java b/src/main/java/Remoa/BE/Member/Domain/FeedbackBookmark.java index be94dfb..cfe1fbe 100644 --- a/src/main/java/Remoa/BE/Member/Domain/FeedbackBookmark.java +++ b/src/main/java/Remoa/BE/Member/Domain/FeedbackBookmark.java @@ -14,6 +14,7 @@ @Setter @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) +@Deprecated public class FeedbackBookmark { @Id diff --git a/src/main/java/Remoa/BE/Member/Domain/Follow.java b/src/main/java/Remoa/BE/Member/Domain/Follow.java index f84b161..dc6dbd3 100644 --- a/src/main/java/Remoa/BE/Member/Domain/Follow.java +++ b/src/main/java/Remoa/BE/Member/Domain/Follow.java @@ -2,7 +2,6 @@ import lombok.Getter; import lombok.Setter; -import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.Where; import javax.persistence.*; diff --git a/src/main/java/Remoa/BE/Member/Domain/Member.java b/src/main/java/Remoa/BE/Member/Domain/Member.java index 2d71b83..c206133 100644 --- a/src/main/java/Remoa/BE/Member/Domain/Member.java +++ b/src/main/java/Remoa/BE/Member/Domain/Member.java @@ -1,10 +1,8 @@ package Remoa.BE.Member.Domain; import Remoa.BE.Post.Domain.Post; -import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Getter; import lombok.Setter; -import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.Where; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -67,6 +65,7 @@ public class Member implements UserDetails { * 한 줄 소개 */ @Column(name = "one_line_introduction") + @Lob private String oneLineIntroduction; /** @@ -79,29 +78,32 @@ public class Member implements UserDetails { * 카카오 api에서 받아오는 카카오 프로필 사진 uri */ @Column(name = "profile_image") - private String profileImage; + private String profileImage="https://remoa.s3.ap-northeast-2.amazonaws.com/img/profile_img.png"; - @OneToMany(mappedBy = "member") + @OneToMany(mappedBy = "member",fetch = FetchType.LAZY) private List posts = new ArrayList<>(); - @OneToMany(mappedBy = "member") + @OneToMany(mappedBy = "member",fetch = FetchType.LAZY) private List comments = new ArrayList<>(); - @OneToMany(mappedBy = "member") + @OneToMany(mappedBy = "member",fetch = FetchType.LAZY) private List feedbacks = new ArrayList<>(); - @OneToMany(mappedBy = "member", cascade = {CascadeType.ALL}) + @OneToMany(mappedBy = "member", cascade = {CascadeType.ALL},fetch = FetchType.LAZY) private List memberCategories = new ArrayList<>(); - @OneToMany(mappedBy = "member", cascade = {CascadeType.ALL}) + @OneToMany(mappedBy = "member", cascade = {CascadeType.ALL},fetch = FetchType.LAZY) private List commentBookmarks = new ArrayList<>(); - @OneToMany(mappedBy = "member", cascade = {CascadeType.ALL}) + @OneToMany(mappedBy = "member", cascade = {CascadeType.ALL},fetch = FetchType.LAZY) private List commentLikes = new ArrayList<>(); - @OneToMany(mappedBy = "fromMember", cascade = {CascadeType.ALL}) + @OneToMany(mappedBy = "fromMember", cascade = {CascadeType.ALL},fetch = FetchType.LAZY) private List follows = new ArrayList<>(); + @OneToMany(mappedBy = "member", cascade = {CascadeType.ALL}, fetch = FetchType.LAZY) + private List commentFeedbacks = new ArrayList<>(); + /** * ADMIN과 일반 USER를 구분하기 위해 존재. Spring Security 이용하기 위함 */ diff --git a/src/main/java/Remoa/BE/Member/Dto/Req/ReqSignupDto.java b/src/main/java/Remoa/BE/Member/Dto/Req/ReqSignupDto.java index d7dfbfe..7db89c2 100644 --- a/src/main/java/Remoa/BE/Member/Dto/Req/ReqSignupDto.java +++ b/src/main/java/Remoa/BE/Member/Dto/Req/ReqSignupDto.java @@ -16,10 +16,6 @@ public class ReqSignupDto { @NotNull(message = "카카오에서 발급받은 id값이 누락되었습니다.") private Long kakaoId; - @NotBlank(message = "이름은 필수값입니다.") - private String nickname; - - private String profileImage; @NotNull(message = "선택 동의사항 값은 필수입니다.") private Boolean termConsent; diff --git a/src/main/java/Remoa/BE/Member/Dto/Res/ResMemberInfoDto.java b/src/main/java/Remoa/BE/Member/Dto/Res/ResMemberInfoDto.java new file mode 100644 index 0000000..8048aa3 --- /dev/null +++ b/src/main/java/Remoa/BE/Member/Dto/Res/ResMemberInfoDto.java @@ -0,0 +1,13 @@ +package Remoa.BE.Member.Dto.Res; + +import lombok.*; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class ResMemberInfoDto { + + private Long memberId; + private String nickname; + private String profileImage; +} diff --git a/src/main/java/Remoa/BE/Member/Dto/Res/ResMypageFollowing.java b/src/main/java/Remoa/BE/Member/Dto/Res/ResMypageFollowing.java new file mode 100644 index 0000000..3756f9b --- /dev/null +++ b/src/main/java/Remoa/BE/Member/Dto/Res/ResMypageFollowing.java @@ -0,0 +1,18 @@ +package Remoa.BE.Member.Dto.Res; + +import lombok.Builder; +import lombok.Getter; + +import java.util.List; + +@Getter +@Builder +public class ResMypageFollowing { + + private String userName; + + private int followNum; + + private List resMypageList; + +} diff --git a/src/main/java/Remoa/BE/Member/Dto/Res/ResMypageList.java b/src/main/java/Remoa/BE/Member/Dto/Res/ResMypageList.java new file mode 100644 index 0000000..849293f --- /dev/null +++ b/src/main/java/Remoa/BE/Member/Dto/Res/ResMypageList.java @@ -0,0 +1,16 @@ +package Remoa.BE.Member.Dto.Res; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ResMypageList { + private String profileImage; + + private String userName; + + private int followingNum; + + private int followerNum; +} diff --git a/src/main/java/Remoa/BE/Member/Dto/Res/ResSignupDto.java b/src/main/java/Remoa/BE/Member/Dto/Res/ResSignupDto.java index b4b390c..4a20807 100644 --- a/src/main/java/Remoa/BE/Member/Dto/Res/ResSignupDto.java +++ b/src/main/java/Remoa/BE/Member/Dto/Res/ResSignupDto.java @@ -3,8 +3,6 @@ import lombok.Builder; import lombok.Getter; -import javax.validation.constraints.NotBlank; - @Builder @Getter public class ResSignupDto { diff --git a/src/main/java/Remoa/BE/Member/Dto/Res/ResUserInfoDto.java b/src/main/java/Remoa/BE/Member/Dto/Res/ResUserInfoDto.java new file mode 100644 index 0000000..58f37df --- /dev/null +++ b/src/main/java/Remoa/BE/Member/Dto/Res/ResUserInfoDto.java @@ -0,0 +1,19 @@ +package Remoa.BE.Member.Dto.Res; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class ResUserInfoDto { + + private String email; + + private String nickname; + private String phoneNumber; + + private String university; + + private String oneLineIntroduction; + +} diff --git a/src/main/java/Remoa/BE/Member/Repository/MemberRepository.java b/src/main/java/Remoa/BE/Member/Repository/MemberRepository.java index 3f07bcd..195dbb1 100644 --- a/src/main/java/Remoa/BE/Member/Repository/MemberRepository.java +++ b/src/main/java/Remoa/BE/Member/Repository/MemberRepository.java @@ -4,16 +4,11 @@ import Remoa.BE.Member.Domain.Member; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import javax.persistence.EntityManager; -import javax.persistence.EntityNotFoundException; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; @Repository @Slf4j @@ -100,6 +95,18 @@ public List loadFollows(Member member) { .getResultList(); } + /** + * 팔로워 Member 객체 리스트 반환 + * @param member + * @return List + */ + public List loadFollowers(Member member){ + return em.createQuery("select f.fromMember from Follow f " + + "where f.toMember = :member", Member.class) + .setParameter("member", member) + .getResultList(); + } + /** * 멤버가 팔로우하는 모든멤버의 아이디를 불러와줌 * @param member diff --git a/src/main/java/Remoa/BE/Member/Service/AwsS3Service.java b/src/main/java/Remoa/BE/Member/Service/AwsS3Service.java new file mode 100644 index 0000000..7389f4f --- /dev/null +++ b/src/main/java/Remoa/BE/Member/Service/AwsS3Service.java @@ -0,0 +1,78 @@ +package Remoa.BE.Member.Service; + +import Remoa.BE.Member.Domain.AwsS3; +import Remoa.BE.utill.FileExtension; +import com.amazonaws.services.s3.AmazonS3; +import com.amazonaws.services.s3.model.AmazonS3Exception; +import com.amazonaws.services.s3.model.CannedAccessControlList; +import com.amazonaws.services.s3.model.ObjectMetadata; +import com.amazonaws.services.s3.model.PutObjectRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +import static Remoa.BE.utill.FileExtension.fileExtension; + +@Service +@RequiredArgsConstructor +@Slf4j +public class AwsS3Service { + + private final AmazonS3 amazonS3; + + @Value("${cloud.aws.s3.bucket}") + private String bucket; + + public String editProfileImg(String profileImgUrl,MultipartFile multipartFile) throws IOException { + + if(!Objects.equals(profileImgUrl, "https://remoa.s3.ap-northeast-2.amazonaws.com/img/profile_img.png")) { + //기존 프로필 사진 s3에서 삭제 + removeProfileUrl(profileImgUrl); + } + + //파일 타입과 사이즈 저장 + ObjectMetadata objectMetadata = new ObjectMetadata(); + objectMetadata.setContentType("image/jpeg"); + log.info(multipartFile.getContentType()); + objectMetadata.setContentLength(multipartFile.getSize()); + + //파일 이름 + String originalFilename = multipartFile.getOriginalFilename(); + + //파일 이름이 겹치지 않게 + String uuid = UUID.randomUUID().toString(); + + //post 폴더에 따로 넣어서 보관 + String s3name = "img/"+uuid+"_"+originalFilename; + + try (InputStream inputStream = multipartFile.getInputStream()) { + amazonS3.putObject(new PutObjectRequest(bucket, s3name, inputStream, objectMetadata) + .withCannedAcl(CannedAccessControlList.PublicRead)); + } catch (IOException e) { + //파일을 제대로 받아오지 못했을때 + //Todo 예외처리 custom 따로 만들기 + throw new RuntimeException(e); + } + + return amazonS3.getUrl(bucket,s3name).toString().replaceAll("\\+", "+"); + + } + + public void removeProfileUrl(String profileImgUrl) throws MalformedURLException { + URL fileUrl = new URL(profileImgUrl); + String objectKey = fileUrl.getPath().replaceAll("^/", ""); + amazonS3.deleteObject(bucket,objectKey); + } +} \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Member/Service/FollowService.java b/src/main/java/Remoa/BE/Member/Service/FollowService.java index a90ae23..93624af 100644 --- a/src/main/java/Remoa/BE/Member/Service/FollowService.java +++ b/src/main/java/Remoa/BE/Member/Service/FollowService.java @@ -9,7 +9,6 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; -import java.util.Optional; @Service @Slf4j diff --git a/src/main/java/Remoa/BE/Member/Service/KakaoService.java b/src/main/java/Remoa/BE/Member/Service/KakaoService.java index 79a2ed0..d37e100 100644 --- a/src/main/java/Remoa/BE/Member/Service/KakaoService.java +++ b/src/main/java/Remoa/BE/Member/Service/KakaoService.java @@ -1,19 +1,19 @@ package Remoa.BE.Member.Service; -import Remoa.BE.Member.Domain.Member; import Remoa.BE.Member.Repository.MemberRepository; +import com.amazonaws.services.s3.AmazonS3; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import java.io.*; import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.ProtocolException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; @@ -24,7 +24,6 @@ public class KakaoService { //카카오 로그인시 접속해야 할 링크 : https://kauth.kakao.com/oauth/authorize?client_id=139febf9e13da4d124d1c1faafcf3f86&redirect_uri=http://localhost:8080/login/kakao&response_type=code - private final MemberRepository MemberRepository; /** * 카카오 인증 서버에 code를 보내고 token을 발급받는 메서드 @@ -104,7 +103,8 @@ public String getToken(String code) throws IOException { * @return 카카오 사용자 정보((kakao)id, nickname, email, profileImage) * @throws IOException */ - public Map getUserInfo(String access_token) throws IOException { + public Map getUserInfo(String access_token) { + //사용자 정보를 받아올 카카오 api 서버. 레모아 서버가 클리아언트로, 카카오 api 서버가 서버로 동작한다고 보면 됩니다. String host = "https://kapi.kakao.com/v2/user/me"; //사용자 정보를 받을 Map 객체 생성 @@ -121,7 +121,7 @@ public Map getUserInfo(String access_token) throws IOException { int responseCode = urlConnection.getResponseCode(); log.debug("responseCode = {}", responseCode); - BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream())); + BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), StandardCharsets.UTF_8)); String line = ""; String res = ""; //다양한 형식(한 줄 이상의 JSON 데이터)를 받기 위한 작업 @@ -133,18 +133,13 @@ public Map getUserInfo(String access_token) throws IOException { //JSON parsing JSONParser parser = new JSONParser(); JSONObject obj = (JSONObject) parser.parse(res); - JSONObject properties = (JSONObject) obj.get("properties"); String id = obj.get("id").toString(); - String nickname = properties.get("nickname").toString(); - String profileImage = properties.get("profile_image").toString(); JSONObject kakao_account = (JSONObject) obj.get("kakao_account"); String email = kakao_account.get("email").toString(); result.put("id", id); - result.put("nickname", nickname); - result.put("image", profileImage); result.put("email", email); br.close(); @@ -157,6 +152,9 @@ public Map getUserInfo(String access_token) throws IOException { return result; } + + + /** * 사용자의 카카오 api 동의 내역을 확인하는 메서드. * kakao developers 공식문서 <- 참고 @@ -186,28 +184,10 @@ public String getAgreementInfo(String access_token) // result는 json 포멧. br.close(); - } catch (MalformedURLException e) { - e.printStackTrace(); - } catch (ProtocolException e) { - e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return result; } - /** - * kakaoId가 db에 있는지 확인해주는 메서드 - * @param kakaoId - * @return db에 존재 -> Member, 없으면 -> null - */ - public Member distinguishKakaoId(Long kakaoId) { - - if (!MemberRepository.findByKakaoId(kakaoId).isPresent()) { - return null; - } - Member kakaoMember = MemberRepository.findByKakaoId(kakaoId).get(); - - return kakaoMember; - } -} +} \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Member/Service/MemberService.java b/src/main/java/Remoa/BE/Member/Service/MemberService.java index d007af6..f629ee1 100644 --- a/src/main/java/Remoa/BE/Member/Service/MemberService.java +++ b/src/main/java/Remoa/BE/Member/Service/MemberService.java @@ -38,13 +38,10 @@ private void validateDuplicateMember(Member member) { } } + public Boolean isNicknameDuplicate(String nickname) { List findMembers = memberRepository.findByNickname(nickname); - if (findMembers.isEmpty()) { - return false; - } else { - return true; - } + return !(findMembers.size()==0); } diff --git a/src/main/java/Remoa/BE/Member/Service/MyFollowingService.java b/src/main/java/Remoa/BE/Member/Service/MyFollowingService.java new file mode 100644 index 0000000..ea749e0 --- /dev/null +++ b/src/main/java/Remoa/BE/Member/Service/MyFollowingService.java @@ -0,0 +1,80 @@ +package Remoa.BE.Member.Service; + +import Remoa.BE.Member.Domain.Member; +import Remoa.BE.Member.Dto.Res.ResMypageFollowing; +import Remoa.BE.Member.Dto.Res.ResMypageList; +import Remoa.BE.Member.Repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +@Service +@Slf4j +@RequiredArgsConstructor +public class MyFollowingService { + + private final MemberRepository memberRepository; + + public List findResMypageList(Member member, int isFollowing){ + List resMypageLists = new ArrayList<>(); + List memberList; + + if(isFollowing == 1) { // 마이페이지 팔로잉 관리 화면 + memberList = memberRepository.loadFollows(member); // member가 팔로우하는 유저 확인 + } else{ // 마이페이지 팔로워 관리 화면 + memberList = memberRepository.loadFollowers(member); + } + + for(int i=0; i followingMemberFollowing = memberRepository.loadFollows(followMember); + // followMember를 팔로우하는 유저 구하기(팔로워) + List followingMemberFollower = memberRepository.loadFollowers(followMember); + + ResMypageList resMypageList = ResMypageList.builder() + .profileImage(followMember.getProfileImage()) + .userName(followMember.getNickname()) + .followingNum(followingMemberFollowing.size()) + .followerNum(followingMemberFollower.size()) + .build(); + resMypageLists.add(resMypageList); + } + + return resMypageLists; + } + + /** + * 마이페이지 팔로잉 관리 화면에 사용 + * @param member + * @return ResMypageFollowing + */ + @Transactional + public ResMypageFollowing mypageFollowing(Member member){ + + return ResMypageFollowing.builder() + .userName(member.getNickname()) + .followNum(member.getFollows().size()) // 내가 팔로우하고 있는 유저 수 + .resMypageList(findResMypageList(member, 1)) + .build(); + } + + /** + * 마이페이지 팔로워 관리 화면에 사용 + * @param member + * @return ResMypageFollowing + */ + @Transactional + public ResMypageFollowing mypageFollower(Member member){ + + return ResMypageFollowing.builder() + .userName(member.getNickname()) + .followNum(memberRepository.loadFollowers(member).size()) // 나를 팔로우하고 있는 유저 수 + .resMypageList(findResMypageList(member, 0)) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Member/Service/ProfileService.java b/src/main/java/Remoa/BE/Member/Service/ProfileService.java index 5755031..8106df0 100644 --- a/src/main/java/Remoa/BE/Member/Service/ProfileService.java +++ b/src/main/java/Remoa/BE/Member/Service/ProfileService.java @@ -1,14 +1,29 @@ package Remoa.BE.Member.Service; import Remoa.BE.Member.Dto.Req.EditProfileForm; -import Remoa.BE.Member.Repository.MemberRepository; +import com.amazonaws.services.s3.AmazonS3; + +import com.amazonaws.services.s3.model.ObjectMetadata; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import Remoa.BE.Member.Domain.Member; +import org.springframework.util.FileCopyUtils; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.UUID; @Service @@ -18,20 +33,44 @@ public class ProfileService { private final MemberService memberService; - + @Value("${cloud.aws.s3.bucket}") + private String bucket; + private final AmazonS3 amazonS3; @Transactional public void editProfile(Long memberId, EditProfileForm profile) { Member member = memberService.findOne(memberId); - if (member == null) { - throw new IllegalArgumentException("Invalid member id"); - } - // 사용자의 프로필 정보 수정 member.setNickname(profile.getNickname()); member.setPhoneNumber(profile.getPhoneNumber()); member.setUniversity(profile.getUniversity()); member.setOneLineIntroduction(profile.getOneLineIntroduction()); } + + public String editProfileImg(String nickname,String profileImageUrl) throws IOException { + // 프로필사진을 jpg로 변환하기 + URL profileURL = new URL(profileImageUrl); + InputStream is = profileURL.openStream(); + + // 이미지 파일 생성 + BufferedImage image = ImageIO.read(is); + File outputFile = new File(nickname+".jpg"); + ImageIO.write(image, "jpg", outputFile); + + // 사진으로 바꾼뒤 바로 S3로 업로드하기 + String url = uploadProfileImg(outputFile); + outputFile.delete(); + return url; + } + + // 프로필 사진 초기설정 - S3에 저장하기 + public String uploadProfileImg(File file){ + String s3FileName = UUID.randomUUID() + "-" + file.getName(); + ObjectMetadata objMeta = new ObjectMetadata(); + objMeta.setContentLength(file.length()); + amazonS3.putObject(bucket, "img/"+s3FileName,file); + return amazonS3.getUrl(bucket,"img/"+s3FileName).toString().replaceAll("\\+", "+"); + + } } \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Notice/Controller/InquiryController.java b/src/main/java/Remoa/BE/Notice/Controller/InquiryController.java new file mode 100644 index 0000000..4dd3e4e --- /dev/null +++ b/src/main/java/Remoa/BE/Notice/Controller/InquiryController.java @@ -0,0 +1,69 @@ +package Remoa.BE.Notice.Controller; + +import Remoa.BE.Notice.Dto.Req.ReqNoticeDto; +import Remoa.BE.Notice.Dto.Res.ResNoticeDto; +import Remoa.BE.Notice.Service.InquiryService; +import Remoa.BE.Notice.domain.Inquiry; +import Remoa.BE.Notice.domain.Notice; +import Remoa.BE.exception.CustomMessage; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static Remoa.BE.exception.CustomBody.errorResponse; +import static Remoa.BE.exception.CustomBody.successResponse; + +@RestController +@RequiredArgsConstructor +public class InquiryController { + + private final InquiryService inquiryService; + + @PostMapping("/inquiry") + public ResponseEntity postInquiry(@Validated @RequestBody ReqNoticeDto reqNoticeDto){ + inquiryService.registerInquiry(reqNoticeDto); + return new ResponseEntity<>(HttpStatus.OK); + } + + @GetMapping("/inquiry") + public ResponseEntity getInquiry(@RequestParam(required = false, defaultValue = "1", name = "page") int pageNumber){ + pageNumber -= 1; + if (pageNumber < 0) { + return errorResponse(CustomMessage.PAGE_NUM_OVER); + } + Page inquiries =inquiryService.getInquiry(pageNumber); + + if ((inquiries.getContent().isEmpty()) && (inquiries.getTotalElements() > 0)) { + return errorResponse(CustomMessage.PAGE_NUM_OVER); + } + + Map responseData = new HashMap<>(); + + List result = new ArrayList<>(); + + for(Inquiry inquiry:inquiries) { + ResNoticeDto resNoticeDto = ResNoticeDto.builder() + .noticeId(inquiry.getInquiryId()) + .title(inquiry.getTitle()) + .postingTime(inquiry.getPostingTime().toLocalDate()) + .view(10) + .build(); + result.add(resNoticeDto); + } + + responseData.put("inquiries", result); //조회한 레퍼런스들 + responseData.put("totalPages", inquiries.getTotalPages()); //전체 페이지의 수 + responseData.put("totalOfAllNotices", inquiries.getTotalElements()); //모든 레퍼런스의 수 + responseData.put("totalOfPageElements", inquiries.getNumberOfElements()); //현 페이지의 레퍼런스 수 + + return successResponse(CustomMessage.OK, responseData); + } +} \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Notice/Controller/NoticeController.java b/src/main/java/Remoa/BE/Notice/Controller/NoticeController.java new file mode 100644 index 0000000..ac1f657 --- /dev/null +++ b/src/main/java/Remoa/BE/Notice/Controller/NoticeController.java @@ -0,0 +1,68 @@ +package Remoa.BE.Notice.Controller; + +import Remoa.BE.Notice.Dto.Req.ReqNoticeDto; +import Remoa.BE.Notice.Dto.Res.ResNoticeDto; +import Remoa.BE.Notice.Service.NoticeService; +import Remoa.BE.Notice.domain.Notice; +import Remoa.BE.exception.CustomMessage; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static Remoa.BE.exception.CustomBody.errorResponse; +import static Remoa.BE.exception.CustomBody.successResponse; + +@RestController +@RequiredArgsConstructor +public class NoticeController { + + private final NoticeService noticeService; + + @PostMapping("/notice") + public ResponseEntity postNotice(@Validated @RequestBody ReqNoticeDto reqNoticeDto){ + noticeService.registerNotice(reqNoticeDto); + return new ResponseEntity<>(HttpStatus.OK); + } + + @GetMapping("/notice") + public ResponseEntity getNotice(@RequestParam(required = false, defaultValue = "1", name = "page") int pageNumber){ + pageNumber -= 1; + if (pageNumber < 0) { + return errorResponse(CustomMessage.PAGE_NUM_OVER); + } + Page notices =noticeService.getNotice(pageNumber); + + if ((notices.getContent().isEmpty()) && (notices.getTotalElements() > 0)) { + return errorResponse(CustomMessage.PAGE_NUM_OVER); + } + Map responseData = new HashMap<>(); + + List result = new ArrayList<>(); + + for(Notice notice:notices) { + ResNoticeDto resNoticeDto = ResNoticeDto.builder() + .noticeId(notice.getNoticeId()) + .title(notice.getTitle()) + .postingTime(notice.getPostingTime().toLocalDate()) + .view(10) + .build(); + result.add(resNoticeDto); + } + + responseData.put("notices", result); //조회한 레퍼런스들 + responseData.put("totalPages", notices.getTotalPages()); //전체 페이지의 수 + responseData.put("totalOfAllNotices", notices.getTotalElements()); //모든 레퍼런스의 수 + responseData.put("totalOfPageElements", notices.getNumberOfElements()); //현 페이지의 레퍼런스 수 + + return successResponse(CustomMessage.OK, responseData); + } + +} diff --git a/src/main/java/Remoa/BE/Notice/Dto/Req/ReqNoticeDto.java b/src/main/java/Remoa/BE/Notice/Dto/Req/ReqNoticeDto.java new file mode 100644 index 0000000..254be51 --- /dev/null +++ b/src/main/java/Remoa/BE/Notice/Dto/Req/ReqNoticeDto.java @@ -0,0 +1,15 @@ +package Remoa.BE.Notice.Dto.Req; + +import lombok.Data; + +import javax.validation.constraints.NotNull; + +@Data +public class ReqNoticeDto { + + @NotNull + private String title; + + @NotNull + private String content; +} diff --git a/src/main/java/Remoa/BE/Notice/Dto/Res/ResNoticeDto.java b/src/main/java/Remoa/BE/Notice/Dto/Res/ResNoticeDto.java new file mode 100644 index 0000000..4ebc7ea --- /dev/null +++ b/src/main/java/Remoa/BE/Notice/Dto/Res/ResNoticeDto.java @@ -0,0 +1,17 @@ +package Remoa.BE.Notice.Dto.Res; + +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Builder +@Data +public class ResNoticeDto { + + private Long noticeId; + private String title; + private LocalDate postingTime; + private Integer view ; +} diff --git a/src/main/java/Remoa/BE/Notice/Repository/InquiryRepository.java b/src/main/java/Remoa/BE/Notice/Repository/InquiryRepository.java new file mode 100644 index 0000000..786096f --- /dev/null +++ b/src/main/java/Remoa/BE/Notice/Repository/InquiryRepository.java @@ -0,0 +1,12 @@ +package Remoa.BE.Notice.Repository; + +import Remoa.BE.Notice.domain.Inquiry; +import Remoa.BE.Notice.domain.Notice; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface InquiryRepository extends JpaRepository { +} diff --git a/src/main/java/Remoa/BE/Notice/Repository/NoticeRepository.java b/src/main/java/Remoa/BE/Notice/Repository/NoticeRepository.java new file mode 100644 index 0000000..c7a4975 --- /dev/null +++ b/src/main/java/Remoa/BE/Notice/Repository/NoticeRepository.java @@ -0,0 +1,14 @@ +package Remoa.BE.Notice.Repository; + +import Remoa.BE.Member.Domain.Comment; +import Remoa.BE.Notice.domain.Notice; +import Remoa.BE.Post.Domain.Post; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface NoticeRepository extends JpaRepository { + +} diff --git a/src/main/java/Remoa/BE/Notice/Service/InquiryService.java b/src/main/java/Remoa/BE/Notice/Service/InquiryService.java new file mode 100644 index 0000000..cbfe498 --- /dev/null +++ b/src/main/java/Remoa/BE/Notice/Service/InquiryService.java @@ -0,0 +1,38 @@ +package Remoa.BE.Notice.Service; + +import Remoa.BE.Notice.Dto.Req.ReqNoticeDto; +import Remoa.BE.Notice.Repository.InquiryRepository; +import Remoa.BE.Notice.domain.Inquiry; +import Remoa.BE.Notice.domain.Notice; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class InquiryService { + + private final InquiryRepository inquiryRepository; + + @Transactional + public void registerInquiry(ReqNoticeDto reqNoticeDto){ + Inquiry inquiry = new Inquiry(); + inquiry.setTitle(reqNoticeDto.getTitle()); + inquiry.setContent(reqNoticeDto.getContent()); + inquiry.setPostingTime(LocalDateTime.now()); + + inquiryRepository.save(inquiry); + } + + public Page getInquiry(int pageNumber){ + int NOTICE_NUMBER = 5; + Pageable pageable = PageRequest.of(pageNumber, NOTICE_NUMBER); + return inquiryRepository.findAll(pageable); + } +} diff --git a/src/main/java/Remoa/BE/Notice/Service/NoticeService.java b/src/main/java/Remoa/BE/Notice/Service/NoticeService.java new file mode 100644 index 0000000..b092f25 --- /dev/null +++ b/src/main/java/Remoa/BE/Notice/Service/NoticeService.java @@ -0,0 +1,37 @@ +package Remoa.BE.Notice.Service; + +import Remoa.BE.Notice.Dto.Req.ReqNoticeDto; +import Remoa.BE.Notice.Repository.NoticeRepository; +import Remoa.BE.Notice.domain.Notice; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class NoticeService { + + private final NoticeRepository noticeRepository; + + @Transactional + public void registerNotice(ReqNoticeDto reqNoticeDto){ + Notice notice = new Notice(); + notice.setTitle(reqNoticeDto.getTitle()); + notice.setContent(reqNoticeDto.getContent()); + notice.setPostingTime(LocalDateTime.now()); + + noticeRepository.save(notice); + } + + public Page getNotice(int pageNumber){ + int NOTICE_NUMBER = 5; + Pageable pageable = PageRequest.of(pageNumber, NOTICE_NUMBER); + return noticeRepository.findAll(pageable); + } +} diff --git a/src/main/java/Remoa/BE/Notice/domain/Inquiry.java b/src/main/java/Remoa/BE/Notice/domain/Inquiry.java new file mode 100644 index 0000000..43fcad0 --- /dev/null +++ b/src/main/java/Remoa/BE/Notice/domain/Inquiry.java @@ -0,0 +1,28 @@ +package Remoa.BE.Notice.domain; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import java.time.LocalDateTime; + +@Entity +@Getter +@Setter +@NoArgsConstructor +public class Inquiry { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long inquiryId; + + private String title; + + private String content; + + private LocalDateTime postingTime; + +} diff --git a/src/main/java/Remoa/BE/Notice/domain/Notice.java b/src/main/java/Remoa/BE/Notice/domain/Notice.java new file mode 100644 index 0000000..529cd33 --- /dev/null +++ b/src/main/java/Remoa/BE/Notice/domain/Notice.java @@ -0,0 +1,30 @@ +package Remoa.BE.Notice.domain; + +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import java.time.LocalDateTime; + +@Entity +@Getter +@Setter +@NoArgsConstructor +public class Notice { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long noticeId; + + private String title; + + private String content; + + private LocalDateTime postingTime; + +} diff --git a/src/main/java/Remoa/BE/Post/Controller/CommentController.java b/src/main/java/Remoa/BE/Post/Controller/CommentController.java new file mode 100644 index 0000000..379f1ca --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Controller/CommentController.java @@ -0,0 +1,87 @@ +package Remoa.BE.Post.Controller; + +import Remoa.BE.Member.Domain.Comment; +import Remoa.BE.Member.Domain.Member; +import Remoa.BE.Member.Service.MemberService; +import Remoa.BE.Post.Service.CommentService; +import Remoa.BE.exception.CustomMessage; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collections; +import java.util.Map; + +import static Remoa.BE.exception.CustomBody.errorResponse; +import static Remoa.BE.exception.CustomBody.successResponse; +import static Remoa.BE.utill.MemberInfo.authorized; +import static Remoa.BE.utill.MemberInfo.getMemberId; + +@Slf4j +@RestController +@RequiredArgsConstructor +public class CommentController { + + private final MemberService memberService; + + private final CommentService commentService; + + @PostMapping("/reference/{reference_id}/comment") + public ResponseEntity registerComment(@RequestBody Map comment, @PathVariable("reference_id") Long postId, HttpServletRequest request){ + String myComment = comment.get("comment"); + if(authorized(request)){ + Long memberId = getMemberId(); + Member myMember = memberService.findOne(memberId); + commentService.registerComment(myMember, myComment, postId, null); + return new ResponseEntity<>(HttpStatus.OK); + } + return errorResponse(CustomMessage.UNAUTHORIZED); + } + + @PostMapping("/reference/{reference_id}/comment/{comment_id}") + public ResponseEntity registerCommentReply(@RequestBody Map comment, @PathVariable("reference_id") Long postId, @PathVariable("comment_id") Long commentId, HttpServletRequest request){ + String myComment = comment.get("comment"); + if(authorized(request)){ + Long memberId = getMemberId(); + Member myMember = memberService.findOne(memberId); + commentService.registerComment(myMember, myComment, postId, commentId); + return new ResponseEntity<>(HttpStatus.OK); + } + return errorResponse(CustomMessage.UNAUTHORIZED); + } + @PutMapping("/reference/comment/{comment_id}") + public ResponseEntity modifyComment(@RequestBody Map comment, @PathVariable("comment_id") Long commentId, HttpServletRequest request){ + String myComment = comment.get("comment"); + + if(authorized(request)){ + commentService.modifyComment(myComment, commentId); + return new ResponseEntity<>(HttpStatus.OK); + } + return errorResponse(CustomMessage.UNAUTHORIZED); + } + + @DeleteMapping("/comment/{comment_id}") + public ResponseEntity deleteComment(@PathVariable("comment_id") Long commentId, HttpServletRequest request){ + if(authorized(request)){ + + commentService.deleteComment(commentId); + return new ResponseEntity<>(HttpStatus.OK); + } + return errorResponse(CustomMessage.UNAUTHORIZED); + } + + @PostMapping("/comment/{comment_id}/like") // 댓글 좋아요 + public ResponseEntity likeComment(@PathVariable("comment_id") Long commentId, HttpServletRequest request){ + if(authorized(request)){ + Long memberId = getMemberId(); + commentService.likeComment(memberId, commentId); + int count = commentService.commentLikeCount(commentId); + Map map = Collections.singletonMap("LikeCount", count); + return successResponse(CustomMessage.OK,map); + } + return errorResponse(CustomMessage.UNAUTHORIZED); + } +} \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Post/Controller/FeedbackController.java b/src/main/java/Remoa/BE/Post/Controller/FeedbackController.java new file mode 100644 index 0000000..a6b6e7e --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Controller/FeedbackController.java @@ -0,0 +1,103 @@ +package Remoa.BE.Post.Controller; + +import Remoa.BE.Member.Domain.Member; +import Remoa.BE.Member.Service.MemberService; +import Remoa.BE.Post.Domain.Post; +import Remoa.BE.Post.Service.FeedbackService; +import Remoa.BE.Post.Service.PostService; +import Remoa.BE.exception.CustomMessage; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collections; +import java.util.Map; + +import static Remoa.BE.exception.CustomBody.errorResponse; +import static Remoa.BE.exception.CustomBody.successResponse; +import static Remoa.BE.utill.MemberInfo.authorized; +import static Remoa.BE.utill.MemberInfo.getMemberId; + +@Slf4j +@RestController +@RequiredArgsConstructor +public class FeedbackController { + + private final FeedbackService feedbackService; + private final MemberService memberService; + private final PostService postService; + + @PostMapping("/reference/{reference_id}/{page_number}") // 레퍼런스에 피드백 등록 + public ResponseEntity registerFeedback(@RequestBody Map feedback, + @PathVariable("reference_id") Long postId, + @PathVariable("page_number") Integer pageNumber, + HttpServletRequest request){ + String myFeedback = feedback.get("feedback"); + if(authorized(request)){ + Long memberId = getMemberId(); + Member myMember = memberService.findOne(memberId); + Post post = postService.findOne(postId); + // 피드백을 등록하려는 게시물의 페이지 수가 없을 경우 예외 처리 + if(post.getPageCount() < pageNumber || pageNumber < 1){ + return errorResponse(CustomMessage.BAD_PAGE_NUM); + } + + feedbackService.registerFeedback(myMember, myFeedback, postId, pageNumber, null); + return new ResponseEntity<>(HttpStatus.OK); + } + return errorResponse(CustomMessage.UNAUTHORIZED); + } + + @PostMapping("/reference/{reference_id}/feedback/{feedback_id}") // 레퍼런스에 피드백 대댓글 등록 + public ResponseEntity registerFeedbackReply(@RequestBody Map feedback, + @PathVariable("reference_id") Long postId, + @PathVariable("feedback_id") Long feedbackId, + HttpServletRequest request){ + String myFeedback = feedback.get("feedback"); + if(authorized(request)){ + Long memberId = getMemberId(); + Member myMember = memberService.findOne(memberId); + feedbackService.registerFeedback(myMember, myFeedback, postId, null, feedbackId); + return new ResponseEntity<>(HttpStatus.OK); + } + return errorResponse(CustomMessage.UNAUTHORIZED); + } + + @PutMapping("/reference/feedback/{feedback_id}") // 피드백 수정 + public ResponseEntity modifyFeedback(@RequestBody Map feedback, @PathVariable("feedback_id") Long feedbackId, HttpServletRequest request){ + String myFeedback = feedback.get("feedback"); + + if(authorized(request)){ + feedbackService.modifyFeedback(myFeedback, feedbackId); + return new ResponseEntity<>(HttpStatus.OK); + } + return errorResponse(CustomMessage.UNAUTHORIZED); + } + + @DeleteMapping("/reference/feedback/{feedback_id}") + public ResponseEntity deleteFeedback(@PathVariable("feedback_id") Long feedbackId, HttpServletRequest request){ + if(authorized(request)){ + + feedbackService.deleteFeedback(feedbackId); + return new ResponseEntity<>(HttpStatus.OK); + } + return errorResponse(CustomMessage.UNAUTHORIZED); + } + + @PostMapping("/reference/feedback/{feedback_id}/like") // 피드백 좋아요 + public ResponseEntity likeFeedback(@PathVariable("feedback_id") Long feedbackId, HttpServletRequest request){ + if(authorized(request)){ + Long memberId = getMemberId(); + Member myMember = memberService.findOne(memberId); + feedbackService.likeFeedback(memberId, myMember, feedbackId); + int count = feedbackService.feedbackLikeCount(feedbackId); + Map map = Collections.singletonMap("LikeCount", count); + return successResponse(CustomMessage.OK,map); + } + return errorResponse(CustomMessage.UNAUTHORIZED); + + } +} \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Post/Controller/MyActivityController.java b/src/main/java/Remoa/BE/Post/Controller/MyActivityController.java new file mode 100644 index 0000000..b0e3c6d --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Controller/MyActivityController.java @@ -0,0 +1,225 @@ +package Remoa.BE.Post.Controller; + +import Remoa.BE.Member.Domain.CommentFeedback; +import Remoa.BE.Member.Domain.Member; +import Remoa.BE.Member.Dto.Res.ResMemberInfoDto; +import Remoa.BE.Member.Service.MemberService; +import Remoa.BE.Post.Domain.PostScarp; +import Remoa.BE.Post.Dto.Response.ResCommentFeedbackDto; +import Remoa.BE.Post.Dto.Response.ResPostDto; +import Remoa.BE.Post.Service.CommentFeedbackService; +import Remoa.BE.Post.Service.PostService; +import Remoa.BE.exception.CustomMessage; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static Remoa.BE.Member.Domain.ContentType.COMMENT; +import static Remoa.BE.Member.Domain.ContentType.FEEDBACK; +import static Remoa.BE.exception.CustomBody.errorResponse; +import static Remoa.BE.exception.CustomBody.successResponse; +import static Remoa.BE.utill.MemberInfo.authorized; +import static Remoa.BE.utill.MemberInfo.getMemberId; + +@RestController +@Slf4j +@RequiredArgsConstructor +public class MyActivityController { + + private final MemberService memberService; + private final CommentFeedbackService commentFeedbackService; + private final PostService postService; + + /** + * 내 활동 관리 + * @param request + * @return Map + * "contents" : 내가 작성한 최신 댓글(Comment, Feedback 무관 1개) + * "posts" : 내가 스크랩한 post들을 가장 최근 스크랩한 순서의 List(12개). + */ + @GetMapping("/user/activity") + public ResponseEntity myActivity(HttpServletRequest request) { + + if (authorized(request)) { + Long memberId = getMemberId(); + Member myMember = memberService.findOne(memberId); + + Map result = new HashMap<>(); + + CommentFeedback commentFeedback = commentFeedbackService.findNewestCommentFeedback(myMember); + + ResCommentFeedbackDto commentOrFeedback = null; + if (commentFeedback != null && commentFeedback.getType().equals(FEEDBACK)) { + commentOrFeedback = ResCommentFeedbackDto.builder() + .title(commentFeedback.getPost().getTitle()) + .postId(commentFeedback.getPost().getPostId()) + .thumbnail(commentFeedback.getPost().getThumbnail().getStoreFileUrl()) + .member(new ResMemberInfoDto(commentFeedback.getMember().getMemberId(), + commentFeedback.getMember().getNickname(), + commentFeedback.getMember().getProfileImage())) + .content(commentFeedback.getFeedback().getFeedback()) + .likeCount(commentFeedback.getFeedback().getFeedbackLikeCount()) + .build(); + } else if (commentFeedback != null && commentFeedback.getType().equals(COMMENT)) { + commentOrFeedback = ResCommentFeedbackDto.builder() + .title(commentFeedback.getPost().getTitle()) + .postId(commentFeedback.getPost().getPostId()) + .thumbnail(commentFeedback.getPost().getThumbnail().getStoreFileUrl()) + .member(new ResMemberInfoDto(commentFeedback.getMember().getMemberId(), + commentFeedback.getMember().getNickname(), + commentFeedback.getMember().getProfileImage())) + .content(commentFeedback.getComment().getComment()) + .likeCount(commentFeedback.getComment().getCommentLikeCount()) + .build(); + } + result.put("content", commentOrFeedback); + + + /** + * 조회한 최근에 스크랩한 12개의 post들을 dto로 mapping. + */ + List posts = postService.findRecentTwelveScrapedPost(myMember).stream() + .map(post -> ResPostDto.builder() + .postId(post.getPostId()) + .postMember(new ResMemberInfoDto(post.getMember().getMemberId(), + post.getMember().getNickname(), + post.getMember().getProfileImage())) + .thumbnail(post.getThumbnail().getStoreFileUrl()) + .title(post.getTitle()) + .likeCount(post.getLikeCount()) + .postingTime(post.getPostingTime().toString()) + .views(post.getViews()) + .scrapCount(post.getScrapCount()) + .categoryName(post.getCategory().getName()) + .build()).collect(Collectors.toList()); + + result.put("posts", posts); + + return successResponse(CustomMessage.OK, result); + } + return errorResponse(CustomMessage.UNAUTHORIZED); + } + + @GetMapping("/user/scrap") // 내가 스크랩한 게시글 확인 + public ResponseEntity myScrap(HttpServletRequest request, + @RequestParam(name = "page", defaultValue = "1", required = false) int pageNum){ + + if (authorized(request)) { + Long memberId = getMemberId(); + Member myMember = memberService.findOne(memberId); + + Map result = new HashMap<>(); + + pageNum -= 1; + if (pageNum < 0) { + return errorResponse(CustomMessage.PAGE_NUM_OVER); + } + + + /** + * 조회한 최근에 스크랩한 12개의 post들을 dto로 mapping. + */ + Page posts = postService.findScrapedPost(pageNum, myMember); + + //조회할 레퍼런스가 db에 있으나, 현재 페이지에 조회할 데이터가 없는 경우 == 페이지 번호를 잘못 입력 + if ((posts.getContent().isEmpty()) && (posts.getTotalElements() > 0)) { + return errorResponse(CustomMessage.PAGE_NUM_OVER); + } + + result.put("posts", posts.stream() + .map(PostScarp::getPost) + .collect(Collectors.toList()) + .stream() + .map(post -> ResPostDto.builder() + .postId(post.getPostId()) + .postMember(new ResMemberInfoDto(post.getMember().getMemberId(), + post.getMember().getNickname(), + post.getMember().getProfileImage())) + .thumbnail(post.getThumbnail().getStoreFileUrl()) + .title(post.getTitle()) + .likeCount(post.getLikeCount()) + .postingTime(post.getPostingTime().toString()) + .views(post.getViews()) + .scrapCount(post.getScrapCount()) + .categoryName(post.getCategory().getName()).build()) + .collect(Collectors.toList())); + result.put("totalPages", posts.getTotalPages()); //전체 페이지의 수 + result.put("totalOfAllComments", posts.getTotalElements()); //모든 코멘트의 수 + result.put("totalOfPageElements", posts.getNumberOfElements()); //현 페이지 피드백의 수 + + return successResponse(CustomMessage.OK, result); + } + return errorResponse(CustomMessage.UNAUTHORIZED); + } + + @GetMapping("/user/comment") + public ResponseEntity myComment(HttpServletRequest request, + @RequestParam(name = "page", defaultValue = "1", required = false) int pageNum){ + if (authorized(request)) { + Long memberId = getMemberId(); + Member myMember = memberService.findOne(memberId); + + pageNum -= 1; + if (pageNum < 0) { + return errorResponse(CustomMessage.PAGE_NUM_OVER); + } + + Map result = new HashMap<>(); + + Page commentOrFeedback = commentFeedbackService.findNewestCommentOrFeedback(pageNum, myMember); + + //조회할 레퍼런스가 db에 있으나, 현재 페이지에 조회할 데이터가 없는 경우 == 페이지 번호를 잘못 입력 + if ((commentOrFeedback.getContent().isEmpty()) && (commentOrFeedback.getTotalElements() > 0)) { + return errorResponse(CustomMessage.PAGE_NUM_OVER); + } + + /** + * 조회한 가장 최근에 작성한 댓글들을 dto로 mapping + */ + List contents = commentOrFeedback.stream().map(commentFeedback -> { + ResCommentFeedbackDto map = null; + if (commentFeedback.getType().equals(FEEDBACK)) { + map = ResCommentFeedbackDto.builder() + .title(commentFeedback.getPost().getTitle()) + .postId(commentFeedback.getPost().getPostId()) + .thumbnail(commentFeedback.getPost().getThumbnail().getStoreFileUrl()) + .member(new ResMemberInfoDto(commentFeedback.getMember().getMemberId(), + commentFeedback.getMember().getNickname(), + commentFeedback.getMember().getProfileImage())) + .content(commentFeedback.getFeedback().getFeedback()) + .likeCount(commentFeedback.getFeedback().getFeedbackLikeCount()).build(); + } else if (commentFeedback.getType().equals(COMMENT)) { + map = ResCommentFeedbackDto.builder() + .title(commentFeedback.getPost().getTitle()) + .postId(commentFeedback.getPost().getPostId()) + .thumbnail(commentFeedback.getPost().getThumbnail().getStoreFileUrl()) + .member(new ResMemberInfoDto(commentFeedback.getMember().getMemberId(), + commentFeedback.getMember().getNickname(), + commentFeedback.getMember().getProfileImage())) + .content(commentFeedback.getComment().getComment()) + .likeCount(commentFeedback.getComment().getCommentLikeCount()).build(); + } + return map; + }).collect(Collectors.toList()); + + result.put("contents", contents); + result.put("totalPages", commentOrFeedback.getTotalPages()); //전체 페이지의 수 + result.put("totalOfAllComments", commentOrFeedback.getTotalElements()); //모든 코멘트의 수 + result.put("totalOfPageElements", commentOrFeedback.getNumberOfElements()); //현 페이지 피드백의 수 + + return successResponse(CustomMessage.OK, result); + } + return errorResponse(CustomMessage.UNAUTHORIZED); + } +} diff --git a/src/main/java/Remoa/BE/Post/Controller/MyFeedbackController.java b/src/main/java/Remoa/BE/Post/Controller/MyFeedbackController.java new file mode 100644 index 0000000..a18b6d5 --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Controller/MyFeedbackController.java @@ -0,0 +1,127 @@ +package Remoa.BE.Post.Controller; + +import Remoa.BE.Member.Domain.Comment; +import Remoa.BE.Member.Domain.Member; +import Remoa.BE.Member.Dto.Res.ResMemberInfoDto; +import Remoa.BE.Member.Service.MemberService; +import Remoa.BE.Post.Domain.Post; +import Remoa.BE.Post.Dto.Response.ResCommentDto; +import Remoa.BE.Post.Dto.Response.ResReceivedCommentDto; +import Remoa.BE.Post.Dto.Response.ResReplyDto; +import Remoa.BE.Post.Service.CommentService; +import Remoa.BE.Post.Service.MyPostService; +import Remoa.BE.exception.CustomMessage; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static Remoa.BE.exception.CustomBody.errorResponse; +import static Remoa.BE.exception.CustomBody.successResponse; +import static Remoa.BE.utill.MemberInfo.authorized; +import static Remoa.BE.utill.MemberInfo.getMemberId; + +@RestController +@RequiredArgsConstructor +@Slf4j +public class MyFeedbackController { + + private final MemberService memberService; + private final MyPostService myPostService; + private final CommentService commentService; + + @GetMapping("/user/feedback") + public ResponseEntity receivedFeedback(HttpServletRequest request, + @RequestParam(required = false, defaultValue = "all") String category, + @RequestParam(required = false, defaultValue = "1", name = "page") int pageNumber) { + + if (authorized(request)) { + Long memberId = getMemberId(); + Member myMember = memberService.findOne(memberId); + + pageNumber -= 1; + if (pageNumber < 0) { + return errorResponse(CustomMessage.PAGE_NUM_OVER); + } + + Page posts = myPostService.getNewestThreePosts(pageNumber, myMember); + + if ((posts.getContent().isEmpty()) && (posts.getTotalElements() > 0)) { + return errorResponse(CustomMessage.PAGE_NUM_OVER); + } + + Map result = new HashMap<>(); + List res = new ArrayList<>(); + + for (Post post : posts) { //조회한 post + Map commentInfo = new HashMap<>(); + + List parentComments = commentService.getRecentThreeCommentsExceptReply(post); + + int commentNumber = 1; + for (Comment parentComment : parentComments) { //조회한 post의 parent comment + List parentCommentsReply = commentService.getParentCommentsReply(parentComment); + + + + List replies = new ArrayList<>(); + for (Comment reply : parentCommentsReply) { + + replies.add(new ResReplyDto( + reply.getCommentId(), + new ResMemberInfoDto(reply.getMember().getMemberId(), + reply.getMember().getNickname(), + reply.getMember().getProfileImage()), + reply.getComment(), + reply.getCommentLikeCount(), + commentService.findCommentLike(myMember.getMemberId(), reply.getCommentId()).isPresent(), + reply.getCommentedTime())); + } + + commentInfo.put("comment_" + commentNumber, new ResCommentDto( + parentComment.getCommentId(), + new ResMemberInfoDto(parentComment.getMember().getMemberId(), + parentComment.getMember().getNickname(), + parentComment.getMember().getProfileImage()), + parentComment.getComment(), + parentComment.getCommentLikeCount(), + commentService.findCommentLike(myMember.getMemberId(), + parentComment.getCommentId()).isPresent(), + parentComment.getCommentedTime(), + replies)); + + commentNumber++; + } + + ResReceivedCommentDto map = ResReceivedCommentDto.builder() + .title(post.getTitle()) + .thumbnail(post.getThumbnail().getStoreFileUrl()) + .postId(post.getPostId()) + .commentInfo(commentInfo) + .build(); + + res.add(map); + + } + result.put("post",res); + result.put("totalPages", posts.getTotalPages()); //전체 페이지의 수 + result.put("totalOfAllComments", posts.getTotalElements()); //모든 코멘트의 수 + result.put("totalOfPageElements", posts.getNumberOfElements()); //현 페이지 피드백의 수 + + return successResponse(CustomMessage.OK, result); + + } + return errorResponse(CustomMessage.UNAUTHORIZED); + } + +} diff --git a/src/main/java/Remoa/BE/Post/Controller/MyPostController.java b/src/main/java/Remoa/BE/Post/Controller/MyPostController.java new file mode 100644 index 0000000..7daf9c7 --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Controller/MyPostController.java @@ -0,0 +1,110 @@ +package Remoa.BE.Post.Controller; + +import Remoa.BE.Member.Domain.Member; +import Remoa.BE.Member.Dto.Res.ResMemberInfoDto; +import Remoa.BE.Member.Service.MemberService; +import Remoa.BE.Post.Domain.Post; +import Remoa.BE.Post.Dto.Response.ResPostDto; +import Remoa.BE.Post.Service.CommentService; +import Remoa.BE.Post.Service.MyPostService; +import Remoa.BE.exception.CustomMessage; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.modelmapper.ModelMapper; +import org.springframework.data.domain.Page; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static Remoa.BE.exception.CustomBody.errorResponse; +import static Remoa.BE.exception.CustomBody.successResponse; +import static Remoa.BE.utill.MemberInfo.authorized; +import static Remoa.BE.utill.MemberInfo.getMemberId; + +@Slf4j +@RestController +@RequiredArgsConstructor +public class MyPostController { + + private final MemberService memberService; + private final MyPostService myPostService; + + // Entity <-> DTO 간의 변환을 편리하게 하고자 ModelMapper 사용.(build.gradle에 의존성 주입 완료) +// private final ModelMapper modelMapper = new ModelMapper(); + + + /** + * 내 작업물 목록 페이지 + */ + @GetMapping("/user/reference") + public ResponseEntity myReference(HttpServletRequest request, + @RequestParam(required = false, defaultValue = "all") String category, + @RequestParam(required = false, defaultValue = "1", name = "page") int pageNumber, + @RequestParam(required = false, defaultValue = "newest") String sort, + @RequestParam(required = false, defaultValue = "") String title) { + if (authorized(request)) { + Long memberId = getMemberId(); + Member myMember = memberService.findOne(memberId); + + pageNumber -= 1; + if (pageNumber < 0) { + return errorResponse(CustomMessage.PAGE_NUM_OVER); + } + + Page posts; + + if (category.equals("idea") || + category.equals("marketing") || + category.equals("design") || + category.equals("video") || + category.equals("etc")) { + posts = myPostService.sortAndPaginatePostsByCategoryAndMember(category, pageNumber, sort, myMember,title); + } else { + posts = myPostService.sortAndPaginatePostsByMember(pageNumber, sort, myMember,title); + } + + //조회할 레퍼런스가 db에 있으나, 현재 페이지에 조회할 데이터가 없는 경우 == 페이지 번호를 잘못 입력 + if ((posts.getContent().isEmpty()) && (posts.getTotalElements() > 0)) { + return errorResponse(CustomMessage.PAGE_NUM_OVER); + } + + List result = new ArrayList<>(); + + for (Post post : posts) { + ResPostDto map = ResPostDto.builder() + .postingTime(post.getPostingTime().toString()) + .postMember(new ResMemberInfoDto(post.getMember().getMemberId(), + post.getMember().getNickname(), + post.getMember().getProfileImage())) + .postId(post.getPostId()) + .views(post.getViews()) + .categoryName(post.getCategory().getName()) + .likeCount(post.getLikeCount()) + .thumbnail(post.getThumbnail().getStoreFileUrl()) + .scrapCount(post.getScrapCount()) + .title(post.getTitle()).build(); + result.add(map); + } + //프론트에서 쓰일 조회한 레퍼런스들과 페이지 관련한 값들 map에 담아서 return. + Map referencesAndPageInfo = new HashMap<>(); + referencesAndPageInfo.put("references", result); //조회한 레퍼런스들 + referencesAndPageInfo.put("totalPages", posts.getTotalPages()); //전체 페이지의 수 + referencesAndPageInfo.put("totalOfAllReferences", posts.getTotalElements()); //모든 레퍼런스의 수 + referencesAndPageInfo.put("totalOfPageElements", posts.getNumberOfElements()); //현 페이지의 레퍼런스 수 + + return successResponse(CustomMessage.OK, referencesAndPageInfo); + } + return errorResponse(CustomMessage.UNAUTHORIZED); + } + + + + +} diff --git a/src/main/java/Remoa/BE/Post/Controller/PostController.java b/src/main/java/Remoa/BE/Post/Controller/PostController.java index 8d81335..ccd2354 100644 --- a/src/main/java/Remoa/BE/Post/Controller/PostController.java +++ b/src/main/java/Remoa/BE/Post/Controller/PostController.java @@ -1,23 +1,27 @@ package Remoa.BE.Post.Controller; import Remoa.BE.Member.Domain.Member; +import Remoa.BE.Member.Dto.Res.ResMemberInfoDto; import Remoa.BE.Member.Service.MemberService; import Remoa.BE.Post.Domain.Post; -import Remoa.BE.Post.Service.FileService; +import Remoa.BE.Post.Domain.UploadFile; +import Remoa.BE.Post.Dto.Request.UploadPostForm; +import Remoa.BE.Post.Dto.Response.ResHomeReferenceDto; +import Remoa.BE.Post.Dto.Response.ResReferenceRegisterDto; import Remoa.BE.Post.Service.PostService; -import Remoa.BE.Post.form.Request.UploadPostForm; -import Remoa.BE.exception.CustomBody; import Remoa.BE.exception.CustomMessage; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpSession; import java.io.IOException; -import java.util.List; +import java.util.*; +import java.util.stream.Collectors; import static Remoa.BE.exception.CustomBody.errorResponse; import static Remoa.BE.exception.CustomBody.successResponse; @@ -27,51 +31,142 @@ @Slf4j @RestController @RequiredArgsConstructor -@CrossOrigin(origins = "*") public class PostController { - private final FileService fileService; - private final PostService postService; - private final MemberService memberService; +// private final ModelMapper modelMapper; - //Todo 게시글 작성중 파일 업로드만 작성 - @PostMapping("/post") - public void posting(Post post, @RequestParam("files") List multipartFile){ - fileService.saveUploadFiles(post,multipartFile); - } + @GetMapping("/reference") + public ResponseEntity searchPost(@RequestParam(required = false, defaultValue = "all") String category, + @RequestParam(required = false, defaultValue = "newest") String sort, + @RequestParam(required = false, defaultValue = "1", name = "page") int pageNumber, + @RequestParam(required = false, defaultValue = "") String title) { - /** - * @param fileId file PK - * @return file이 저장된 url 반환 - */ - @GetMapping("/post/file/{fileId}/url") - public String getFileUrl(@PathVariable("fileId") Long fileId ){ - return fileService.getUrl(fileId); - } + Map responseData = new HashMap<>(); + + pageNumber -= 1; + if (pageNumber < 0) { + return errorResponse(CustomMessage.PAGE_NUM_OVER); + } + + Page allPosts; + if (category.equals("idea") || + category.equals("marketing") || + category.equals("design") || + category.equals("video") || + category.equals("etc")) { + //sort -> 최신순 : newest, 좋아요순 : like, 스크랩순 : scrap, 조회순 : view + allPosts = postService.sortAndPaginatePostsByCategory(category, sort, pageNumber,title); + } else { + //sort -> 최신순 : newest, 좋아요순 : like, 스크랩순 : scrap, 조회순 : view + allPosts = postService.sortAndPaginatePosts(sort, pageNumber,title); + } + + if ((allPosts.getContent().isEmpty()) && (allPosts.getTotalElements() > 0)) { + return errorResponse(CustomMessage.PAGE_NUM_OVER); + } + + List result = new ArrayList<>(); - /** - * @param fileId file PK - * @return file을 바로 다운로드 할 수 있다 - */ - @GetMapping("/post/file/{fileId}") - public ResponseEntity getFileDownload(@PathVariable("fileId") Long fileId ) throws IOException { - return fileService.getObject(fileId); + for (Post post : allPosts) { + ResHomeReferenceDto map = ResHomeReferenceDto.builder() + .postThumbnail(post.getThumbnail().getStoreFileUrl()) + .postId(post.getPostId()) + .title(post.getTitle()) + .views(post.getViews()) + .likeCount(post.getLikeCount()) + .scrapCount(post.getScrapCount()) + .postMember(new ResMemberInfoDto(post.getMember().getMemberId(), + post.getMember().getNickname(), + post.getMember().getProfileImage())) + .build(); + + result.add(map); + } + + responseData.put("references", result); //조회한 레퍼런스들 + responseData.put("totalPages", allPosts.getTotalPages()); //전체 페이지의 수 + responseData.put("totalOfAllReferences", allPosts.getTotalElements()); //모든 레퍼런스의 수 + responseData.put("totalOfPageElements", allPosts.getNumberOfElements()); //현 페이지의 레퍼런스 수 + + return successResponse(CustomMessage.OK, responseData); } + @PostMapping("/reference") // 게시물 등록 - public ResponseEntity share(@RequestPart UploadPostForm uploadPostForm, - @RequestPart List uploadFiles, HttpServletRequest request){ - //TODO postingTime 설정. 로그인 여부 거르는 건 Spring Security 설정으로 가능해서 우선 없어도 괜찮을듯함. + public ResponseEntity share(@RequestPart("data") UploadPostForm uploadPostForm, + @RequestPart("thumbnail")MultipartFile thumbnail, + @RequestPart("file") List uploadFiles, HttpServletRequest request) throws IOException { if(authorized(request)){ Long memberId = getMemberId(); + + Post savePost = postService.registerPost(uploadPostForm,thumbnail,uploadFiles,memberId); + + //잘못된 파일 유형 + if(savePost == null){ + return errorResponse(CustomMessage.BAD_FILE); + } + + Post post = postService.findOne(savePost.getPostId()); + + ResReferenceRegisterDto resReferenceRegisterDto = ResReferenceRegisterDto.builder() + .postId(post.getPostId()) + .title(post.getTitle()) + .category(post.getCategory().getName()) + .contestAwardType(post.getContestAwardType()) + .contestName(post.getContestName()) + .youtubeLink(post.getYoutubeLink()) + .pageCount(post.getPageCount()) + .fileNames(post.getUploadFiles().stream().map(UploadFile::getOriginalFileName).collect(Collectors.toList())) + .build(); + return successResponse(CustomMessage.OK, resReferenceRegisterDto); + + + } + + return errorResponse(CustomMessage.UNAUTHORIZED); + } + + @PostMapping("/reference/{reference_id}/like") + public ResponseEntity likeReference(@PathVariable("reference_id") Long referenceId, HttpServletRequest request) { + if (authorized(request)) { + Long memberId = getMemberId(); Member myMember = memberService.findOne(memberId); - postService.registPost(uploadPostForm,uploadFiles,myMember); - return successResponse(CustomMessage.OK,myMember); + postService.likePost(memberId, myMember, referenceId); + int count = postService.findLikeCount(referenceId); + Map map = Collections.singletonMap("likeCount", count); + return successResponse(CustomMessage.OK,map); } + return errorResponse(CustomMessage.UNAUTHORIZED); + } + @PostMapping("/reference/{reference_id}/scrap") + public ResponseEntity scrapReference(@PathVariable("reference_id") Long referenceId, HttpServletRequest request){ + if(authorized(request)){ + Long memberId = getMemberId(); + Member myMember = memberService.findOne(memberId); + postService.scrapPost(memberId, myMember, referenceId); + int count = postService.findScrapCount(referenceId); + Map map = Collections.singletonMap("scrapCount", count); + return successResponse(CustomMessage.OK,map); + } return errorResponse(CustomMessage.UNAUTHORIZED); } -} + @DeleteMapping("/user/reference/{reference_id}") + public ResponseEntity deleteReference(@PathVariable("reference_id") Long postId, HttpServletRequest request){ + if(authorized(request)){ + Long memberId = getMemberId(); + Member myMember = memberService.findOne(memberId); + + // 현재 로그인한 사용자가 올린 게시글이 맞는지 확인/예외처리 + if(postService.checkMemberPost(myMember, postId)){ + postService.deleteReference(postId); + return new ResponseEntity<>(HttpStatus.OK); + } + } + return errorResponse(CustomMessage.CAN_NOT_ACCESS); + } + +} \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Post/Controller/ViewerController.java b/src/main/java/Remoa/BE/Post/Controller/ViewerController.java new file mode 100644 index 0000000..20aa32b --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Controller/ViewerController.java @@ -0,0 +1,134 @@ +package Remoa.BE.Post.Controller; + +import Remoa.BE.Member.Domain.Member; +import Remoa.BE.Member.Dto.Res.ResMemberInfoDto; +import Remoa.BE.Member.Service.MemberService; +import Remoa.BE.Post.Domain.Post; +import Remoa.BE.Post.Domain.UploadFile; +import Remoa.BE.Post.Dto.Response.ResCommentDto; +import Remoa.BE.Post.Dto.Response.ResFeedbackDto; +import Remoa.BE.Post.Dto.Response.ResReferenceViewerDto; +import Remoa.BE.Post.Dto.Response.ResReplyDto; +import Remoa.BE.Post.Service.CommentService; +import Remoa.BE.Post.Service.FeedbackService; +import Remoa.BE.Post.Service.PostService; +import Remoa.BE.exception.CustomMessage; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.stream.Collectors; + +import static Remoa.BE.exception.CustomBody.successResponse; +import static Remoa.BE.utill.MemberInfo.authorized; +import static Remoa.BE.utill.MemberInfo.getMemberId; + +@Slf4j +@RestController +@RequiredArgsConstructor +public class ViewerController { + + private final CommentService commentService; + private final PostService postService; + private final FeedbackService feedbackService; + private final MemberService memberService; + + @GetMapping("reference/{reference_id}") + public ResponseEntity referenceViewer(HttpServletRequest request, + @PathVariable("reference_id") Long referenceId) { + + Long myMemberId = null; + Member myMember = null; + if (authorized(request)) { + myMemberId = getMemberId(); + myMember = memberService.findOne(myMemberId); + } + final Long finalMyMemberId = myMemberId; + + // query parameter로 넘어온 id값의 post 조회 + Post post = postService.findOneViewPlus(referenceId); + + // 조회한 post의 comment(대댓글 제외) 조회 및 CommentDto로 매핑 -> 이후 주석에서 대댓글 comment 조회 + List comments = commentService.findAllCommentsOfPost(post).stream() + .filter(comment -> comment.getParentComment() == null) + .map(comment -> ResCommentDto.builder() + .commentId(comment.getCommentId()) + .isLiked(finalMyMemberId == null ? null : commentService.findCommentLike(finalMyMemberId, comment.getCommentId()).isPresent()) + .member(new ResMemberInfoDto(comment.getMember().getMemberId(), + comment.getMember().getNickname(), + comment.getMember().getProfileImage())) + .comment(comment.getComment()) + .likeCount(comment.getCommentLikeCount()) + .commentedTime(comment.getCommentedTime()) + //아래부터 대댓글 comment 조회 및 dto 매핑 + .replies(commentService.getParentCommentsReply(comment).stream() + .map(reply -> ResReplyDto.builder() + .replyId(reply.getCommentId()) + .member(new ResMemberInfoDto(reply.getMember().getMemberId(), + reply.getMember().getNickname(), + reply.getMember().getProfileImage())) + .content(reply.getComment()) + .likeCount(reply.getCommentLikeCount()) + .isLiked(finalMyMemberId == null ? null : commentService.findCommentLike(finalMyMemberId, reply.getCommentId()).isPresent()) + .repliedTime(reply.getCommentedTime()) + .build()).collect(Collectors.toList())) + .build()).collect(Collectors.toList()); + + // 조회한 post의 feedback(대댓글 제외) 조회 및 FeedbackDto로 매핑 -> 이후 주석에서 대댓글 feedback 조회 + List feedbacks = feedbackService.findAllFeedbacksOfPost(post).stream() + .filter(feedback -> feedback.getParentFeedback() == null) + .map(feedback -> ResFeedbackDto.builder() + .feedbackId(feedback.getFeedbackId()) + .isLiked(finalMyMemberId == null ? null : feedbackService.findFeedbackLike(finalMyMemberId, feedback.getFeedbackId()).isPresent()) + .member(new ResMemberInfoDto(feedback.getMember().getMemberId(), + feedback.getMember().getNickname(), + feedback.getMember().getProfileImage())) + .feedback(feedback.getFeedback()) + .page(feedback.getPageNumber()) + .likeCount(feedback.getFeedbackLikeCount()) + .feedbackTime(feedback.getFeedbackTime()) + //아래부터 대댓글 feedback 조회 및 dto 매핑 + .replies(feedbackService.getParentFeedbacksReply(feedback).stream() + .map(reply -> ResReplyDto.builder() + .replyId(reply.getFeedbackId()) + .member(new ResMemberInfoDto(reply.getMember().getMemberId(), + reply.getMember().getNickname(), + reply.getMember().getProfileImage())) + .content(reply.getFeedback()) + .likeCount(reply.getFeedbackLikeCount()) + .isLiked(finalMyMemberId == null ? null : feedbackService.findFeedbackLike(finalMyMemberId, reply.getFeedbackId()).isPresent()) + .repliedTime(reply.getFeedbackTime()) + .build()).collect(Collectors.toList())) + .build()).collect(Collectors.toList()); + + // 위에 생성한 CommentDto, FeedbackDto를 이용해 ReferenceViewerDto 매핑. + ResReferenceViewerDto result = ResReferenceViewerDto.builder() + .postId(post.getPostId()) + .postMember(new ResMemberInfoDto(post.getMember().getMemberId(), + post.getMember().getNickname(), + post.getMember().getProfileImage())) + .thumbnail(post.getThumbnail().getStoreFileUrl()) + .contestName(post.getContestName()) + .contestAwardType(post.getContestAwardType()) + .category(post.getCategory().getName()) + .title(post.getTitle()) + .likeCount(post.getLikeCount()) + .isLiked(finalMyMemberId == null ? null : postService.isThisPostLiked(myMember)) + .scrapCount(post.getScrapCount()) + .isScraped(finalMyMemberId == null ? null : postService.isThisPostScraped(myMember)) + .postingTime(post.getPostingTime().toString()) + .views(post.getViews()) + .pageCount(post.getPageCount()) + .fileNames(post.getUploadFiles().stream().map(UploadFile::getStoreFileUrl).collect(Collectors.toList())) + .comments(comments) + .feedbacks(feedbacks) + .build(); + + return successResponse(CustomMessage.OK, result); + } +} \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Post/Domain/Post.java b/src/main/java/Remoa/BE/Post/Domain/Post.java index e9753b4..0ea147e 100644 --- a/src/main/java/Remoa/BE/Post/Domain/Post.java +++ b/src/main/java/Remoa/BE/Post/Domain/Post.java @@ -1,11 +1,14 @@ package Remoa.BE.Post.Domain; import Remoa.BE.Member.Domain.Comment; +import Remoa.BE.Member.Domain.CommentFeedback; +import Remoa.BE.Member.Domain.Feedback; import Remoa.BE.Member.Domain.Member; import lombok.*; import org.hibernate.annotations.Where; import javax.persistence.*; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -21,8 +24,7 @@ public class Post { @Id - @GeneratedValue - @Column(name = "post_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long postId; /** @@ -37,6 +39,9 @@ public class Post { */ private String title; + @OneToOne + private UploadFile thumbnail; + /** * 참여 공모전의 이름 */ @@ -44,21 +49,21 @@ public class Post { private String contestName; /** - * 참여한 공모전의 마감 기한 + * 유튜브 링크 */ - private String deadline; + private String youtubeLink; /** - * 참여한 공모전의 수상 내역 + * 참여한 공모전의 마감 기한 */ - @Column(name = "contest_award") - private Boolean ContestAward; + private String deadline; + /** * pm쪽에 문의해야할듯. */ @Column(name = "contest_aware_type") - private String contestAwareType; + private String contestAwardType; /** * Post에 대한 좋아요 수 @@ -70,24 +75,34 @@ public class Post { * Post가 작성된 시간 */ @Column(name = "posting_time") - private String postingTime; + private LocalDateTime postingTime; /** * Post의 조회수 */ private Integer views = 0; + private Integer scrapCount = 0; + + private Integer pageCount; + /** * Post에 작성되어진 Comment */ - @OneToMany(mappedBy = "post") + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL) private List comments = new ArrayList<>(); + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL) + private List feedbacks = new ArrayList<>(); + + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL) + private List commentFeedbacks = new ArrayList<>(); + /** * Post에서 쓰인 files */ - @OneToMany(mappedBy = "post") - private List uploadFiles = new ArrayList<>(); + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL) + private List uploadFiles; @OneToMany(mappedBy = "post", cascade = CascadeType.ALL) private List postScarps = new ArrayList<>(); diff --git a/src/main/java/Remoa/BE/Post/Domain/PostLike.java b/src/main/java/Remoa/BE/Post/Domain/PostLike.java index d2d112e..b7ee5dd 100644 --- a/src/main/java/Remoa/BE/Post/Domain/PostLike.java +++ b/src/main/java/Remoa/BE/Post/Domain/PostLike.java @@ -5,7 +5,6 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.Where; import javax.persistence.*; diff --git a/src/main/java/Remoa/BE/Post/Domain/PostScarp.java b/src/main/java/Remoa/BE/Post/Domain/PostScarp.java index 49fc0ac..c5d4f11 100644 --- a/src/main/java/Remoa/BE/Post/Domain/PostScarp.java +++ b/src/main/java/Remoa/BE/Post/Domain/PostScarp.java @@ -5,10 +5,10 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.Where; import javax.persistence.*; +import java.time.LocalDateTime; @Getter @Setter @@ -30,11 +30,15 @@ public class PostScarp { @JoinColumn(name = "post_id") private Post post; + @Column(name = "scrap_time") + private LocalDateTime scrapTime; + private Boolean deleted = Boolean.FALSE; public static PostScarp createPostScrap(Member member, Post post) { PostScarp postScrap = new PostScarp(); postScrap.setPost(post); + postScrap.setScrapTime(LocalDateTime.now()); postScrap.setMember(member); return postScrap; diff --git a/src/main/java/Remoa/BE/Post/Domain/UploadFile.java b/src/main/java/Remoa/BE/Post/Domain/UploadFile.java index eeb155f..6337ff5 100644 --- a/src/main/java/Remoa/BE/Post/Domain/UploadFile.java +++ b/src/main/java/Remoa/BE/Post/Domain/UploadFile.java @@ -2,7 +2,6 @@ import lombok.Getter; import lombok.Setter; -import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.Where; import javax.persistence.*; @@ -16,12 +15,8 @@ public class UploadFile { @Id @GeneratedValue - @Column(name = "file_id") private Long uploadFileId; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "post_id") - private Post post; /** * 업로드된 파일의 원본 이름 @@ -48,6 +43,10 @@ public class UploadFile { @Column(name = "store_file_url") private String storeFileUrl; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id") + private Post post; + private Boolean deleted = Boolean.FALSE; //이후 업로드 날짜 및 시간, 컨텐츠 타입, 사이즈 등의 필드등이 필요할 때 손봐야할듯. diff --git a/src/main/java/Remoa/BE/Post/Dto/Request/UploadPostForm.java b/src/main/java/Remoa/BE/Post/Dto/Request/UploadPostForm.java new file mode 100644 index 0000000..3067840 --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Dto/Request/UploadPostForm.java @@ -0,0 +1,21 @@ +package Remoa.BE.Post.Dto.Request; + +import lombok.*; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@Builder +public class UploadPostForm { + + private String title; // Post name + + private String contestName; + + private String contestAwardType; + + private String category; // Category name + + private String youtubeLink; // 유튜브 링크 +} diff --git a/src/main/java/Remoa/BE/Post/Dto/Response/ResCommentDto.java b/src/main/java/Remoa/BE/Post/Dto/Response/ResCommentDto.java new file mode 100644 index 0000000..96511dd --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Dto/Response/ResCommentDto.java @@ -0,0 +1,26 @@ +package Remoa.BE.Post.Dto.Response; + +import Remoa.BE.Member.Dto.Res.ResMemberInfoDto; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ResCommentDto { + + private Long commentId; + private ResMemberInfoDto member; + private String comment; + private Integer likeCount; + private Boolean isLiked; + private LocalDateTime commentedTime; + private List replies; + +} \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Post/Dto/Response/ResCommentFeedbackDto.java b/src/main/java/Remoa/BE/Post/Dto/Response/ResCommentFeedbackDto.java new file mode 100644 index 0000000..1a7d0fe --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Dto/Response/ResCommentFeedbackDto.java @@ -0,0 +1,21 @@ +package Remoa.BE.Post.Dto.Response; + +import Remoa.BE.Member.Dto.Res.ResMemberInfoDto; +import lombok.Builder; +import lombok.Data; + +/** + * 내 활동 관리에 쓰이는 Comment와 Feedback을 구분 없이 최신순으로 볼러오는 데 쓰이는 dto. + */ +@Data +@Builder +public class ResCommentFeedbackDto { + + private String title; + private Long postId; + private String thumbnail; + private ResMemberInfoDto member; + private String content; + private Integer likeCount; + +} diff --git a/src/main/java/Remoa/BE/Post/Dto/Response/ResFeedbackDto.java b/src/main/java/Remoa/BE/Post/Dto/Response/ResFeedbackDto.java new file mode 100644 index 0000000..89ba13d --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Dto/Response/ResFeedbackDto.java @@ -0,0 +1,25 @@ +package Remoa.BE.Post.Dto.Response; + +import Remoa.BE.Member.Dto.Res.ResMemberInfoDto; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class ResFeedbackDto { + private Long feedbackId; + private ResMemberInfoDto member; + private String feedback; + private Integer page; + private Integer likeCount; + private Boolean isLiked; + private LocalDateTime feedbackTime; + private List replies; +} diff --git a/src/main/java/Remoa/BE/Post/Dto/Response/ResHomeReferenceDto.java b/src/main/java/Remoa/BE/Post/Dto/Response/ResHomeReferenceDto.java new file mode 100644 index 0000000..96345b1 --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Dto/Response/ResHomeReferenceDto.java @@ -0,0 +1,18 @@ +package Remoa.BE.Post.Dto.Response; + +import Remoa.BE.Member.Dto.Res.ResMemberInfoDto; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class ResHomeReferenceDto { + + private String postThumbnail; + private Long postId; + private String title; + private int views; + private int likeCount; + private int scrapCount; + private ResMemberInfoDto postMember; +} diff --git a/src/main/java/Remoa/BE/Post/Dto/Response/ResPostDto.java b/src/main/java/Remoa/BE/Post/Dto/Response/ResPostDto.java new file mode 100644 index 0000000..1b02c5b --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Dto/Response/ResPostDto.java @@ -0,0 +1,24 @@ +package Remoa.BE.Post.Dto.Response; + +import Remoa.BE.Member.Dto.Res.ResMemberInfoDto; +import lombok.*; + +/** + * 작업물 목록을 보여줄 때 쓰일 Post의 간단한 정보만을 담은 Dto. + */ +@Setter +@Getter +@Builder +public class ResPostDto { + + public Long postId; + public ResMemberInfoDto postMember; + public String thumbnail; + public String title; + public Integer likeCount; + public String postingTime; + public Integer views; + public Integer scrapCount; + public String categoryName; + +} diff --git a/src/main/java/Remoa/BE/Post/Dto/Response/ResReceivedCommentDto.java b/src/main/java/Remoa/BE/Post/Dto/Response/ResReceivedCommentDto.java new file mode 100644 index 0000000..3f9acbd --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Dto/Response/ResReceivedCommentDto.java @@ -0,0 +1,19 @@ +package Remoa.BE.Post.Dto.Response; + +import Remoa.BE.Member.Dto.Res.ResMemberInfoDto; +import lombok.Builder; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Data +@Builder +public class ResReceivedCommentDto { + + private Long postId; + private String thumbnail; + private Map commentInfo; + private String title; + +} diff --git a/src/main/java/Remoa/BE/Post/Dto/Response/ResReferenceRegisterDto.java b/src/main/java/Remoa/BE/Post/Dto/Response/ResReferenceRegisterDto.java new file mode 100644 index 0000000..0419c41 --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Dto/Response/ResReferenceRegisterDto.java @@ -0,0 +1,29 @@ +package Remoa.BE.Post.Dto.Response; + +import lombok.Builder; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +@Builder +@Getter +@Setter +public class ResReferenceRegisterDto { + + private Long postId; + + private String title; // Post name + + private String contestName; + + private String category; // Category name + + private String contestAwardType; + + private String youtubeLink; + + private Integer pageCount; + + private List fileNames; +} diff --git a/src/main/java/Remoa/BE/Post/Dto/Response/ResReferenceViewerDto.java b/src/main/java/Remoa/BE/Post/Dto/Response/ResReferenceViewerDto.java new file mode 100644 index 0000000..bbf7af2 --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Dto/Response/ResReferenceViewerDto.java @@ -0,0 +1,31 @@ +package Remoa.BE.Post.Dto.Response; + +import Remoa.BE.Member.Dto.Res.ResMemberInfoDto; +import lombok.Builder; +import lombok.Data; + +import java.util.List; + +@Data +@Builder +public class ResReferenceViewerDto { + + public Long postId; + public ResMemberInfoDto postMember; + public String thumbnail; + private String contestName; + private String contestAwardType; + private String category; + public String title; + public Integer likeCount; + public Boolean isLiked; + public Integer scrapCount; + public Boolean isScraped; + public String postingTime; + public Integer views; + private Integer pageCount; + private List fileNames; + private List comments; + private List feedbacks; + +} diff --git a/src/main/java/Remoa/BE/Post/Dto/Response/ResReplyDto.java b/src/main/java/Remoa/BE/Post/Dto/Response/ResReplyDto.java new file mode 100644 index 0000000..1cf50de --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Dto/Response/ResReplyDto.java @@ -0,0 +1,24 @@ +package Remoa.BE.Post.Dto.Response; + +import Remoa.BE.Member.Dto.Res.ResMemberInfoDto; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * comment, feedback의 대댓글에 모두 사용할 수 있음. + */ +@Data +@AllArgsConstructor +@Builder +public class ResReplyDto { + + private Long replyId; + private ResMemberInfoDto member; + private String content; + private Integer likeCount; + private Boolean isLiked; + private LocalDateTime repliedTime; +} diff --git a/src/main/java/Remoa/BE/Post/Repository/CommentFeedbackCustomRepository.java b/src/main/java/Remoa/BE/Post/Repository/CommentFeedbackCustomRepository.java new file mode 100644 index 0000000..0856279 --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Repository/CommentFeedbackCustomRepository.java @@ -0,0 +1,16 @@ +package Remoa.BE.Post.Repository; + +import Remoa.BE.Member.Domain.Comment; +import Remoa.BE.Member.Domain.CommentFeedback; +import Remoa.BE.Member.Domain.Feedback; +import Remoa.BE.Member.Domain.Member; + +import java.util.List; +import java.util.Optional; + +public interface CommentFeedbackCustomRepository { + Optional findByMemberOrderByTime(Member member); + Optional findByComment(Comment comment); + Optional findByFeedback(Feedback feedback); + +} diff --git a/src/main/java/Remoa/BE/Post/Repository/CommentFeedbackCustomRepositoryImpl.java b/src/main/java/Remoa/BE/Post/Repository/CommentFeedbackCustomRepositoryImpl.java new file mode 100644 index 0000000..4f694ab --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Repository/CommentFeedbackCustomRepositoryImpl.java @@ -0,0 +1,49 @@ +package Remoa.BE.Post.Repository; + +import Remoa.BE.Member.Domain.*; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +@RequiredArgsConstructor +public class CommentFeedbackCustomRepositoryImpl implements CommentFeedbackCustomRepository{ + + private final JPAQueryFactory jpaQueryFactory; + + QCommentFeedback commentFeedback = QCommentFeedback.commentFeedback; + QMember member = QMember.member; + QComment comment = QComment.comment1; + QFeedback feedback = QFeedback.feedback1; + + @Override + public Optional findByMemberOrderByTime(Member member) { + return jpaQueryFactory.select(commentFeedback) + .from(commentFeedback) + .join(commentFeedback.member, this.member) + .where(this.member.eq(member)) + .orderBy(commentFeedback.time.desc()) + .limit(1L).stream().findAny(); + } + + @Override + public Optional findByComment(Comment comment) { + return jpaQueryFactory.select(commentFeedback) + .from(commentFeedback) + .join(commentFeedback.comment, this.comment) + .where(this.comment.eq(comment)) + .stream().findAny(); + } + + @Override + public Optional findByFeedback(Feedback feedback) { + return jpaQueryFactory.select(commentFeedback) + .from(commentFeedback) + .join(commentFeedback.feedback, this.feedback) + .where(this.feedback.eq(feedback)) + .stream().findAny(); + } + +} diff --git a/src/main/java/Remoa/BE/Post/Repository/CommentFeedbackRepository.java b/src/main/java/Remoa/BE/Post/Repository/CommentFeedbackRepository.java new file mode 100644 index 0000000..e21a9f0 --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Repository/CommentFeedbackRepository.java @@ -0,0 +1,15 @@ +package Remoa.BE.Post.Repository; + +import Remoa.BE.Member.Domain.CommentFeedback; +import Remoa.BE.Member.Domain.Member; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CommentFeedbackRepository extends JpaRepository, CommentFeedbackCustomRepository { + + Page findByMemberOrderByTimeDesc(Pageable pageable, Member member); + +} diff --git a/src/main/java/Remoa/BE/Post/Repository/CommentLikeRepository.java b/src/main/java/Remoa/BE/Post/Repository/CommentLikeRepository.java new file mode 100644 index 0000000..4033337 --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Repository/CommentLikeRepository.java @@ -0,0 +1,14 @@ +package Remoa.BE.Post.Repository; + +import Remoa.BE.Member.Domain.Comment; +import Remoa.BE.Member.Domain.CommentLike; +import Remoa.BE.Member.Domain.FeedbackLike; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface CommentLikeRepository extends JpaRepository { + Optional findByMemberMemberIdAndCommentCommentId(Long memberId, Long commentId); +} \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Post/Repository/CommentPagingRepository.java b/src/main/java/Remoa/BE/Post/Repository/CommentPagingRepository.java new file mode 100644 index 0000000..d2d177e --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Repository/CommentPagingRepository.java @@ -0,0 +1,15 @@ +package Remoa.BE.Post.Repository; + +import Remoa.BE.Member.Domain.Comment; +import Remoa.BE.Post.Domain.Post; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface CommentPagingRepository extends PagingAndSortingRepository { + + Page findByParentCommentIsNullAndPost(Pageable pageable, Post post); + +} diff --git a/src/main/java/Remoa/BE/Post/Repository/CommentRepository.java b/src/main/java/Remoa/BE/Post/Repository/CommentRepository.java index cf7049d..ad386ad 100644 --- a/src/main/java/Remoa/BE/Post/Repository/CommentRepository.java +++ b/src/main/java/Remoa/BE/Post/Repository/CommentRepository.java @@ -19,13 +19,15 @@ public class CommentRepository { private final EntityManager em; - + public Optional findOne(Long id) { + return Optional.ofNullable(em.find(Comment.class, id)); + } public void saveComment(Comment comment) { em.persist(comment); } - public Comment findByCommentId(Long commentId) { - return em.find(Comment.class, commentId); + public Optional findByCommentId(Long commentId) { + return Optional.ofNullable(em.find(Comment.class, commentId)); } /** @@ -34,7 +36,8 @@ public Comment findByCommentId(Long commentId) { * @return List */ public List findByPost(Post post) { - return em.createQuery("select c from Comment c where c.post = :post", Comment.class) + return em.createQuery("select c from Comment c where c.post = :post order by c.commentedTime desc", + Comment.class) .setParameter("post", post) .getResultList(); } @@ -87,4 +90,19 @@ public Integer findCommentBookmark(Comment comment) { .getResultList() .size(); }*/ + + public List findRepliesOfParentComment(Comment parentComment) { + return em.createQuery("select c from Comment c " + + "where c.parentComment = :comment order by c.commentedTime desc", Comment.class) + .setParameter("comment", parentComment) + .getResultList(); + } + + public void updateComment(Comment newComment){ + em.merge(newComment); + } + + public void deleteComment(Comment comment){ + em.remove(comment); + } } diff --git a/src/main/java/Remoa/BE/Post/Repository/FeedbackLikeRepository.java b/src/main/java/Remoa/BE/Post/Repository/FeedbackLikeRepository.java new file mode 100644 index 0000000..0232b8f --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Repository/FeedbackLikeRepository.java @@ -0,0 +1,12 @@ +package Remoa.BE.Post.Repository; + +import Remoa.BE.Member.Domain.FeedbackLike; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface FeedbackLikeRepository extends JpaRepository { + Optional findByMemberMemberIdAndFeedbackFeedbackId(Long memberId, Long feedbackId); +} \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Post/Repository/FeedbackRepository.java b/src/main/java/Remoa/BE/Post/Repository/FeedbackRepository.java index 703c21c..f2f6570 100644 --- a/src/main/java/Remoa/BE/Post/Repository/FeedbackRepository.java +++ b/src/main/java/Remoa/BE/Post/Repository/FeedbackRepository.java @@ -1,9 +1,6 @@ package Remoa.BE.Post.Repository; -import Remoa.BE.Member.Domain.Feedback; -import Remoa.BE.Member.Domain.FeedbackBookmark; -import Remoa.BE.Member.Domain.FeedbackLike; -import Remoa.BE.Member.Domain.Member; +import Remoa.BE.Member.Domain.*; import Remoa.BE.Post.Domain.Post; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -19,13 +16,15 @@ public class FeedbackRepository { private final EntityManager em; - + public Optional findOne(Long id){ + return Optional.ofNullable(em.find(Feedback.class, id)); + } public void saveFeedback(Feedback feedback) { em.persist(feedback); } - public Feedback findByFeedbackId(Long feedbackId) { - return em.find(Feedback.class, feedbackId); + public Optional findByFeedbackId(Long feedbackId) { + return Optional.ofNullable(em.find(Feedback.class, feedbackId)); } /** @@ -34,11 +33,19 @@ public Feedback findByFeedbackId(Long feedbackId) { * @return List */ public List findByPost(Post post) { - return em.createQuery("select c from Feedback c where c.post = :post", Feedback.class) + return em.createQuery("select f from Feedback f where f.post = :post order by f.feedbackTime desc", + Feedback.class) .setParameter("post", post) .getResultList(); } + public List findRepliesOfParentFeedback(Feedback parentFeedback) { + return em.createQuery("select f from Feedback f " + + "where f.parentFeedback = :feedback order by f.feedbackTime desc", Feedback.class) + .setParameter("feedback", parentFeedback) + .getResultList(); + } + public void saveFeedbackLike(FeedbackLike feedbackLike) { em.persist(feedbackLike); } @@ -80,6 +87,10 @@ public Optional findMemberCommendBookmark(Member member, Feedb .findAny(); } + public void updateFeedback(Feedback newFeedback){ + em.merge(newFeedback); + } + /* //필요없을 거 같아서 주석처리... public Integer findFeedbackBookmark(Feedback feedback) { return em.createQuery("select cb from FeedbackBookmark cb where cb.feedback = :feedback", FeedbackBookmark.class) @@ -87,4 +98,8 @@ public Integer findFeedbackBookmark(Feedback feedback) { .getResultList() .size(); }*/ + + public void deleteFeedback(Feedback feedback){ + em.remove(feedback); + } } diff --git a/src/main/java/Remoa/BE/Post/Repository/PostCustomRepository.java b/src/main/java/Remoa/BE/Post/Repository/PostCustomRepository.java new file mode 100644 index 0000000..61d37ac --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Repository/PostCustomRepository.java @@ -0,0 +1,13 @@ +package Remoa.BE.Post.Repository; + +import Remoa.BE.Member.Domain.Member; +import Remoa.BE.Post.Domain.Post; +import Remoa.BE.Post.Domain.PostScarp; + +import java.util.List; + +public interface PostCustomRepository { + + List findByMemberRecentTwelve(Member member); + +} diff --git a/src/main/java/Remoa/BE/Post/Repository/PostCustomRepositoryImpl.java b/src/main/java/Remoa/BE/Post/Repository/PostCustomRepositoryImpl.java new file mode 100644 index 0000000..4e14696 --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Repository/PostCustomRepositoryImpl.java @@ -0,0 +1,38 @@ +package Remoa.BE.Post.Repository; + +import Remoa.BE.Member.Domain.Member; +import Remoa.BE.Member.Domain.QMember; +import Remoa.BE.Post.Domain.Post; +import Remoa.BE.Post.Domain.PostScarp; +import Remoa.BE.Post.Domain.QPost; +import Remoa.BE.Post.Domain.QPostScarp; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.stream.Collectors; + +@Repository +@RequiredArgsConstructor +public class PostCustomRepositoryImpl implements PostCustomRepository { + + private final JPAQueryFactory jpaQueryFactory; + QMember member = QMember.member; + QPost post = QPost.post; + QPostScarp postScarp = QPostScarp.postScarp; + + @Override + public List findByMemberRecentTwelve(Member member) { + return jpaQueryFactory.select(postScarp) + .from(postScarp) + .join(postScarp.member, this.member) + .where(this.member.eq(member)) + .orderBy(postScarp.scrapTime.desc()) + .limit(12L) + .fetch() + .stream().map(PostScarp::getPost) + .collect(Collectors.toList()); + + } +} diff --git a/src/main/java/Remoa/BE/Post/Repository/PostLikeRepository.java b/src/main/java/Remoa/BE/Post/Repository/PostLikeRepository.java new file mode 100644 index 0000000..4a83ad2 --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Repository/PostLikeRepository.java @@ -0,0 +1,10 @@ +package Remoa.BE.Post.Repository; + +import Remoa.BE.Post.Domain.PostLike; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PostLikeRepository extends JpaRepository { + PostLike findByMemberMemberIdAndPostPostId(Long memberId, Long postId); +} diff --git a/src/main/java/Remoa/BE/Post/Repository/PostPagingRepository.java b/src/main/java/Remoa/BE/Post/Repository/PostPagingRepository.java new file mode 100644 index 0000000..4541023 --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Repository/PostPagingRepository.java @@ -0,0 +1,42 @@ +package Remoa.BE.Post.Repository; + +import Remoa.BE.Member.Domain.Member; +import Remoa.BE.Post.Domain.Category; +import Remoa.BE.Post.Domain.Post; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.stereotype.Repository; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * JPA Query Creation을 사용해서 post data sorting & slicing + */ +@Repository +public interface PostPagingRepository extends PagingAndSortingRepository, PostCustomRepository { + + Page findByMemberAndTitleContainingOrderByPostingTimeDesc(Pageable pageable, Member member,String title); + + Page findByMemberAndTitleContainingOrderByViewsDesc(Pageable pageable, Member member,String title); + + Page findByMemberAndTitleContainingOrderByLikeCountDesc(Pageable pageable, Member member,String title); + + Page findByMemberAndTitleContainingOrderByScrapCountDesc(Pageable pageable, Member member,String title); + + Page findByMemberAndCategoryAndTitleContaining(Pageable pageable, Member member, Category category,String title); + + //"IsNotEmpty" 부분에 "Cannot resolve property 'isNotEmpty'"경고가 나오는 건 JPA의 isNotEmpty 예약어를 intellij가 인식하지 못하고 자바의 프로퍼티로 인식하기 때문. 즉, 무시해도 됨. + Page findByMemberAndCategoryAndCommentsIsNotEmpty(Pageable pageable, Member member, Category category); + + //"IsNotEmpty" 부분에 "Cannot resolve property 'isNotEmpty'"경고가 나오는 건 JPA의 isNotEmpty 예약어를 intellij가 인식하지 못하고 자바의 프로퍼티로 인식하기 때문. 즉, 무시해도 됨. + Page findByMemberAndCommentsIsNotEmpty(Pageable pageable, Member member); + + Page findByTitleContaining(Pageable pageable,String title); + + Page findByCategoryAndTitleContaining(Pageable pageable, Category category,String title); + + List findByMemberRecentTwelve(Member member); + +} diff --git a/src/main/java/Remoa/BE/Post/Repository/PostRepository.java b/src/main/java/Remoa/BE/Post/Repository/PostRepository.java index a31258a..1104c46 100644 --- a/src/main/java/Remoa/BE/Post/Repository/PostRepository.java +++ b/src/main/java/Remoa/BE/Post/Repository/PostRepository.java @@ -1,5 +1,6 @@ package Remoa.BE.Post.Repository; +import Remoa.BE.Member.Domain.Comment; import Remoa.BE.Member.Domain.Member; import Remoa.BE.Post.Domain.Category; import Remoa.BE.Post.Domain.Post; @@ -10,6 +11,10 @@ import org.springframework.stereotype.Repository; import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Root; import java.util.List; import java.util.Optional; @@ -24,37 +29,45 @@ public void savePost(Post post) { em.persist(post); } - public Post findByPostId(Long postId) { - return em.find(Post.class, postId); + public List findAll(){ + return em.createQuery("SELECT p FROM Post p", Post.class).getResultList(); } - public Optional findByMemberId(Member member) { + + public Optional findOne(Long postId) { // 위 findByPostId와 거의 같음 반환형을 Optional로 하기 위함 + return Optional.ofNullable(em.find(Post.class, postId)); + } + + public List findByMember(Member member) { return em.createQuery("select p from Post p where p.member = :member", Post.class) .setParameter("member", member) - .getResultStream() - .findAny(); + .getResultList(); + } + + public List findByTitleContaining(String name){ + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery query = cb.createQuery(Post.class); + Root root = query.from(Post.class); + query.select(root).where(cb.like(root.get("title"), "%" + name + "%")); + return em.createQuery(query).getResultList(); } public void savePostScrap(PostScarp postScarp) { em.persist(postScarp); } - public Post findScrapedPost(Member member) { + public Optional findScrapedPost(Member member) { return em.createQuery("select ps from PostScarp ps where ps.member = :member", PostScarp.class) .setParameter("member", member) .getResultStream() - .findAny() - .get() - .getPost(); + .findAny(); } - public Post findLikedPost(Member member) { + public Optional findLikedPost(Member member) { return em.createQuery("select pl from PostLike pl where pl.member = :member", PostLike.class) .setParameter("member", member) .getResultStream() - .findAny() - .get() - .getPost(); + .findAny(); } public List findPostsByCategory(Category category) { @@ -63,6 +76,12 @@ public List findPostsByCategory(Category category) { .getResultList(); } + public void saveComment(Comment comment) { + em.persist(comment); + } - + public void deletePost(Long postId){ + Post post = em.find(Post.class, postId); + em.remove(post); + } } diff --git a/src/main/java/Remoa/BE/Post/Repository/PostScrapRepository.java b/src/main/java/Remoa/BE/Post/Repository/PostScrapRepository.java new file mode 100644 index 0000000..069e1f5 --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Repository/PostScrapRepository.java @@ -0,0 +1,16 @@ +package Remoa.BE.Post.Repository; + +import Remoa.BE.Member.Domain.Member; +import Remoa.BE.Post.Domain.PostScarp; +import Remoa.BE.Post.Service.PostService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PostScrapRepository extends JpaRepository { + PostScarp findByMemberMemberIdAndPostPostId(Long memberId, Long postId); + + Page findByMemberOrderByScrapTimeDesc(Pageable pageable, Member member); +} diff --git a/src/main/java/Remoa/BE/Post/Service/CommentFeedbackService.java b/src/main/java/Remoa/BE/Post/Service/CommentFeedbackService.java new file mode 100644 index 0000000..31bf05b --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Service/CommentFeedbackService.java @@ -0,0 +1,59 @@ +package Remoa.BE.Post.Service; + +import Remoa.BE.Member.Domain.*; +import Remoa.BE.Post.Domain.Post; +import Remoa.BE.Post.Repository.CommentFeedbackRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; + +import java.time.LocalDateTime; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class CommentFeedbackService { + private static final int CONTENT_PAGE_SIZE = 4; + private final CommentFeedbackRepository commentFeedbackRepository; + + @Transactional + public CommentFeedback saveCommentFeedback(Comment comment, Feedback feedback, ContentType type, + Member member, Post post, LocalDateTime time) { + + CommentFeedback commentFeedback = CommentFeedback.builder() + .type(type) + .member(member) + .post(post) + .comment(comment) + .feedback(feedback) + .time(time) + .deleted(false).build(); + return commentFeedbackRepository.save(commentFeedback); + } + + public Page findNewestCommentOrFeedback(int page, Member member) { + Pageable pageable = PageRequest.of(page, CONTENT_PAGE_SIZE); + return commentFeedbackRepository.findByMemberOrderByTimeDesc(pageable, member); + } + + public CommentFeedback findNewestCommentFeedback(Member member) { + return commentFeedbackRepository.findByMemberOrderByTime(member).orElse(null); + } + + public CommentFeedback findComment(Comment comment) { + return commentFeedbackRepository.findByComment(comment) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Comment not found")); + } + + public CommentFeedback findFeedback(Feedback feedback) { + return commentFeedbackRepository.findByFeedback(feedback) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Feedback not found")); + } + +} diff --git a/src/main/java/Remoa/BE/Post/Service/CommentService.java b/src/main/java/Remoa/BE/Post/Service/CommentService.java index 93744ed..0a939ca 100644 --- a/src/main/java/Remoa/BE/Post/Service/CommentService.java +++ b/src/main/java/Remoa/BE/Post/Service/CommentService.java @@ -1,17 +1,25 @@ package Remoa.BE.Post.Service; -import Remoa.BE.Member.Domain.Comment; -import Remoa.BE.Member.Domain.CommentBookmark; -import Remoa.BE.Member.Domain.CommentLike; -import Remoa.BE.Member.Domain.Member; +import Remoa.BE.Member.Domain.*; +import Remoa.BE.Member.Service.MemberService; import Remoa.BE.Post.Domain.Post; +import Remoa.BE.Post.Repository.CommentPagingRepository; +import Remoa.BE.Post.Repository.CommentLikeRepository; import Remoa.BE.Post.Repository.CommentRepository; -import Remoa.BE.Post.Repository.PostRepository; +import Remoa.BE.Post.Repository.FeedbackRepository; import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; +import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; + +import static Remoa.BE.Member.Domain.ContentType.COMMENT; @Service @Transactional(readOnly = true) @@ -19,7 +27,12 @@ public class CommentService { private final CommentRepository commentRepository; - private final PostRepository postRepository; + + private final CommentLikeRepository commentLikeRepository; + private final PostService postService; + private final CommentPagingRepository commentPagingRepository; + private final CommentFeedbackService commentFeedbackService; + private final MemberService memberService; @Transactional public Long writeComment(Comment comment) { @@ -27,8 +40,7 @@ public Long writeComment(Comment comment) { return comment.getCommentId(); } - public List loadCommentsByPostId(Long postId) { - Post post = postRepository.findByPostId(postId); + public List findAllCommentsOfPost(Post post) { return commentRepository.findByPost(post); } @@ -39,10 +51,99 @@ public Long commentLikeAction(Comment comment, Member member) { return commentLike.getCommentLikeId(); } + public Comment findOne(Long commentId) { + Optional comment = commentRepository.findOne(commentId); + return comment.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Comment not found")); + } + + public int commentLikeCount(Long commentId){ + Comment comment = findOne(commentId); + return comment.getCommentLikeCount(); + } + @Transactional public Long commentBookmarkAction(Comment comment, Member member) { CommentBookmark commentBookmark = CommentBookmark.createCommentBookmark(member, comment); commentRepository.saveCommentBookmark(commentBookmark); return commentBookmark.getCommentBookmarkId(); } -} + + @Transactional + public Comment registerComment(Member member, String comment, Long postId, Long commentId){ + + Comment parentComment = null; + + if (commentId != null) { + parentComment = commentRepository.findByCommentId(commentId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Comment not found")); + } + + Comment commentObj = new Comment(); +// String formatDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + Post post = postService.findOne(postId); + LocalDateTime time = LocalDateTime.now(); + + commentObj.setPost(post); + commentObj.setMember(member); + commentObj.setParentComment(parentComment); //대댓글인 경우 원 댓글의 Feedback, 댓글인 경우 null + commentObj.setComment(comment); + commentObj.setCommentLikeCount(0); + commentObj.setCommentedTime(time); + commentRepository.saveComment(commentObj); + + if (parentComment == null) { + commentFeedbackService.saveCommentFeedback(commentObj, null, COMMENT, member, post, time); + } + + return commentObj; + + } + + @Transactional + public void modifyComment(String comment, Long commentId){ + Comment commentObj = findOne(commentId); + commentObj.setComment(comment); + commentRepository.updateComment(commentObj); + } + + + @Transactional + public void deleteComment(Long commentId){ + Comment commentObj = findOne(commentId); + CommentFeedback commentOfCommentFeedback = commentFeedbackService.findComment(commentObj); + commentObj.setDeleted(true); + commentOfCommentFeedback.setDeleted(true); + } + + public List getRecentThreeCommentsExceptReply(Post post) { + PageRequest pageable = PageRequest.of(0, 3, Sort.by("commentedTime").descending()); + return commentPagingRepository.findByParentCommentIsNullAndPost(pageable, post).getContent(); + } + + public List getParentCommentsReply(Comment parentComment) { + return commentRepository.findRepliesOfParentComment(parentComment); + } + + public Optional findCommentLike(Long memberId, Long commentId) { + return commentLikeRepository.findByMemberMemberIdAndCommentCommentId(memberId, commentId); + } + + @Transactional + public void likeComment(Long memberId, Long commentId){ + Comment commentObj = findOne(commentId); + Integer commentLikeCount = commentObj.getCommentLikeCount(); + Member myMember = memberService.findOne(memberId); + + // CommentLike를 db에서 조회해보고 조회 결과가 null이면 like+=1, CommentLike 엔티티 생성 + // null이 아니면 like -= 1, 조회결과인 해당 CommentLike 엔티티 삭제 + Optional commentLike = findCommentLike(memberId, commentId); + if(commentLike.isEmpty()){ + commentObj.setCommentLikeCount(commentLikeCount + 1); // 좋아요 수 1 증가 + CommentLike commentLikeObj = CommentLike.createCommentLike(myMember, commentObj); + commentLikeRepository.save(commentLikeObj); + }else{ + commentObj.setCommentLikeCount(commentLikeCount - 1); // 좋아요 수 1 차감 + commentLikeRepository.deleteById(commentLike.get().getCommentLikeId()); // db에서 삭제 + } + } +} \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Post/Service/FeedbackService.java b/src/main/java/Remoa/BE/Post/Service/FeedbackService.java new file mode 100644 index 0000000..62955f7 --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Service/FeedbackService.java @@ -0,0 +1,123 @@ +package Remoa.BE.Post.Service; + +import Remoa.BE.Member.Domain.*; +import Remoa.BE.Member.Service.MemberService; +import Remoa.BE.Post.Domain.Post; +import Remoa.BE.Post.Domain.UploadFile; +import Remoa.BE.Post.Repository.FeedbackLikeRepository; +import Remoa.BE.Post.Repository.FeedbackRepository; +import Remoa.BE.Post.Repository.PostRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.server.ResponseStatusException; + +import javax.persistence.EntityNotFoundException; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import static Remoa.BE.Member.Domain.ContentType.*; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class FeedbackService { + private final FeedbackRepository feedbackRepository; + private final FeedbackLikeRepository feedbackLikeRepository; + private final PostService postService; + private final CommentFeedbackService commentFeedbackService; + + @Transactional + public Feedback findOne(Long feedbackId){ + Optional feedback = feedbackRepository.findOne(feedbackId); + return feedback.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Feedback not found")); + } + + public int feedbackLikeCount(Long feedbackId){ + Feedback feedback = findOne(feedbackId); + return feedback.getFeedbackLikeCount(); + } + + public List findAllFeedbacksOfPost(Post post) { + return feedbackRepository.findByPost(post); + } + + public List getParentFeedbacksReply(Feedback parentFeedback) { + return feedbackRepository.findRepliesOfParentFeedback(parentFeedback); + } + + public Optional findFeedbackLike(Long memberId, Long feedbackId) { + return feedbackLikeRepository.findByMemberMemberIdAndFeedbackFeedbackId(memberId, feedbackId); + } + + @Transactional + public void registerFeedback(Member member, String feedback, Long postId, Integer pageNumber, Long feedbackId){ + + Feedback parentFeedback = null; + if (feedbackId != null) { + parentFeedback = feedbackRepository.findByFeedbackId(feedbackId) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Feedback not found")); + } + + + Feedback feedbackObj = new Feedback(); + Post post = postService.findOne(postId); + + LocalDateTime time = LocalDateTime.now(); + + feedbackObj.setPost(post); + feedbackObj.setMember(member); + feedbackObj.setParentFeedback(parentFeedback); //대댓글인 경우 원 댓글의 Feedback, 댓글인 경우 null + feedbackObj.setPageNumber(pageNumber); //대댓글인 경우 null. parentFeedback.getPageNumber()통헤서 값 넣어도 됩니다. + feedbackObj.setFeedback(feedback); + feedbackObj.setFeedbackLikeCount(0); + feedbackObj.setFeedbackTime(time); + feedbackRepository.saveFeedback(feedbackObj); + + if (parentFeedback == null) { + commentFeedbackService.saveCommentFeedback(null, feedbackObj, FEEDBACK, member, post, time); + } + + } + + @Transactional + public void modifyFeedback(String feedback, Long feedbackId){ + Feedback feedbackObj = findOne(feedbackId); + feedbackObj.setFeedback(feedback); + feedbackRepository.updateFeedback(feedbackObj); + } + + @Transactional + public void deleteFeedback(Long feedbackId){ + Feedback feedbackObj = findOne(feedbackId); + CommentFeedback feedbackOfCommentFeedback = commentFeedbackService.findFeedback(feedbackObj); + feedbackObj.setDeleted(true); + feedbackOfCommentFeedback.setDeleted(true); + } + + @Transactional + public void likeFeedback(Long memberId, Member myMember, Long feedbackId){ + Feedback feedbackObj = findOne(feedbackId); + Integer feedbackLikeCount = feedbackObj.getFeedbackLikeCount(); + + //FeedbackLike를 db에서 조회해보고 조회 결과가 null이면 like+=1, FeedbackLike 엔티티 추가 + // null이 아니면 like -=1, 조회결과인 해당 FeedbackLike 엔티티 삭제 + Optional feedbackLike = findFeedbackLike(memberId, feedbackId); + if (feedbackLike.isEmpty()){ + feedbackObj.setFeedbackLikeCount(feedbackLikeCount + 1); // 좋아요 수 1 증가 + FeedbackLike feedbackLikeObj = FeedbackLike.createFeedbackLike(myMember, feedbackObj); + feedbackLikeRepository.save(feedbackLikeObj); + } else{ + feedbackObj.setFeedbackLikeCount(feedbackLikeCount - 1); // 좋아요 수 1 차감 + feedbackLikeRepository.deleteById(feedbackLike.get().getFeedbackLikeId()); // db에서 삭제 + } + } +} \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Post/Service/FileService.java b/src/main/java/Remoa/BE/Post/Service/FileService.java index 14d7553..6fa26ae 100644 --- a/src/main/java/Remoa/BE/Post/Service/FileService.java +++ b/src/main/java/Remoa/BE/Post/Service/FileService.java @@ -22,6 +22,7 @@ import java.io.InputStream; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -47,11 +48,18 @@ public class FileService { * 파일들을 저장해준다 */ @Transactional - public void saveUploadFiles(Post post,List multipartFile){ + public void saveUploadFiles(Post post,MultipartFile thumbnail, List multipartFile){ - multipartFile.forEach(item -> saveUploadFile(post,item)); + //썸네일 파일 저장 추가 + saveUploadFile(thumbnail,post,"thumbnail"); + multipartFile.forEach(file -> saveUploadFile(file, post,"post")); - post.setUploadFiles(uploadFileList); + + //새로운 인스턴스 만들어서 set하지 않으면 clear 되면서 null이 계속 저장됨. + UploadFile uploadFile = uploadFileList.get(0); + post.setThumbnail(uploadFile); + + post.setUploadFiles(new ArrayList<>(uploadFileList.subList(1, uploadFileList.size()))); postRepository.savePost(post); uploadFileList.clear(); @@ -59,18 +67,18 @@ public void saveUploadFiles(Post post,List multipartFile){ /** * - * @param post 게시글 * @param multipartFile 파일 - * saveUploadFiles 에서 파일 하나씩 가져와서 s3에 넣는다 */ @Transactional - public void saveUploadFile(Post post, MultipartFile multipartFile){ + public void saveUploadFile(MultipartFile multipartFile, Post post,String folderName){ //파일 타입과 사이즈 저장 ObjectMetadata objectMetadata = new ObjectMetadata(); objectMetadata.setContentType(multipartFile.getContentType()); objectMetadata.setContentLength(multipartFile.getSize()); + log.info(multipartFile.getContentType()); + //파일 이름 String originalFilename = multipartFile.getOriginalFilename(); @@ -82,8 +90,8 @@ public void saveUploadFile(Post post, MultipartFile multipartFile){ //파일 이름이 겹치지 않게 String uuid = UUID.randomUUID().toString(); - //postId 폴더에 따로 넣어서 보관 - String s3name = uuid+"_"+originalFilename; + //post 폴더에 따로 넣어서 보관 + String s3name = folderName+"/"+uuid+"_"+originalFilename; try (InputStream inputStream = multipartFile.getInputStream()) { amazonS3.putObject(new PutObjectRequest(bucket, s3name, inputStream, objectMetadata) @@ -97,14 +105,17 @@ public void saveUploadFile(Post post, MultipartFile multipartFile){ //파일 보관 url String storeFileUrl = amazonS3.getUrl(bucket,s3name).toString().replaceAll("\\+", "+"); UploadFile uploadFile = new UploadFile(); - uploadFile.setPost(post); uploadFile.setOriginalFileName(originalFilename); uploadFile.setSaveFileName(s3name); uploadFile.setStoreFileUrl(storeFileUrl); uploadFile.setExtension(ext); + uploadFile.setPost(post); + uploadFileList.add(uploadFile); - log.info(storeFileUrl); + uploadFileRepository.saveFile(uploadFile); + + log.info(storeFileUrl); } public String getUrl(Long fileId){ @@ -132,7 +143,8 @@ public ResponseEntity getObject(Long fileId) throws IOException { String fileOriginalName = file.get().getOriginalFileName(); //encode 메서드에 두 번째 파라메터에 StandardCharsets.UTF_8만 쓰면 오류가 나서 뒤에 name을 임사방편으로 붙임. 기능상 문제는 없을듯 함 - String fileNameFix = URLEncoder.encode(fileOriginalName, StandardCharsets.UTF_8.name()).replaceAll("\\+", "%20"); + String fileNameFix = URLEncoder.encode(fileOriginalName, StandardCharsets.UTF_8.name()) + .replaceAll("\\+", "%20"); HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM); httpHeaders.setContentLength(bytes.length); diff --git a/src/main/java/Remoa/BE/Post/Service/MyPostService.java b/src/main/java/Remoa/BE/Post/Service/MyPostService.java new file mode 100644 index 0000000..1d44545 --- /dev/null +++ b/src/main/java/Remoa/BE/Post/Service/MyPostService.java @@ -0,0 +1,104 @@ +package Remoa.BE.Post.Service; + +import Remoa.BE.Member.Domain.Member; +import Remoa.BE.Post.Domain.Post; +import Remoa.BE.Post.Repository.CategoryRepository; +import Remoa.BE.Post.Repository.PostPagingRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MyPostService { + + private final PostPagingRepository postPagingRepository; + private final CategoryRepository categoryRepository; + private static final int PAGE_SIZE = 12; + private static final int RECEIVED_COMMENT_PAGE_SIZE = 3; + + public Page sortAndPaginatePostsByMember(int pageNumber, String sort, Member myMember,String title) { + Page posts; + PageRequest pageable = PageRequest.of(pageNumber, PAGE_SIZE); + //switch문을 통해 각 옵션에 맞게 sorting + switch (sort) { + case "view": + posts = postPagingRepository.findByMemberAndTitleContainingOrderByViewsDesc(pageable, myMember,title); + break; + case "like": + posts = postPagingRepository.findByMemberAndTitleContainingOrderByLikeCountDesc(pageable, myMember,title); + break; + case "scrap": + posts = postPagingRepository.findByMemberAndTitleContainingOrderByScrapCountDesc(pageable, myMember,title); + break; + default: + //sort 문자열이 잘못됐을 경우 default인 최신순으로 정렬 + posts = postPagingRepository.findByMemberAndTitleContainingOrderByPostingTimeDesc(pageable, myMember,title); + break; + } + return posts; + } + + public Page sortAndPaginatePostsByCategoryAndMember(String category, int pageNumber, String sort, Member myMember,String title) { + Page posts; + PageRequest pageable; + switch (sort) { + case "view": + pageable = PageRequest.of(pageNumber, PAGE_SIZE, Sort.by("views").descending()); + break; + case "like": + pageable = PageRequest.of(pageNumber, PAGE_SIZE, Sort.by("likeCount").descending()); + break; + case "scrap": + pageable = PageRequest.of(pageNumber, PAGE_SIZE, Sort.by("scrapCount").descending()); + break; + default: + //sort 문자열이 잘못됐을 경우 default인 최신순으로 정렬 + pageable = PageRequest.of(pageNumber, PAGE_SIZE, Sort.by("postingTime").descending()); + break; + } + posts = postPagingRepository.findByMemberAndCategoryAndTitleContaining(pageable, myMember, categoryRepository.findByCategoryName(category),title); + return posts; + } + + /** + * 받은 피드백 관리에서 쓰이는 최신 3개순 포스트 + * @param page + * @param member + * @param category + * @return member가 작성한 최신 3개의 Post. + */ + public Page getNewestThreePostsSortCategory(int page, Member member, String category) { + PageRequest pageable = PageRequest.of(page, RECEIVED_COMMENT_PAGE_SIZE, Sort.by("postingTime").descending()); + return postPagingRepository.findByMemberAndCategoryAndCommentsIsNotEmpty(pageable, member, categoryRepository.findByCategoryName(category)); + } + + /** + * 받은 피드백 관리에서 쓰이는 최신 3개순 포스트 + * @param page + * @param member + * @return member가 작성한 최신 3개의 Post. + */ + public Page getNewestThreePosts(int page, Member member) { + PageRequest pageable = PageRequest.of(page, RECEIVED_COMMENT_PAGE_SIZE, Sort.by("postingTime").descending()); + return postPagingRepository.findByMemberAndCommentsIsNotEmpty(pageable, member); + } + + /** + * 내 활동 관리에 쓰이는 코멘트 및 피드백을 단 작업물 + * @param size + * @param member + * @return Post + */ + public Page getCommentedPost(int size, Member member) { + PageRequest pageable = PageRequest.of(0, size, Sort.by("postingTime").descending()); + return postPagingRepository.findByMemberAndCommentsIsNotEmpty(pageable, member); + } + +} \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Post/Service/PostService.java b/src/main/java/Remoa/BE/Post/Service/PostService.java index 9f962fa..2869ca0 100644 --- a/src/main/java/Remoa/BE/Post/Service/PostService.java +++ b/src/main/java/Remoa/BE/Post/Service/PostService.java @@ -1,56 +1,232 @@ package Remoa.BE.Post.Service; import Remoa.BE.Member.Domain.Member; +import Remoa.BE.Member.Service.MemberService; import Remoa.BE.Post.Domain.Category; import Remoa.BE.Post.Domain.Post; -import Remoa.BE.Post.Domain.UploadFile; -import Remoa.BE.Post.Repository.PostRepository; -import Remoa.BE.Post.Repository.UploadFileRepository; -import Remoa.BE.Post.Repository.CategoryRepository; -import Remoa.BE.Post.form.Request.UploadPostForm; +import Remoa.BE.Post.Domain.PostLike; +import Remoa.BE.Post.Domain.PostScarp; +import Remoa.BE.Post.Dto.Request.UploadPostForm; +import Remoa.BE.Post.Repository.*; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.server.ResponseStatusException; +import java.io.IOException; +import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import static Remoa.BE.utill.FileExtension.fileExtension; @Slf4j @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class PostService { - private final UploadFileRepository uploadFileRepository; + + private final MemberService memberService; private final PostRepository postRepository; private final CategoryRepository categoryRepository; - private final List uploadFileList; - private final FileService fileService; - public Post dtoToEntity(UploadPostForm uploadPostForm, Member member){ // dto를 db에 저장하기 위해 entity로 변환 + private final PostPagingRepository postPagingRepository; + + private final PostScrapRepository postScrapRepository; + private final PostLikeRepository postLikeRepository; + + private static final int HOME_PAGE_SIZE = 12; + + public Post findOne(Long postId) { + Optional post = postRepository.findOne(postId); + return post.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Post not found")); + } + + @Transactional + public Post findOneViewPlus(Long postId) { + Optional findPost = postRepository.findOne(postId); + findPost.ifPresent(post -> post.setViews(post.getViews() + 1)); + return findPost.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Post not found")); + } + + public int findScrapCount(Long postId){ + Post findPost = findOne(postId); + return findPost.getPostScarps().size(); + } + + public int findLikeCount(Long postId){ + Post findPost = findOne(postId); + return findPost.getPostLikes().size(); + } + + @Transactional + public void likePost(Long memberId, Member myMember, Long referenceId) { + Post post = findOne(referenceId); + Integer postLikeCount = post.getLikeCount(); // 이 게시물을 좋아요한 수 + + PostLike postLike = postLikeRepository.findByMemberMemberIdAndPostPostId(memberId, referenceId); + if (postLike == null) { + post.setLikeCount(postLikeCount + 1); // 좋아요 + 1 + PostLike postLikeObj = PostLike.createPostLike(myMember, post); + postLikeRepository.save(postLikeObj); + } else { + post.setLikeCount(post.getLikeCount() - 1); // 좋아요 + 1 + postLikeRepository.deleteById(postLike.getPostLikeId()); + } + } + + @Transactional + public Post registerPost(UploadPostForm uploadPostForm, MultipartFile thumbnail, List uploadFiles, Long memberId) throws IOException { - // String category를 이용해 Category 엔티티를 찾기 Category category = categoryRepository.findByCategoryName(uploadPostForm.getCategory()); - return Post.builder() - .title(uploadPostForm.getTitle()) - .member(member) - .contestName(uploadPostForm.getContestName()) - .category(category) - .contestAwareType(uploadPostForm.getContestAward()) - .build(); + Member member = memberService.findOne(memberId); + + + //확장자 확인 + String extension = fileExtension(uploadFiles.get(0)); + if (extension.equals("pdf") || extension.equals("jpg") || extension.equals("png")) { + int pageCount; + if (extension.equals("pdf")) { + PDDocument document = PDDocument.load(uploadFiles.get(0).getInputStream()); + pageCount = document.getNumberOfPages(); + } else { + pageCount = uploadFiles.size(); + } + Post post = Post.builder() + .title(uploadPostForm.getTitle()) + .member(member) + .contestName(uploadPostForm.getContestName()) + .category(category) + .youtubeLink(uploadPostForm.getYoutubeLink()) + .contestAwardType(uploadPostForm.getContestAwardType()) + .pageCount(pageCount) + .postingTime(LocalDateTime.now()) + .likeCount(0) + .views(0) + .scrapCount(0) + .deleted(false) + .build(); + + fileService.saveUploadFiles(post, thumbnail, uploadFiles); + return post; + } else { + throw new IOException(); + } + + + } + + public Page sortAndPaginatePosts(String sort, int pageNumber, String title) { + Page Posts; + Pageable pageable; + switch (sort) { + case "like": + pageable = PageRequest.of(pageNumber, HOME_PAGE_SIZE, Sort.by("likeCount").descending()); + Posts = postPagingRepository.findByTitleContaining(pageable, title); + break; + case "scrap": + pageable = PageRequest.of(pageNumber, HOME_PAGE_SIZE, Sort.by("scrapCount").descending()); + Posts = postPagingRepository.findByTitleContaining(pageable, title); + break; + case "view": + pageable = PageRequest.of(pageNumber, HOME_PAGE_SIZE, Sort.by("views").descending()); + Posts = postPagingRepository.findByTitleContaining(pageable, title); + break; + default: + //sort 문자열이 잘못됐을 경우 default인 최신순으로 정렬 + pageable = PageRequest.of(pageNumber, HOME_PAGE_SIZE, Sort.by("postingTime").descending()); + Posts = postPagingRepository.findByTitleContaining(pageable, title); + break; + } + return Posts; + } + + public Page sortAndPaginatePostsByCategory(String category, String sort, int pageNumber, String title) { + Page Posts; + Pageable pageable; + switch (sort) { + case "like": + pageable = PageRequest.of(pageNumber, HOME_PAGE_SIZE, Sort.by("likeCount").descending()); + break; + case "scrap": + pageable = PageRequest.of(pageNumber, HOME_PAGE_SIZE, Sort.by("scrapCount").descending()); + break; + case "view": + pageable = PageRequest.of(pageNumber, HOME_PAGE_SIZE, Sort.by("views").descending()); + break; + default: + //sort 문자열이 잘못됐을 경우 default인 최신순으로 정렬 + pageable = PageRequest.of(pageNumber, HOME_PAGE_SIZE, Sort.by("postingTime").descending()); + break; + } + Posts = postPagingRepository.findByCategoryAndTitleContaining(pageable, categoryRepository.findByCategoryName(category), title); + return Posts; + } + + public PostScarp getPostScrapByMemberIdAndPostId(Long memberId, Long postId) { + return postScrapRepository.findByMemberMemberIdAndPostPostId(memberId, postId); } @Transactional - public void registPost(UploadPostForm uploadPostForm, List uploadFiles, Member member){ + public void scrapPost(Long memberId, Member myMember, Long referenceId) { + Post post = findOne(referenceId); + Integer postScrapCount = post.getScrapCount(); // 이 게시물을 스크랩한 수 + + // scrapPost를 db에서 조회해보고 조회 결과가 null이면 scrapCount += 1, PostScrap 생성 + // null이 아니면 scrapCount -= 1, 조회결과인 해당 PostScrap 삭제 + PostScarp postScarp = getPostScrapByMemberIdAndPostId(memberId, referenceId); + if (postScarp == null) { + post.setScrapCount(postScrapCount + 1); // 스크랩 수 1 증가 + PostScarp postScrapObj = PostScarp.createPostScrap(myMember, post); + postScrapRepository.save(postScrapObj); + } else { + post.setScrapCount(post.getScrapCount() - 1); // 스크랩 수 1 차감 + postScrapRepository.deleteById(postScarp.getPostScrapId()); // db에서 삭제 + } + } - Post post = dtoToEntity(uploadPostForm, member); - postRepository.savePost(post); - fileService.saveUploadFiles(post, uploadFiles); + public Page findScrapedPost(int page, Member member) { + Pageable pageable = PageRequest.of(page, HOME_PAGE_SIZE); + return postScrapRepository.findByMemberOrderByScrapTimeDesc(pageable, member); } -} + public List findRecentTwelveScrapedPost(Member member) { + return postPagingRepository.findByMemberRecentTwelve(member); + } + + public boolean checkMemberPost(Member myMember, Long postId){ + // postId를 이용해 Post 엔티티에 등록된 Member를 불러오고 이 Member가 컨트롤러에서 불러온 Member와 맞는지 확인하고 true/false 반환 + Post post = findOne(postId); + Member member = post.getMember(); + return myMember == member; + } + + public boolean isThisPostScraped(Member myMember) { + return postRepository.findScrapedPost(myMember).isPresent(); + } + + public boolean isThisPostLiked(Member myMember) { + return postRepository.findLikedPost(myMember).isPresent(); + } + + + @Transactional + public void deleteReference(Long postId){ + postRepository.deletePost(postId); + } +} \ No newline at end of file diff --git a/src/main/java/Remoa/BE/Post/form/Request/UploadPostForm.java b/src/main/java/Remoa/BE/Post/form/Request/UploadPostForm.java deleted file mode 100644 index bcecf18..0000000 --- a/src/main/java/Remoa/BE/Post/form/Request/UploadPostForm.java +++ /dev/null @@ -1,27 +0,0 @@ -package Remoa.BE.Post.form.Request; - -import Remoa.BE.Post.Domain.Category; -import Remoa.BE.Post.Domain.Post; -import lombok.Builder; -import lombok.Getter; -import lombok.Setter; -import org.springframework.web.multipart.MultipartFile; - -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.Size; -import java.util.List; - -@Getter -@Setter -public class UploadPostForm { - - private String title; // Post name - - private String contestName; - - private String category; // Category name - - private String contestAward; - - //private List uploadFiles; -} diff --git a/src/main/java/Remoa/BE/config/AppConfig.java b/src/main/java/Remoa/BE/config/AppConfig.java new file mode 100644 index 0000000..d930292 --- /dev/null +++ b/src/main/java/Remoa/BE/config/AppConfig.java @@ -0,0 +1,13 @@ +package Remoa.BE.config; + +import org.modelmapper.ModelMapper; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AppConfig { + @Bean + public ModelMapper modelMapper() { + return new ModelMapper(); + } +} \ No newline at end of file diff --git a/src/main/java/Remoa/BE/config/CorsConfig.java b/src/main/java/Remoa/BE/config/CorsConfig.java index a369d88..ade0a09 100644 --- a/src/main/java/Remoa/BE/config/CorsConfig.java +++ b/src/main/java/Remoa/BE/config/CorsConfig.java @@ -11,14 +11,16 @@ public class CorsConfig { @Bean public CorsFilter corsFilter() { + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); - config.addAllowedOrigin("*"); + config.addAllowedOriginPattern("*"); // addAllowedOriginPattern("*") 대신 사용 config.addAllowedHeader("*"); config.addAllowedMethod("*"); + source.registerCorsConfiguration("/**", config); - source.registerCorsConfiguration("*", config); return new CorsFilter(source); } } \ No newline at end of file diff --git a/src/main/java/Remoa/BE/config/DbInit.java b/src/main/java/Remoa/BE/config/DbInit.java index e1350be..98462bf 100644 --- a/src/main/java/Remoa/BE/config/DbInit.java +++ b/src/main/java/Remoa/BE/config/DbInit.java @@ -1,6 +1,5 @@ package Remoa.BE.config; -import Remoa.BE.Member.Domain.Role; import Remoa.BE.Post.Domain.Category; import Remoa.BE.Member.Domain.Member; import Remoa.BE.Post.Service.CategoryService; diff --git a/src/main/java/Remoa/BE/config/QueryDslConfig.java b/src/main/java/Remoa/BE/config/QueryDslConfig.java new file mode 100644 index 0000000..61169fe --- /dev/null +++ b/src/main/java/Remoa/BE/config/QueryDslConfig.java @@ -0,0 +1,20 @@ +package Remoa.BE.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + +@Configuration +public class QueryDslConfig { + + @PersistenceContext + private EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } +} diff --git a/src/main/java/Remoa/BE/config/SecureConfig.java b/src/main/java/Remoa/BE/config/SecureConfig.java index 8bff517..2c60b10 100644 --- a/src/main/java/Remoa/BE/config/SecureConfig.java +++ b/src/main/java/Remoa/BE/config/SecureConfig.java @@ -15,7 +15,7 @@ @Configuration public class SecureConfig { - private final CorsFilter corsFilter; + /** * Spring Security에서 사용할 password encoder로 BCryptPasswordEncoder 지정 @@ -31,7 +31,6 @@ public PasswordEncoder passwordEncoder() { @Bean public SecurityFilterChain configure(HttpSecurity http) throws Exception { - http.addFilter(corsFilter); http.formLogin().disable(); diff --git a/src/main/java/Remoa/BE/exception/CustomMessage.java b/src/main/java/Remoa/BE/exception/CustomMessage.java index e1da644..28d1ba5 100644 --- a/src/main/java/Remoa/BE/exception/CustomMessage.java +++ b/src/main/java/Remoa/BE/exception/CustomMessage.java @@ -2,13 +2,16 @@ import lombok.Getter; import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; @Getter public enum CustomMessage { //200 정상처리 상태코드 OK(HttpStatus.OK,"올바른 요청","정상적으로 처리되었습니다"), + OK_DUPLICATE(HttpStatus.OK,"올바른 요청","닉네임이 중복되었습니다"), + + OK_UN_DUPLICATE(HttpStatus.OK,"올바른 요청","사용가능한 닉네임 입니다"), + OK_UNFOLLOW(HttpStatus.OK,"올바른 요청","회원을 언팔로잉 합니다"), //201 한 api에서 정상처리상 구분이 필요할떄 사용 @@ -18,14 +21,23 @@ public enum CustomMessage { //400 잘못된 요청 VALIDATED(HttpStatus.BAD_REQUEST,"잘못된 요청","요청한 값이 유효성검사를 통과하지 못했습니다"), - NO_MEMBER(HttpStatus.BAD_REQUEST,"잘못된 요청","요청한 memberId가 존재하지 않습니다"), + NO_ID(HttpStatus.BAD_REQUEST,"잘못된 요청","요청한 Id가 존재하지 않습니다"), - //401권한오류 - UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"권한이 없습니다","인증에 필요한 쿠키 정보가 없습니다"), + FOLLOW_ME(HttpStatus.BAD_REQUEST,"서비스 로직상 오류","자신을 팔로우할 수 없습니다"), + + BAD_DUPLICATE(HttpStatus.BAD_REQUEST,"서비스 로직상 오류","닉네임이 중복되었습니다"), - //409 상태 충돌 - FOLLOW_ME(HttpStatus.CONFLICT,"서비스 로직상 오류","자신을 팔로우할 수 없습니다"); + BAD_PROFILE_IMG(HttpStatus.BAD_REQUEST,"서비스 로직상 오류","해당 멤버의 프로필사진이 존재하지 않습니다"), + BAD_FILE(HttpStatus.BAD_REQUEST,"서비스 로직상 오류","해당 파일 유형은 지원하지 않습니다"), + + BAD_PAGE_NUM(HttpStatus.BAD_REQUEST, "피드백 등록시 페이지 넘버 오류", "존재하지 않는 페이지에 피드백을 등록하려 합니다"), + + PAGE_NUM_OVER(HttpStatus.BAD_REQUEST, "레퍼런스 조회시 페이지 넘버 오류", "올바르지 않는 페이지 번호입니다."), + //401권한오류 + UNAUTHORIZED(HttpStatus.UNAUTHORIZED,"권한이 없습니다","인증에 필요한 쿠키 정보가 없습니다"), + // 403 권한오류 + CAN_NOT_ACCESS(HttpStatus.FORBIDDEN, "권한이 없습니다","다른 사람이 작성한 글을 삭제할 수 없습니다"); private final HttpStatus httpStatus; private final String message; diff --git a/src/main/java/Remoa/BE/exception/CustomizedExceptionHandler.java b/src/main/java/Remoa/BE/exception/CustomizedExceptionHandler.java index 922f7f7..3fc7279 100644 --- a/src/main/java/Remoa/BE/exception/CustomizedExceptionHandler.java +++ b/src/main/java/Remoa/BE/exception/CustomizedExceptionHandler.java @@ -1,6 +1,5 @@ package Remoa.BE.exception; -import Remoa.BE.exception.response.FailResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -8,6 +7,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.server.ResponseStatusException; +import java.io.IOException; import java.util.HashMap; import java.util.Map; @@ -31,6 +31,12 @@ public ResponseEntity methodValidException(MethodArgumentNotValidExcepti @ExceptionHandler public ResponseEntity responseStatusException(ResponseStatusException ex){ - return errorResponse(CustomMessage.NO_MEMBER); + return errorResponse(CustomMessage.NO_ID); + } + + //파일 유형 에러 + @ExceptionHandler + public ResponseEntity ioException(IOException ex){ + return errorResponse(CustomMessage.BAD_FILE); } } diff --git a/src/main/java/Remoa/BE/utill/FileExtension.java b/src/main/java/Remoa/BE/utill/FileExtension.java new file mode 100644 index 0000000..ffcc1df --- /dev/null +++ b/src/main/java/Remoa/BE/utill/FileExtension.java @@ -0,0 +1,13 @@ +package Remoa.BE.utill; + +import org.springframework.web.multipart.MultipartFile; + +public class FileExtension { + public static String fileExtension(MultipartFile multipartFile){ + String fileName = multipartFile.getOriginalFilename(); + assert fileName != null; + int lastIndex = fileName.lastIndexOf("."); + return fileName.substring(lastIndex + 1); + } + +} diff --git a/src/main/java/Remoa/BE/utill/MemberInfo.java b/src/main/java/Remoa/BE/utill/MemberInfo.java index b04ff16..4ae2b6b 100644 --- a/src/main/java/Remoa/BE/utill/MemberInfo.java +++ b/src/main/java/Remoa/BE/utill/MemberInfo.java @@ -1,6 +1,7 @@ package Remoa.BE.utill; import Remoa.BE.Member.Domain.Member; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -16,6 +17,7 @@ import java.util.List; import java.util.Optional; +@Slf4j public class MemberInfo { public static Long getMemberId() { Member member = (Member) SecurityContextHolder.getContext().getAuthentication().getPrincipal();