diff --git a/server/.gitignore b/server/.gitignore index 3ac33f5..390b86a 100755 --- a/server/.gitignore +++ b/server/.gitignore @@ -269,15 +269,14 @@ gradle-app.setting src/main/generated/** -src/main/resources/application-prd.yml -src/main/resources/application.properties +application-prd.yml +application-swagger.yml +application.properties ## logs ## logs/ -### security information -application.yml ### operation log replay_pid.*.log diff --git a/server/README.md b/server/README.md index d8b90ed..0c3e0d6 100644 --- a/server/README.md +++ b/server/README.md @@ -2,6 +2,8 @@ * http://localhost:8080/swagger-ui/index.html +* http://localhost:8080/test + # 수동 배포 방법 정리.(개선 필요.) ```sh diff --git a/server/build.gradle b/server/build.gradle index 1eaa208..c5aeba9 100644 --- a/server/build.gradle +++ b/server/build.gradle @@ -61,6 +61,8 @@ dependencies { // https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 implementation group: 'org.apache.commons', name: 'commons-collections4', version: '4.4' + implementation group: 'commons-fileupload', name: 'commons-fileupload', version: '1.4' + implementation group: 'commons-io', name: 'commons-io', version: '2.4' // if (profile == "localh2") { // runtimeOnly 'com.h2database:h2' @@ -69,8 +71,6 @@ dependencies { // } runtimeOnly 'com.mysql:mysql-connector-j' - runtimeOnly 'com.mysql:mysql-connector-j' - annotationProcessor 'org.projectlombok:lombok' // spring boot 3.0 query dsl setting. @@ -82,7 +82,8 @@ dependencies { // p6spy : sql 로그 남기기(바인딩된 파라미터 간편 확인.) implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0' - testImplementation 'org.springframework.boot:spring-boot-starter-test' +// testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter-test:3.0.2' } diff --git a/server/scripts/deploy-swagger.sh b/server/scripts/deploy-swagger.sh new file mode 100755 index 0000000..c39702f --- /dev/null +++ b/server/scripts/deploy-swagger.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# swagger +REPOSITORY=/home/bside/311TEN003_for_swagger + +# local +#REPOSITORY=/Users/dongseoklee/github/311TEN003 + +# common +PROJECT_LOCATION_FOLDER_NAME=server +PROJECT_NAME=bside_311 + +cd $REPOSITORY/$PROJECT_LOCATION_FOLDER_NAME/ + +echo "> Git reset --hard" +git reset --hard + +echo "> Git Pull" + +git pull + +echo "Release Version Updated" +#grep "^Release" ./releasenote.txt | tail -1 > ./src/main/frontend/public/latestReleaseVer.txt + + +echo "> gradlew, deploy-swagger.sh 권한 변경 " +chmod 777 gradlew +chmod 774 scripts/deploy-swagger.sh + +echo "> 프로젝트 Build 시작" +./gradlew build --exclude-task test + +echo "> Build 파일 복사" + +cp ./build/libs/*.jar $REPOSITORY/ + +echo "> 현재 구동중인 애플리케이션 pid 확인" + +#CURRENT_PID=$(pgrep -f ${PROJECT_NAME}.*.jar) +CURRENT_PID=$(pgrep -f "active=swagger") + +echo "$CURRENT_PID" + +if [ -z "$CURRENT_PID" ]; then + echo "> 현재 구동중인 애플리케이션이 없으므로 종료하지 않습니다." +else + echo "> kill -2 $CURRENT_PID" + kill -9 "$CURRENT_PID" + sleep 10 +fi + +echo "> 새 어플리케이션 배포" + +# JAR_NAME=$(ls $REPOSITORY/ |grep jar | tail -n 1) +JAR_NAME=$(ls $REPOSITORY/ |grep ${PROJECT_NAME}.*.jar | tail -n 1) + +echo "> JAR Name: $JAR_NAME" + +nohup java -jar $REPOSITORY/"$JAR_NAME" --spring.profiles.active=swagger & + diff --git a/server/scripts/deploy.sh b/server/scripts/deploy.sh index 2c0c644..8ad7442 100755 --- a/server/scripts/deploy.sh +++ b/server/scripts/deploy.sh @@ -36,7 +36,8 @@ cp ./build/libs/*.jar $REPOSITORY/ echo "> 현재 구동중인 애플리케이션 pid 확인" -CURRENT_PID=$(pgrep -f ${PROJECT_NAME}.*.jar) +#CURRENT_PID=$(pgrep -f ${PROJECT_NAME}.*.jar) +CURRENT_PID=$(pgrep -f "active=prd") echo "$CURRENT_PID" diff --git a/server/src/main/java/com/bside/bside_311/Bside311Application.java b/server/src/main/java/com/bside/bside_311/Bside311Application.java index 62c1b73..35e9f36 100644 --- a/server/src/main/java/com/bside/bside_311/Bside311Application.java +++ b/server/src/main/java/com/bside/bside_311/Bside311Application.java @@ -5,27 +5,25 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.data.domain.AuditorAware; -import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import java.util.Optional; -@EnableJpaAuditing @SpringBootApplication public class Bside311Application { - public static void main(String[] args) { - SpringApplication.run(Bside311Application.class, args); - } + public static void main(String[] args) { + SpringApplication.run(Bside311Application.class, args); + } - @Bean - public AuditorAware auditorProvider() { - return () -> { - Long userNoFromAuthentication = AuthUtil.getUserNoFromAuthentication(); - if (userNoFromAuthentication == null) { - return Optional.empty(); - } - return Optional.of(userNoFromAuthentication); - }; - } + @Bean + public AuditorAware auditorProvider() { + return () -> { + Long userNoFromAuthentication = AuthUtil.getUserNoFromAuthentication(); + if (userNoFromAuthentication == null) { + return Optional.empty(); + } + return Optional.of(userNoFromAuthentication); + }; + } } diff --git a/server/src/main/java/com/bside/bside_311/PostDomain.java b/server/src/main/java/com/bside/bside_311/PostDomain.java new file mode 100644 index 0000000..e22221f --- /dev/null +++ b/server/src/main/java/com/bside/bside_311/PostDomain.java @@ -0,0 +1,10 @@ +package com.bside.bside_311; + +import com.bside.bside_311.entity.Post; + +public class PostDomain extends Post { + + public static PostDomain of(Post post) throws CloneNotSupportedException { + return (PostDomain) Post.of(post); + } +} diff --git a/server/src/main/java/com/bside/bside_311/PostDomainMultiple.java b/server/src/main/java/com/bside/bside_311/PostDomainMultiple.java new file mode 100644 index 0000000..51e5df7 --- /dev/null +++ b/server/src/main/java/com/bside/bside_311/PostDomainMultiple.java @@ -0,0 +1,48 @@ +package com.bside.bside_311; + +import com.bside.bside_311.dto.GetPostVo; +import com.bside.bside_311.dto.GetPostsMvo; +import com.bside.bside_311.dto.PostResponseDto; +import com.bside.bside_311.entity.Post; +import com.bside.bside_311.repository.PostMybatisRepository; +import com.bside.bside_311.repository.PostRepository; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@Builder +@Setter +@AllArgsConstructor +public class PostDomainMultiple { + private final PostRepository postRepository; + private final PostMybatisRepository postMybatisRepository; + List postDomainList; +// List postDomainList; + Long totalCount; + + + + public static PostDomainMultiple init(PostRepository postRepository, PostMybatisRepository postMybatisRepository){ + return PostDomainMultiple.builder().postRepository(postRepository).postMybatisRepository(postMybatisRepository).build(); + } + + public static PostDomainMultiple of(GetPostVo getPostVo, PostRepository postRepository, PostMybatisRepository postMybatisRepository) { + PostDomainMultiple postDomainMultiple = init(postRepository, postMybatisRepository); + List getPostsMvos = postMybatisRepository.getPosts(getPostVo); + Long totalCount = postMybatisRepository.getPostsCount(getPostVo); + List posts = getPostsMvos.stream().map(Post::of).toList(); + List list = posts.stream().map(post -> { + try { + return PostDomain.of(post); + } catch (CloneNotSupportedException e) { + throw new RuntimeException(e); + } + }).toList(); + postDomainMultiple.setPostDomainList(list); + postDomainMultiple.setTotalCount(totalCount); + return postDomainMultiple; + } +} diff --git a/server/src/main/java/com/bside/bside_311/config/JpaConfiguration.java b/server/src/main/java/com/bside/bside_311/config/JpaConfiguration.java new file mode 100644 index 0000000..ddb3a52 --- /dev/null +++ b/server/src/main/java/com/bside/bside_311/config/JpaConfiguration.java @@ -0,0 +1,11 @@ +package com.bside.bside_311.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@Profile("!test") +@Configuration +@EnableJpaAuditing +public class JpaConfiguration { +} diff --git a/server/src/main/java/com/bside/bside_311/controller/AlcoholController.java b/server/src/main/java/com/bside/bside_311/controller/AlcoholController.java index c3e1bed..2650800 100644 --- a/server/src/main/java/com/bside/bside_311/controller/AlcoholController.java +++ b/server/src/main/java/com/bside/bside_311/controller/AlcoholController.java @@ -14,6 +14,8 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; @@ -88,6 +90,16 @@ public GetAlcoholResponseDto getAlcohol( return alcoholService.getAlcohol(page, size, orderColumn, orderType, searchKeyword); } + @Operation(summary = "[o]술 목록 조회v2(성능 최적화)", description = "술 조회 API Page, size 사용법.
ex1) /alcohols/v2?page=0&size=10&sort=id,desc
ex2) /alcohols/v2?page=0&size=10&sort=id,desc&sort=content,asc&searchKeyword=키워드&searchUserNos=1,2,4") + @GetMapping("/v2") + public Page getAlcoholV2( +// @Schema(description = "페이지 번호와 사이즈.정렬 까지.(0부터) ex)[1]page=0&size=5&sort=id,desc [2]page=1&size=15&sort=id,desc&sort=content,asc", example = "0") + Pageable pageable, + @Schema(description = "알코올 키워드", example = "키워드") String searchKeyword) { + log.info(">>> AlcoholController.getAlcohol"); + return alcoholService.getAlcoholV2(pageable, searchKeyword); + } + @Operation(summary = "[o]술 상세 조회", description = "술 상세 조회 API") @GetMapping("/{alcoholNo}") public AlcoholResponseDto getAlcoholDetail(@PathVariable("alcoholNo") Long alcoholNo) { diff --git a/server/src/main/java/com/bside/bside_311/controller/PostController.java b/server/src/main/java/com/bside/bside_311/controller/PostController.java index 26beafd..dc415b6 100644 --- a/server/src/main/java/com/bside/bside_311/controller/PostController.java +++ b/server/src/main/java/com/bside/bside_311/controller/PostController.java @@ -22,7 +22,10 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; +import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -35,6 +38,10 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + @Slf4j @Validated @RestController @@ -51,8 +58,7 @@ public class PostController { public AddPostResponseDto addPost(@RequestBody @Valid AddPostRequestDto addPostRequestDto) { log.info(">>> PostController.addPost"); return postService.addPost(Post.of(addPostRequestDto), addPostRequestDto.getAlcoholNo(), - addPostRequestDto.getAlcoholFeature(), addPostRequestDto.getTagList(), - addPostRequestDto.getAlcoholInfo()); + addPostRequestDto.getAlcoholFeature(), addPostRequestDto.getTagList()); } @Operation(summary = "[o]게시글 수정", description = "게시글 수정 API") @@ -74,7 +80,7 @@ public void deletePost(@PathVariable("postNo") Long postNo) { postService.deletePost(postNo); } - @Operation(summary = "[o]게시글 목록 조회", description = "게시글 조회 API") + @Operation(summary = "[o]게시글 목록 조회(v1)", description = "게시글 조회 API") @GetMapping public GetPostResponseDto getPosts(@RequestParam(name = "page", defaultValue = "0") @Schema(description = "페이지번호(0부터), 기본값 0.", example = "0") @@ -83,16 +89,69 @@ public GetPostResponseDto getPosts(@RequestParam(name = "page", defaultValue = " @Schema(description = "사이즈, 기본값 10.", example = "10") Long size, @RequestParam(required = false, name = "orderColumn") - @Schema(description = "정렬 컬럼", example = "alcohol_no") + @Schema(description = "정렬 컬럼", example = "post_no") String orderColumn, @RequestParam(required = false, name = "orderType") @Schema(description = "정렬 타입", example = "DESC") String orderType, @RequestParam(required = false, name = "searchKeyword") @Schema(description = "키워드", example = "키워드") - String searchKeyword) { + String searchKeyword, + @RequestParam(required = false, name = "searchUserNos") + @Schema(description = "검색 유저 번호들.", example = "1,2,4") + String searchUserNos + + ) { + log.info(">>> PostController.getPost"); + List searchUserNoList = new ArrayList<>(); + try { + searchUserNoList = + Arrays.stream(searchUserNos.split(",")).map(Long::parseLong).toList(); + } catch (NumberFormatException e) { + log.error(">>> PostController.getPost searchUserNos 파싱 에러 NumberFormatException", e); + } catch (Exception e) { + log.error(">>> PostController.getPost searchUserNos 파싱 에러 Exception", e); + } + return postService.getPosts(page, size, orderColumn, orderType, searchKeyword, + searchUserNoList); + } + + @Operation(summary = "[o]게시글 목록 조회(v2)", description = "게시글 조회 API Page, size 사용법.
ex1) /posts/v2?page=0&size=10&sort=id,desc
ex2) /posts/v2?page=0&size=10&sort=id,desc&sort=content,asc&searchKeyword=키워드&searchUserNos=1,2,4") + @GetMapping("/v2") + public Page getPostsV2( +// @Schema(description = "페이지 번호와 사이즈.정렬 까지.(0부터) ex)[1]page=0&size=5&sort=id,desc [2]page=1&size=15&sort=id,desc&sort=content,asc", example = "0") + Pageable pageable, + @RequestParam(required = false, name = "searchKeyword") + @Schema(description = "키워드", example = "키워드") + String searchKeyword, + @RequestParam(required = false, name = "searchUserNos") + @Schema(description = "검색 유저 번호들.", example = "1,2,4") + String searchUserNos, + @RequestParam(required = false, name = "isLikedByMe") + @Schema(description = "나에 의해서 좋아하는 게시글 필터 여부(true or false).", example = "false") + Boolean isLikedByMe, + @RequestParam(required = false, name = "isCommentedByMe") + @Schema(description = "내가 댓글을 단 게시글 필터 여부.(true or false)", example = "false") + Boolean isCommentedByMe + + ) { log.info(">>> PostController.getPost"); - return postService.getPosts(page, size, orderColumn, orderType, searchKeyword); + List searchUserNoList = new ArrayList<>(); + if (StringUtils.hasText(searchUserNos)) { + try { + searchUserNoList = + Arrays.stream(searchUserNos.split(",")).map(Long::parseLong).toList(); + } catch (NumberFormatException e) { + log.error(">>> PostController.getPost searchUserNos 파싱 에러 NumberFormatException", e); + throw new IllegalArgumentException("searchUserNos 파싱 에러 NumberFormatException", e); + } catch (Exception e) { + log.error(">>> PostController.getPost searchUserNos 파싱 에러 Exception", e); + throw new IllegalArgumentException("searchUserNos 파싱 에러 Exception", e); + } + } + + return postService.getPostsV2(pageable, searchKeyword, searchUserNoList, isLikedByMe, + isCommentedByMe); } @Operation(summary = "[o]게시글 상세 조회", description = "게시글 상세 조회 API") diff --git a/server/src/main/java/com/bside/bside_311/controller/TagController.java b/server/src/main/java/com/bside/bside_311/controller/TagController.java index d8ce2e5..e50c403 100644 --- a/server/src/main/java/com/bside/bside_311/controller/TagController.java +++ b/server/src/main/java/com/bside/bside_311/controller/TagController.java @@ -1,20 +1,14 @@ package com.bside.bside_311.controller; import com.bside.bside_311.dto.SearchTagResponseDto; -import com.bside.bside_311.dto.UserSignupRequestDto; -import com.bside.bside_311.dto.UserSignupResponseDto; -import com.bside.bside_311.entity.User; import com.bside.bside_311.service.TagService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; diff --git a/server/src/main/java/com/bside/bside_311/controller/UserController.java b/server/src/main/java/com/bside/bside_311/controller/UserController.java index ac53bff..a7989d7 100644 --- a/server/src/main/java/com/bside/bside_311/controller/UserController.java +++ b/server/src/main/java/com/bside/bside_311/controller/UserController.java @@ -7,12 +7,12 @@ import com.bside.bside_311.dto.MyInfoResponseDto; import com.bside.bside_311.dto.UserFollowResponseDto; import com.bside.bside_311.dto.UserLoginRequestDto; +import com.bside.bside_311.dto.UserResponseDto; import com.bside.bside_311.dto.UserSignupRequestDto; import com.bside.bside_311.dto.UserSignupResponseDto; import com.bside.bside_311.dto.UserUpdateRequestDto; import com.bside.bside_311.entity.Role; import com.bside.bside_311.entity.User; -import com.bside.bside_311.repository.UserMybatisRepository; import com.bside.bside_311.service.UserService; import com.bside.bside_311.util.AuthUtil; import io.swagger.v3.oas.annotations.Operation; @@ -20,8 +20,9 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.http.HttpStatus; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -39,7 +40,6 @@ @Tag(name = "유저", description = "유저 API") public class UserController { private final UserService userService; - private final UserMybatisRepository userMybatisRepository; @Operation(summary = "[o]일반 유저 등록", description = "일반 유저 등록 API") @PostMapping("/signup") @@ -94,7 +94,6 @@ public void passwordTempSend() { @Operation(summary = "[o]유저 정보 변경") @PatchMapping() @UserRequired - @PreAuthorize("hasAnyRole('ROLE_USER')") public void updateUser(@RequestBody @Valid UserUpdateRequestDto userUpdateRequestDto) { Long userNo = AuthUtil.getUserNoFromAuthentication(); userService.updateUser(userNo, userUpdateRequestDto); @@ -120,6 +119,24 @@ public MyInfoResponseDto getMyInfo() { return userService.getMyInfo(myUserNo); } + @Operation(summary = "[o]내가 팔로잉하는 유저 조회", description = "내가 팔로잉하는 유저 정보 조회 API") + @UserRequired + @GetMapping("/my-following-users") + public Page getMyFollowingUsers(Pageable pageable) { + Long myUserNo = AuthUtil.getUserNoFromAuthentication(); + log.info(">>> UserController.getMyFollowingUsers"); + return userService.getMyFollowingUsers(myUserNo, pageable); + } + + @Operation(summary = "[o]나를 팔로잉하는 유저 조회", description = "나를 팔로잉하는 유저 조회") + @UserRequired + @GetMapping("/users-of-following-me") + public Page getUsersOfFollowingMe(Pageable pageable) { + Long myUserNo = AuthUtil.getUserNoFromAuthentication(); + log.info(">>> UserController.getMyFollowingUsers"); + return userService.getUsersOfFollowingMe(myUserNo, pageable); + } + @Operation(summary = "[o]유저 정보 조회", description = "유저 정보 조회 API") @GetMapping("/{userNo}/summary") public GetUserInfoResponseDto getUserInfo(@PathVariable("userNo") Long userNo) { @@ -139,7 +156,7 @@ public void withdraw() { @Operation(summary = "[o]유저 팔로우하기", description = "유저 팔로우하기 API") @PostMapping("/follow/{userNo}") @UserRequired - UserFollowResponseDto followUser(@PathVariable("userNo") Long userNo) { + public UserFollowResponseDto followUser(@PathVariable("userNo") Long userNo) { log.info(">>> UserController.followUser"); Long myUserNo = AuthUtil.getUserNoFromAuthentication(); return UserFollowResponseDto.of(userService.followUser(myUserNo, userNo)); @@ -148,7 +165,7 @@ UserFollowResponseDto followUser(@PathVariable("userNo") Long userNo) { @Operation(summary = "[o]유저 언팔로우하기", description = "유저 언팔로우하기 API") @PostMapping("/unfollow/{userNo}") @UserRequired - void unfollowUser(@PathVariable("userNo") Long userNo) { + public void unfollowUser(@PathVariable("userNo") Long userNo) { log.info(">>> UserController.unfollowUser"); Long myUserNo = AuthUtil.getUserNoFromAuthentication(); userService.unfollowUser(myUserNo, userNo); diff --git a/server/src/main/java/com/bside/bside_311/dto/AddCommentRequestDto.java b/server/src/main/java/com/bside/bside_311/dto/AddCommentRequestDto.java index 331e2ec..3ba6906 100644 --- a/server/src/main/java/com/bside/bside_311/dto/AddCommentRequestDto.java +++ b/server/src/main/java/com/bside/bside_311/dto/AddCommentRequestDto.java @@ -2,11 +2,13 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.AccessLevel; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter +@AllArgsConstructor public class AddCommentRequestDto { @Schema(example = "댓글 내용", description = "댓글 내용") private String commentContent; diff --git a/server/src/main/java/com/bside/bside_311/dto/AddPostRequestDto.java b/server/src/main/java/com/bside/bside_311/dto/AddPostRequestDto.java index 8d5977f..a932379 100644 --- a/server/src/main/java/com/bside/bside_311/dto/AddPostRequestDto.java +++ b/server/src/main/java/com/bside/bside_311/dto/AddPostRequestDto.java @@ -1,7 +1,6 @@ package com.bside.bside_311.dto; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.Valid; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -18,9 +17,6 @@ public class AddPostRequestDto { @Schema(example = "1", description = "선택 주류 번호") private Long alcoholNo; - - @Schema(description = "등록 알코올 정보.(선택. 입력시 술 조회 후 추가.)") - AddAlcoholRequestDto alcoholInfo; @Schema(example = "산뜻함. 달콤함.", description = "주류 특징") private String alcoholFeature; @Schema(example = "게시글 내용", description = "게시글 내용") diff --git a/server/src/main/java/com/bside/bside_311/dto/AlcoholResponseDto.java b/server/src/main/java/com/bside/bside_311/dto/AlcoholResponseDto.java index 8f356ac..39f9392 100644 --- a/server/src/main/java/com/bside/bside_311/dto/AlcoholResponseDto.java +++ b/server/src/main/java/com/bside/bside_311/dto/AlcoholResponseDto.java @@ -1,7 +1,10 @@ package com.bside.bside_311.dto; import com.bside.bside_311.entity.Alcohol; +import com.bside.bside_311.entity.AlcoholTag; +import com.bside.bside_311.entity.PostTag; import com.bside.bside_311.entity.Tag; +import com.bside.bside_311.entity.YesOrNo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -12,6 +15,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; @NoArgsConstructor(access = AccessLevel.PROTECTED) @Builder @@ -88,6 +92,39 @@ public static AlcoholResponseDto of(Alcohol alcohol, List attachDtos, tags.stream().map(Tag::getName).toList()); } return alcoholResponseDtoBuilder - .build(); + .build(); + } + + public static AlcoholResponseDto of(Alcohol alcohol, List attachDtos) { + List alcoholTags = alcohol.getAlcoholTags().stream() + .filter(postTag -> YesOrNo.N == postTag.getDelYn()) + .collect(Collectors.toList()); + List tagList = alcoholTags.stream() + .filter( + alcoholTag -> YesOrNo.N == alcoholTag.getTag().getDelYn()) + .map(alcoholTag -> alcoholTag.getTag().getName()).toList(); + + return AlcoholResponseDto.builder().alcoholNo(alcohol.getId()) + .alcoholName(alcohol.getName()) + .alcoholTypeNo( + alcohol.getAlcoholType().getId()) + .alcoholType( + alcohol.getAlcoholType().getName()) + .nickNames( + alcohol.getAlcoholNicknames().stream() + .filter(alcoholNickname -> alcoholNickname.getDelYn() == + YesOrNo.N).map( + alcoholNickname -> alcoholNickname.getNickname()) + .toList()) + .manufacturer(alcohol.getManufacturer()) + .description(alcohol.getDescription()) + .degree(alcohol.getDegree()) + .period(alcohol.getPeriod()) + .productionYear( + alcohol.getProductionYear()) + .volume(alcohol.getVolume()) + .alcoholAttachUrls(attachDtos) + .tagList(tagList).build(); } } + diff --git a/server/src/main/java/com/bside/bside_311/dto/AlcoholSearchCondition.java b/server/src/main/java/com/bside/bside_311/dto/AlcoholSearchCondition.java new file mode 100644 index 0000000..3460026 --- /dev/null +++ b/server/src/main/java/com/bside/bside_311/dto/AlcoholSearchCondition.java @@ -0,0 +1,16 @@ +package com.bside.bside_311.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@Getter +@Builder +@AllArgsConstructor +public class AlcoholSearchCondition { + @Schema(description = "키워드", example = "키워드") + String searchKeyword; +} diff --git a/server/src/main/java/com/bside/bside_311/dto/AttachDto.java b/server/src/main/java/com/bside/bside_311/dto/AttachDto.java index 9c99688..905742d 100644 --- a/server/src/main/java/com/bside/bside_311/dto/AttachDto.java +++ b/server/src/main/java/com/bside/bside_311/dto/AttachDto.java @@ -17,7 +17,7 @@ public class AttachDto { @Schema(example = "1", description = "첨부 번호") private Long attachNo; - @Schema(example = "http://www.naver.com", description = "프로필 사진 링크.") + @Schema(example = "http://www.naver.com", description = "첨부 사진 링크.") private String attachUrl; @Schema(example = "profile", description = "첨부 타입") private String attachType; diff --git a/server/src/main/java/com/bside/bside_311/dto/ChangePasswordRequestDto.java b/server/src/main/java/com/bside/bside_311/dto/ChangePasswordRequestDto.java index 656859e..d2bf19e 100644 --- a/server/src/main/java/com/bside/bside_311/dto/ChangePasswordRequestDto.java +++ b/server/src/main/java/com/bside/bside_311/dto/ChangePasswordRequestDto.java @@ -2,11 +2,15 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter +@Builder +@AllArgsConstructor public class ChangePasswordRequestDto { @Schema(example = "1a2s3d4f1!", description = "기존 비밀번호") private String password; diff --git a/server/src/main/java/com/bside/bside_311/dto/CommentResponseDto.java b/server/src/main/java/com/bside/bside_311/dto/CommentResponseDto.java index be271e2..fc9d444 100644 --- a/server/src/main/java/com/bside/bside_311/dto/CommentResponseDto.java +++ b/server/src/main/java/com/bside/bside_311/dto/CommentResponseDto.java @@ -1,19 +1,41 @@ package com.bside.bside_311.dto; import com.bside.bside_311.entity.Comment; +import com.bside.bside_311.entity.User; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + @NoArgsConstructor(access = AccessLevel.PROTECTED) +@Builder @AllArgsConstructor @Getter public class CommentResponseDto { private Long commentNo; private String commentContent; + private LocalDateTime createdDate; + private LocalDateTime lastModifiedDate; + private Long createdBy; + private String userId; + private String nickname; + @Builder.Default + private List profileImgUrls = new ArrayList<>(); - public static CommentResponseDto of(Comment comment) { - return new CommentResponseDto(comment.getId(), comment.getContent()); + public static CommentResponseDto of(Comment comment, User createUser, + List profileImgUrls) { + return CommentResponseDto.builder() + .commentNo(comment.getId()) + .commentContent(comment.getContent()) + .createdDate(comment.getCreatedDate()) + .lastModifiedDate(comment.getLastModifiedDate()) + .createdBy(comment.getCreatedBy()) + .userId(createUser.getUserId()).nickname(createUser.getNickname()) + .profileImgUrls(profileImgUrls).build(); } } diff --git a/server/src/main/java/com/bside/bside_311/dto/GetPostCommentsResponseDto.java b/server/src/main/java/com/bside/bside_311/dto/GetPostCommentsResponseDto.java index 27ab8c1..b691c63 100644 --- a/server/src/main/java/com/bside/bside_311/dto/GetPostCommentsResponseDto.java +++ b/server/src/main/java/com/bside/bside_311/dto/GetPostCommentsResponseDto.java @@ -1,12 +1,14 @@ package com.bside.bside_311.dto; import com.bside.bside_311.entity.Comment; +import com.bside.bside_311.entity.User; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -16,9 +18,13 @@ public class GetPostCommentsResponseDto { private List list; private Long totalCount; - public static GetPostCommentsResponseDto of(List comments) { + public static GetPostCommentsResponseDto of(List comments, + Map createdByToUser, + Map> uToAMap) { List list = - comments.stream().map(CommentResponseDto::of).collect(Collectors.toList()); + comments.stream().map(comment -> CommentResponseDto.of(comment + , createdByToUser.get(comment.getCreatedBy()), + uToAMap.get(comment.getCreatedBy()))).collect(Collectors.toList()); return new GetPostCommentsResponseDto(list, (long) list.size()); } } diff --git a/server/src/main/java/com/bside/bside_311/dto/GetPostVo.java b/server/src/main/java/com/bside/bside_311/dto/GetPostVo.java index 5213b1a..c972927 100644 --- a/server/src/main/java/com/bside/bside_311/dto/GetPostVo.java +++ b/server/src/main/java/com/bside/bside_311/dto/GetPostVo.java @@ -4,6 +4,8 @@ import lombok.Getter; import lombok.Setter; +import java.util.List; + @Getter @Setter @Builder @@ -15,14 +17,16 @@ public class GetPostVo { String orderColumn; String orderType; String searchKeyword; + List searchUserNoList; public GetPostVo(Long page, Long size, Long offset, String orderColumn, String orderType, - String searchKeyword) { + String searchKeyword, List searchUserNoList) { this.page = page; this.size = size; this.offset = offset; this.orderColumn = orderColumn; this.orderType = orderType; this.searchKeyword = searchKeyword; + this.searchUserNoList = searchUserNoList; } } diff --git a/server/src/main/java/com/bside/bside_311/dto/GetPostsToOneMvo.java b/server/src/main/java/com/bside/bside_311/dto/GetPostsToOneMvo.java new file mode 100644 index 0000000..3e4e994 --- /dev/null +++ b/server/src/main/java/com/bside/bside_311/dto/GetPostsToOneMvo.java @@ -0,0 +1,18 @@ +package com.bside.bside_311.dto; + +import lombok.Data; + +import java.time.LocalDateTime; + +@Data +public class GetPostsToOneMvo { + Long postNo; + String nickname; + String userId; + Boolean isFollowedByMe; + Long userFollowNo; + Boolean isLikedByMe; + Long postLikeNo; + Long alcoholNo; + String alcoholType; +} diff --git a/server/src/main/java/com/bside/bside_311/dto/GetUserInfoResponseDto.java b/server/src/main/java/com/bside/bside_311/dto/GetUserInfoResponseDto.java index a655156..008e4e9 100644 --- a/server/src/main/java/com/bside/bside_311/dto/GetUserInfoResponseDto.java +++ b/server/src/main/java/com/bside/bside_311/dto/GetUserInfoResponseDto.java @@ -3,21 +3,24 @@ import com.bside.bside_311.entity.User; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AccessLevel; -import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import lombok.experimental.SuperBuilder; import java.util.ArrayList; import java.util.List; @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -public class GetUserInfoResponseDto extends MyInfoResponseDto{ +public class GetUserInfoResponseDto extends MyInfoResponseDto { - public GetUserInfoResponseDto(String id, String nickname, List profileImages, - String introduction, Long followerCount, Boolean isFollowing) { - super(id, nickname, profileImages, introduction, followerCount); + @Schema(example = "true", description = "조회자가 대상자를 팔로워하는 지 여부") + private Boolean isFollowing; + + public GetUserInfoResponseDto(Long userNo, String id, String nickname, + List profileImages, + String introduction, Long followerCount, Long followingCount, + Boolean isFollowing) { + super(userNo, id, nickname, profileImages, introduction, followerCount, followingCount); this.isFollowing = isFollowing; } @@ -26,21 +29,19 @@ public GetUserInfoResponseDto(MyInfoResponseDto myInfoResponseDto, Boolean isFol this.isFollowing = isFollowing; } - @Schema(example = "true", description = "조회자가 대상자를 팔로워하는 지 여부") - private Boolean isFollowing; - public static GetUserInfoResponseDto of(User user) { MyInfoResponseDto myInfoResponseDto = MyInfoResponseDto.builder() - .id(user.getUserId()) - .nickname(user.getNickname()) - .profileImages(new ArrayList<>()) - .introduction(user.getIntroduction()) - .followerCount(null) - .build(); + .id(user.getUserId()) + .nickname(user.getNickname()) + .profileImages(new ArrayList<>()) + .introduction(user.getIntroduction()) + .followerCount(null) + .build(); return new GetUserInfoResponseDto(myInfoResponseDto, null); } - public static GetUserInfoResponseDto of(MyInfoResponseDto userInfoResponseDto, Boolean isFollowing) { + public static GetUserInfoResponseDto of(MyInfoResponseDto userInfoResponseDto, + Boolean isFollowing) { return new GetUserInfoResponseDto(userInfoResponseDto, isFollowing); } } diff --git a/server/src/main/java/com/bside/bside_311/dto/MyInfoResponseDto.java b/server/src/main/java/com/bside/bside_311/dto/MyInfoResponseDto.java index 72fd2b5..d8c0010 100644 --- a/server/src/main/java/com/bside/bside_311/dto/MyInfoResponseDto.java +++ b/server/src/main/java/com/bside/bside_311/dto/MyInfoResponseDto.java @@ -17,6 +17,8 @@ @Builder @AllArgsConstructor public class MyInfoResponseDto { + @Schema(example = "1", description = "유저 번호") + Long userNo; @Schema(example = "apple", description = "아이디(중복검사 필요)") String id; @@ -32,22 +34,30 @@ public class MyInfoResponseDto { @Schema(example = "24", description = "팔로워수") Long followerCount; + @Schema(example = "24", description = "당사자가 타인을 팔로잉 하는 수") + Long followingCount; + public MyInfoResponseDto(MyInfoResponseDto myInfoResponseDto) { + this.userNo = myInfoResponseDto.getUserNo(); this.id = myInfoResponseDto.getId(); this.nickname = myInfoResponseDto.getNickname(); this.profileImages = myInfoResponseDto.getProfileImages(); this.introduction = myInfoResponseDto.getIntroduction(); this.followerCount = myInfoResponseDto.getFollowerCount(); + this.followingCount = myInfoResponseDto.getFollowingCount(); } - public static MyInfoResponseDto of(User user, List profileImages, Long followerCount) { + public static MyInfoResponseDto of(User user, List profileImages, Long followerCount, + Long followingCount) { MyInfoResponseDto myInfoResponseDto = MyInfoResponseDto.builder() + .userNo(user.getId()) .id(user.getUserId()) .nickname(user.getNickname()) .profileImages(profileImages) .introduction(user.getIntroduction()) .followerCount(followerCount) + .followingCount(followingCount) .build(); return myInfoResponseDto; } diff --git a/server/src/main/java/com/bside/bside_311/dto/PostResponseDto.java b/server/src/main/java/com/bside/bside_311/dto/PostResponseDto.java index 72828fe..0a61e61 100644 --- a/server/src/main/java/com/bside/bside_311/dto/PostResponseDto.java +++ b/server/src/main/java/com/bside/bside_311/dto/PostResponseDto.java @@ -4,8 +4,10 @@ import com.bside.bside_311.entity.Alcohol; import com.bside.bside_311.entity.Comment; import com.bside.bside_311.entity.Post; +import com.bside.bside_311.entity.PostTag; import com.bside.bside_311.entity.Tag; import com.bside.bside_311.entity.User; +import com.bside.bside_311.entity.YesOrNo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -18,6 +20,7 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; @NoArgsConstructor(access = AccessLevel.PROTECTED) @Builder @@ -26,12 +29,13 @@ public class PostResponseDto { private String nickname; private String id; + private Long createdBy; @Schema(example = "[\"www.naver.com\", \"www.daum.net\"]", description = "프로필 이미지 URL") @Builder.Default private List profileImgUrls = new ArrayList<>(); private boolean isFollowedByMe; private boolean isLikedByMe; - private LocalDateTime updateDt; + private LocalDateTime lastModifiedDate; private boolean edited; private Long postNo; private String postContent; @@ -89,8 +93,10 @@ public static PostResponseDto of(Post post, User user, Alcohol alcohol, List postAttachDtos, List userAttachDtos) { + List postTags = post.getPostTags().stream() + .filter(postTag -> YesOrNo.N == postTag.getDelYn()) + .collect(Collectors.toList()); + List tagList = postTags.stream() + .filter(postTag -> YesOrNo.N == postTag.getTag().getDelYn()) + .map(postTag -> postTag.getTag().getName()).toList(); + PostResponseDto postResponseDto = + PostResponseDto.builder().nickname(getPostsToOneMvo.getNickname()) + .id(getPostsToOneMvo.getUserId()) + .createdBy(post.getCreatedBy()) + .isFollowedByMe(getPostsToOneMvo.getIsFollowedByMe()) + .isLikedByMe(getPostsToOneMvo.getIsLikedByMe()) + .lastModifiedDate(post.getLastModifiedDate()) + .edited(post.getCreatedDate() + .isEqual(post.getLastModifiedDate()) ? + false : true) + .postNo(post.getId()) + .postContent(post.getContent()) + .commentCount((long) post.getComments().stream().filter( + comment -> YesOrNo.N == comment.getDelYn()).toList() + .size()) + .positionInfo(post.getPosition()) + .alcoholNo(getPostsToOneMvo.getAlcoholNo()) + .alcoholType(getPostsToOneMvo.getAlcoholType()) + .alcoholName(getPostsToOneMvo.getAlcoholType()) + .postAttachUrls(postAttachDtos) + .profileImgUrls(userAttachDtos) + .tagList(tagList) + .likeCount( + (long) post.getPostLikes().stream().filter( + postLike -> YesOrNo.N == postLike.getDelYn()) + .collect( + Collectors.toList()).size()) + .quoteCount((long) post.getPostQuoteEds().stream() + .filter( + postQuoted -> YesOrNo.N == postQuoted.getDelYn()) + .collect( + Collectors.toList()).size()) + .build(); + return postResponseDto; + } } diff --git a/server/src/main/java/com/bside/bside_311/dto/PostSearchCondition.java b/server/src/main/java/com/bside/bside_311/dto/PostSearchCondition.java new file mode 100644 index 0000000..6f4dc03 --- /dev/null +++ b/server/src/main/java/com/bside/bside_311/dto/PostSearchCondition.java @@ -0,0 +1,26 @@ +package com.bside.bside_311.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@NoArgsConstructor +@Getter +@Builder +@AllArgsConstructor +public class PostSearchCondition { + @Schema(description = "키워드", example = "키워드") + String searchKeyword; + @Schema(description = "검색 유저 번호들.", example = "1,2,4") + List searchUserNoList; + @Schema(description = "나에 의해서 좋아하는 게시글 필터 여부(true or false).", example = "false") + Boolean isLikedByMe; + @Schema(description = "내가 댓글을 단 게시글 필터 여부.(true or false)", example = "false") + Boolean isCommentedByMe; + @Schema(description = "나의 유저 정보.(null, or 1L, 2L ...)", example = "1") + Long myUserNo; +} diff --git a/server/src/main/java/com/bside/bside_311/dto/UserLoginRequestDto.java b/server/src/main/java/com/bside/bside_311/dto/UserLoginRequestDto.java index 9f66b4e..de0ece4 100644 --- a/server/src/main/java/com/bside/bside_311/dto/UserLoginRequestDto.java +++ b/server/src/main/java/com/bside/bside_311/dto/UserLoginRequestDto.java @@ -3,14 +3,20 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter +@Builder +@AllArgsConstructor public class UserLoginRequestDto { @Schema(example = "apple", description = "아이디") + @NotBlank String id; + @Schema(example = "1a2s3d4f1!", description = "패스워드8~20자 대소문자, 숫자, 특수기호)") @NotBlank String password; diff --git a/server/src/main/java/com/bside/bside_311/dto/UserResponseDto.java b/server/src/main/java/com/bside/bside_311/dto/UserResponseDto.java new file mode 100644 index 0000000..ac11511 --- /dev/null +++ b/server/src/main/java/com/bside/bside_311/dto/UserResponseDto.java @@ -0,0 +1,36 @@ +package com.bside.bside_311.dto; + +import com.bside.bside_311.entity.User; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Builder +@AllArgsConstructor +@Getter +public class UserResponseDto { + private String nickname; + private String id; + private Long userNo; + private String introduction; + private Long createdBy; + @Builder.Default + private List profileImgUrls = new ArrayList<>(); + + public static UserResponseDto of(User user, List userAttachDtos) { + return UserResponseDto.builder() + .nickname(user.getNickname()) + .id(user.getUserId()) + .userNo(user.getId()) + .introduction(user.getIntroduction()) + .createdBy(user.getCreatedBy()) + .profileImgUrls(userAttachDtos) + .build(); + } +} diff --git a/server/src/main/java/com/bside/bside_311/dto/UserSignupRequestDto.java b/server/src/main/java/com/bside/bside_311/dto/UserSignupRequestDto.java index b316422..acfaa8e 100644 --- a/server/src/main/java/com/bside/bside_311/dto/UserSignupRequestDto.java +++ b/server/src/main/java/com/bside/bside_311/dto/UserSignupRequestDto.java @@ -5,11 +5,14 @@ import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @NoArgsConstructor(access = AccessLevel.PROTECTED) +@Builder +@AllArgsConstructor @Getter public class UserSignupRequestDto { @Schema(example = "test@example.com", description = "유저 이메일") @@ -26,11 +29,6 @@ public class UserSignupRequestDto { @NotBlank String nickname; - @Builder - public UserSignupRequestDto(String email, String password, String id, String nickname) { - this.email = email; - this.password = password; - this.id = id; - this.nickname = nickname; - } + @Schema(example = "자기소개입니다", description = "자기소개 이다.") + String introduction; } diff --git a/server/src/main/java/com/bside/bside_311/dto/UserUpdateRequestDto.java b/server/src/main/java/com/bside/bside_311/dto/UserUpdateRequestDto.java index 7db5428..2c6e915 100644 --- a/server/src/main/java/com/bside/bside_311/dto/UserUpdateRequestDto.java +++ b/server/src/main/java/com/bside/bside_311/dto/UserUpdateRequestDto.java @@ -1,13 +1,14 @@ package com.bside.bside_311.dto; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; import lombok.AccessLevel; +import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter +@AllArgsConstructor public class UserUpdateRequestDto { @Schema(example = "안녕하세요.", description = "자기소개.") String introduction; diff --git a/server/src/main/java/com/bside/bside_311/entity/BaseEntity.java b/server/src/main/java/com/bside/bside_311/entity/BaseEntity.java index bbc16c1..5168072 100644 --- a/server/src/main/java/com/bside/bside_311/entity/BaseEntity.java +++ b/server/src/main/java/com/bside/bside_311/entity/BaseEntity.java @@ -9,7 +9,6 @@ import lombok.Getter; import lombok.Setter; import org.hibernate.annotations.ColumnDefault; -import org.hibernate.annotations.DynamicInsert; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.LastModifiedBy; import org.springframework.data.jpa.domain.support.AuditingEntityListener; diff --git a/server/src/main/java/com/bside/bside_311/entity/Post.java b/server/src/main/java/com/bside/bside_311/entity/Post.java index ccef358..2a2d28e 100644 --- a/server/src/main/java/com/bside/bside_311/entity/Post.java +++ b/server/src/main/java/com/bside/bside_311/entity/Post.java @@ -85,6 +85,10 @@ public static Post of(AddPostRequestDto addPostRequestDto) { return post; } + public static Post of(Post post) throws CloneNotSupportedException { + return (Post) post.clone(); + } + public static Post of(GetPostsMvo getPostsMvo) { Post post = Post.builder() diff --git a/server/src/main/java/com/bside/bside_311/entity/User.java b/server/src/main/java/com/bside/bside_311/entity/User.java index 0a99836..c0e77b4 100644 --- a/server/src/main/java/com/bside/bside_311/entity/User.java +++ b/server/src/main/java/com/bside/bside_311/entity/User.java @@ -14,6 +14,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import lombok.ToString; import org.hibernate.annotations.DynamicInsert; import org.hibernate.annotations.DynamicUpdate; import org.springframework.util.ObjectUtils; @@ -27,6 +28,7 @@ @DynamicInsert @DynamicUpdate @Builder +@ToString @NoArgsConstructor(access = AccessLevel.PROTECTED) public class User extends BaseEntity { @Id @@ -80,6 +82,7 @@ public static User of(UserSignupRequestDto userSignupRequestDto, Role role) { .userId(userSignupRequestDto.getId()) .nickname(userSignupRequestDto.getNickname()) .role(role) + .introduction(userSignupRequestDto.getIntroduction()) .build(); } diff --git a/server/src/main/java/com/bside/bside_311/init/Initializer.java b/server/src/main/java/com/bside/bside_311/init/Initializer.java index 5a2e8ce..32cde6f 100644 --- a/server/src/main/java/com/bside/bside_311/init/Initializer.java +++ b/server/src/main/java/com/bside/bside_311/init/Initializer.java @@ -1,30 +1,41 @@ package com.bside.bside_311.init; import com.bside.bside_311.controller.AlcoholController; +import com.bside.bside_311.controller.AttachController; import com.bside.bside_311.controller.PostController; import com.bside.bside_311.controller.UserController; import com.bside.bside_311.dto.AddAlcoholRequestDto; +import com.bside.bside_311.dto.AddCommentRequestDto; import com.bside.bside_311.dto.AddPostRequestDto; +import com.bside.bside_311.dto.ImageRequestDto; import com.bside.bside_311.dto.UserSignupRequestDto; import com.bside.bside_311.dto.UserSignupResponseDto; import com.bside.bside_311.entity.AlcoholType; +import com.bside.bside_311.entity.AttachType; import com.bside.bside_311.entity.PostType; import com.bside.bside_311.repository.AlcoholTypeRepository; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; +import org.apache.commons.io.FileUtils; import org.springframework.context.annotation.Profile; import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; import org.springframework.web.reactive.function.BodyInserters; import org.springframework.web.reactive.function.client.WebClient; import reactor.core.publisher.Mono; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.List; @Profile("local") @@ -35,9 +46,36 @@ public class Initializer { private final AlcoholTypeRepository alcoholTypeRepository; private final UserController userController; private final AlcoholController alcoholController; + private final AttachController attachController; private final PostController postController; + private static UsernamePasswordAuthenticationToken easyUserRoleAuthenticationFactory( + Long userNo) { + return new UsernamePasswordAuthenticationToken(userNo, "", + List.of(new SimpleGrantedAuthority("ROLE_USER"))); + } + + public static File convertInputStreamToFile(InputStream in) throws IOException { + + File tempFile = File.createTempFile(String.valueOf(in.hashCode()), ".tmp"); + tempFile.deleteOnExit(); + + copyInputStreamToFile(in, tempFile); + + return tempFile; + } + + private static void copyInputStreamToFile(InputStream inputStream, File file) throws IOException { + + FileUtils.copyInputStreamToFile(inputStream, file); + } + + private void setSecurityContextUserNo(Long userNo) { + SecurityContextHolder.getContext() + .setAuthentication(easyUserRoleAuthenticationFactory(userNo)); + } + @PostConstruct public void init() throws JsonProcessingException { alcoholTypeRepository.save(AlcoholType.builder().name("소주").description("소주다.").build()); @@ -64,8 +102,7 @@ public void init() throws JsonProcessingException { } """; userController.signup(objectMapper.readValue(signupBody, UserSignupRequestDto.class)); - Authentication authentication = new UsernamePasswordAuthenticationToken(1L, "", - List.of(new SimpleGrantedAuthority("ROLE_USER"))); + Authentication authentication = easyUserRoleAuthenticationFactory(1L); SecurityContextHolder.getContext().setAuthentication(authentication); for (int i = 0; i < 5; i++) { userController.signup(UserSignupRequestDto.builder() @@ -73,7 +110,16 @@ public void init() throws JsonProcessingException { .password("1a2s3d4f1!") .id(String.format("test%d", i)) .nickname(String.format("bside%d", i)) + .introduction( + String.format("testIntroduction%d", i)) .build()); + } + securityContextHolderClear(); + SecurityContextHolder.getContext().setAuthentication(null); + + for (int i = 0; i < 5; i++) { + SecurityContextHolder.getContext() + .setAuthentication(easyUserRoleAuthenticationFactory((long) (i + 1))); alcoholController. addAlcohol(AddAlcoholRequestDto.builder() .alcoholName(String.format("test%d", i)) @@ -86,6 +132,8 @@ public void init() throws JsonProcessingException { .degree((float) (i + 0.5)) .period(20L + i) .productionYear(1990L + i) + .tagList(List.of(String.format("testTag%d", i), + String.format("testTag23453%d", i))) .volume(700L + i).build()); postController.addPost(AddPostRequestDto.builder() .alcoholNo((long) (i + 1)) @@ -96,8 +144,133 @@ public void init() throws JsonProcessingException { ).tagList(List.of(String.format("testTag%d", i), String.format("testTag23453%d", i))) .build()); + SecurityContextHolder.getContext().setAuthentication(null); } + // following + // 1L -> 3L + // 2L -> 3L + // 4L -> 2L + setSecurityContextUserNo(1L); + userController.followUser(3L); + securityContextHolderClear(); + + setSecurityContextUserNo(2L); + userController.followUser(3L); + securityContextHolderClear(); + + setSecurityContextUserNo(4L); + userController.followUser(2L); + securityContextHolderClear(); + + // post Like + // 5L -> 2L + setSecutiryContextDoSomethingAndClear(5L, () -> { + postController.likePost(2L); + }); + // 4L -> 2L + setSecutiryContextDoSomethingAndClear(4L, () -> { + postController.likePost(2L); + }); + // 1L -> 3L + setSecutiryContextDoSomethingAndClear(1L, () -> { + postController.likePost(3L); + }); + + // comment + // 1L -> 2L 포스트에. 정말 웃기다 크크. + // 3L -> 2L 포스트에. 오 저기는 꼭 가봐야겠어. + // 5L -> 1L 포스트에 와 이건 뭐임. + // 4L -> 1L 포스트에. 이건 뭐임. + // 2L -> 2L 포스트에. ㅋㅋ 신기하긴 함. + setSecutiryContextDoSomethingAndClear(1L, () -> { + postController.addComment(2L, new AddCommentRequestDto("정말 웃기다 크크.")); + }); + setSecutiryContextDoSomethingAndClear(3L, () -> { + postController.addComment(2L, new AddCommentRequestDto("오 저기는 꼭 가봐야겠어.")); + }); + setSecutiryContextDoSomethingAndClear(5L, () -> { + postController.addComment(1L, new AddCommentRequestDto("와 이건 뭐임.")); + }); + setSecutiryContextDoSomethingAndClear(4L, () -> { + postController.addComment(1L, new AddCommentRequestDto("오 ㅋㅋㅋ")); + }); + setSecutiryContextDoSomethingAndClear(2L, () -> { + postController.addComment(2L, new AddCommentRequestDto("ㅋㅋㅋ ㄹㅇ 강추해요.")); + }); + + setSecutiryContextDoSomethingAndClear(2L, () -> { + postController.addComment(2L, new AddCommentRequestDto("ㅋㅋㅋ ㄹㅇ 강추해요.")); + }); + + // 인용하기. + // 2번 게시글이 1번 게시글을 인용. + // 3번 게시글이 1번 게시글을 인용. + // 5번 게시글이 3번 게시글을 인용. + setSecutiryContextDoSomethingAndClear(2L, () -> { + postController.addQuote(2L, 1L); + }); + setSecutiryContextDoSomethingAndClear(3L, () -> { + postController.addQuote(3L, 1L); + }); + setSecutiryContextDoSomethingAndClear(5L, () -> { + postController.addQuote(5L, 3L); + }); + + + // 1번부터 5번까지 게시글 첨부파일 등록 + // 1번부터 5번까지 내 프로필 등록. +// attachPhoto(); + + } + + private void attachPhoto() { + for (int i = 0; i < 5; i++) { + int finalI = i; + setSecutiryContextDoSomethingAndClear((long) (i + 1), () -> { + try { + attachController.userAttachPicture((long) (finalI + 1), + AttachType.POST.name(), + new ImageRequestDto(makeMultiPartFileByLocalFile(String.format("%d.png", finalI + 1), + "/example/attach/post"))); + attachController.userAttachPicture((long) (finalI + 1), + AttachType.PROFILE.name(), + new ImageRequestDto(makeMultiPartFileByLocalFile(String.format("%d.png", finalI + 1), + "/example/attach/profile"))); + + attachController.userAttachPicture((long) (finalI + 1), + AttachType.ALCOHOL.name(), + new ImageRequestDto(makeMultiPartFileByLocalFile(String.format("%d.png", finalI + 1), + "/example/attach/alcohol"))); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + } + + public MultipartFile makeMultiPartFileByLocalFile(String fileName, String classPathFolderPath) + throws IOException { +// File file = new File("C:\\temp\\test.xlsx"); +// DiskFileItem fileItem = new DiskFileItem("file", Files.probeContentType(file.toPath()), false, file.getName(), (int) file.length() , file.getParentFile()); InputStream +// input = new FileInputStream(file); +// OutputStream os = fileItem.getOutputStream(); +// IOUtils.copy(input, os); +// MultipartFile multipartFile = new MockMultipartFile("1.png", new FileInputStream(new File(filePath))); + + MultipartFile multipartFile = new MockMultipartFile(fileName, new FileInputStream( + convertInputStreamToFile( + Initializer.class.getResourceAsStream(classPathFolderPath + "/" + fileName)))); + return multipartFile; + } + + public void setSecutiryContextDoSomethingAndClear(Long userNo, Runnable runnable) { + setSecurityContextUserNo(userNo); + runnable.run(); + securityContextHolderClear(); + } + + private void securityContextHolderClear() { SecurityContextHolder.getContext().setAuthentication(null); } @@ -133,5 +306,4 @@ public void init2() { } } - } diff --git a/server/src/main/java/com/bside/bside_311/repository/AlcoholRepository.java b/server/src/main/java/com/bside/bside_311/repository/AlcoholRepository.java index 289dfc3..bdb5f9e 100644 --- a/server/src/main/java/com/bside/bside_311/repository/AlcoholRepository.java +++ b/server/src/main/java/com/bside/bside_311/repository/AlcoholRepository.java @@ -7,7 +7,7 @@ import java.util.Optional; -public interface AlcoholRepository extends JpaRepository{ +public interface AlcoholRepository extends JpaRepository, AlcoholRepositoryCustom { Optional findByNameAndDelYnIs(String name, YesOrNo delYn); Optional findByIdAndDelYnIs(Long userNo, YesOrNo delYn); diff --git a/server/src/main/java/com/bside/bside_311/repository/AlcoholRepositoryCustom.java b/server/src/main/java/com/bside/bside_311/repository/AlcoholRepositoryCustom.java new file mode 100644 index 0000000..a7d9ae0 --- /dev/null +++ b/server/src/main/java/com/bside/bside_311/repository/AlcoholRepositoryCustom.java @@ -0,0 +1,10 @@ +package com.bside.bside_311.repository; + +import com.bside.bside_311.dto.AlcoholSearchCondition; +import com.bside.bside_311.entity.Alcohol; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface AlcoholRepositoryCustom { + Page searchAlcoholPage(AlcoholSearchCondition condition, Pageable pageable); +} diff --git a/server/src/main/java/com/bside/bside_311/repository/AlcoholRepositoryImpl.java b/server/src/main/java/com/bside/bside_311/repository/AlcoholRepositoryImpl.java new file mode 100644 index 0000000..0e2c782 --- /dev/null +++ b/server/src/main/java/com/bside/bside_311/repository/AlcoholRepositoryImpl.java @@ -0,0 +1,57 @@ +package com.bside.bside_311.repository; + +import com.bside.bside_311.dto.AlcoholSearchCondition; +import com.bside.bside_311.entity.Alcohol; +import com.bside.bside_311.entity.Post; +import com.bside.bside_311.entity.QAlcohol; +import com.bside.bside_311.entity.QAlcoholType; +import com.bside.bside_311.entity.QUser; +import com.bside.bside_311.entity.YesOrNo; +import com.bside.bside_311.repository.support.Querydsl4RepositorySupport; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.util.StringUtils; + +import java.util.Optional; +import java.util.function.Function; + +import static com.bside.bside_311.entity.QAlcohol.alcohol; +import static com.bside.bside_311.entity.QAlcoholType.alcoholType; +import static com.bside.bside_311.entity.QPost.post; + +public class AlcoholRepositoryImpl extends Querydsl4RepositorySupport + implements AlcoholRepositoryCustom { + private final JPAQueryFactory queryFactory; + + public AlcoholRepositoryImpl(EntityManager em) { + super(Alcohol.class); + this.queryFactory = new JPAQueryFactory(em); + } + + @Override + public Page searchAlcoholPage(AlcoholSearchCondition condition, Pageable pageable) { + Function jpaQueryFactoryJPAQueryFunction = + query -> query.select(alcohol) + .from(alcohol) + .leftJoin(alcoholType).on((alcohol.alcoholType.eq(alcoholType)) + .and(alcohol.delYn.eq(YesOrNo.N)) + .and(alcoholType.delYn.eq(YesOrNo.N))) + .where(contentLike(condition.getSearchKeyword()), + notDeleted()); + return applyPagination(pageable, jpaQueryFactoryJPAQueryFunction + ); + } + + private BooleanExpression contentLike(String searchKeyword) { + return StringUtils.hasText(searchKeyword) ? alcohol.name.contains(searchKeyword) : null; + } + + private BooleanExpression notDeleted() { + return alcohol.delYn.eq(YesOrNo.N); + } +} diff --git a/server/src/main/java/com/bside/bside_311/repository/AttachRepository.java b/server/src/main/java/com/bside/bside_311/repository/AttachRepository.java index 3aff651..776f1a6 100644 --- a/server/src/main/java/com/bside/bside_311/repository/AttachRepository.java +++ b/server/src/main/java/com/bside/bside_311/repository/AttachRepository.java @@ -14,4 +14,6 @@ public interface AttachRepository extends JpaRepository { // Long findByIdAndDelYnIs(Long attachNo, YesOrNo delYn); List findByRefNoAndAttachTypeIsAndDelYnIs(Long refNo, AttachType attachType, YesOrNo delYn); + List findByRefNoInAndAttachTypeIsAndDelYnIs(List refNo, AttachType attachType, + YesOrNo delYn); } diff --git a/server/src/main/java/com/bside/bside_311/repository/PostMybatisRepository.java b/server/src/main/java/com/bside/bside_311/repository/PostMybatisRepository.java index 6eb2a06..4d1d43d 100644 --- a/server/src/main/java/com/bside/bside_311/repository/PostMybatisRepository.java +++ b/server/src/main/java/com/bside/bside_311/repository/PostMybatisRepository.java @@ -2,6 +2,7 @@ import com.bside.bside_311.dto.GetPostVo; import com.bside.bside_311.dto.GetPostsMvo; +import com.bside.bside_311.dto.GetPostsToOneMvo; import org.apache.ibatis.annotations.Mapper; import org.springframework.stereotype.Repository; @@ -13,4 +14,6 @@ public interface PostMybatisRepository { List getPosts(GetPostVo getPostVo); Long getPostsCount(GetPostVo getPostVo); + + List getPostsToOne(List postNos); } diff --git a/server/src/main/java/com/bside/bside_311/repository/PostRepository.java b/server/src/main/java/com/bside/bside_311/repository/PostRepository.java index 32481fd..24e7512 100644 --- a/server/src/main/java/com/bside/bside_311/repository/PostRepository.java +++ b/server/src/main/java/com/bside/bside_311/repository/PostRepository.java @@ -6,6 +6,6 @@ import java.util.Optional; -public interface PostRepository extends JpaRepository { +public interface PostRepository extends JpaRepository, PostRepositoryCustom { Optional findByIdAndDelYnIs(Long postNo, YesOrNo delYn); } diff --git a/server/src/main/java/com/bside/bside_311/repository/PostRepositoryCustom.java b/server/src/main/java/com/bside/bside_311/repository/PostRepositoryCustom.java new file mode 100644 index 0000000..cb059a2 --- /dev/null +++ b/server/src/main/java/com/bside/bside_311/repository/PostRepositoryCustom.java @@ -0,0 +1,10 @@ +package com.bside.bside_311.repository; + +import com.bside.bside_311.dto.PostSearchCondition; +import com.bside.bside_311.entity.Post; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface PostRepositoryCustom { + Page searchPageSimple(PostSearchCondition condition, Pageable pageable); +} diff --git a/server/src/main/java/com/bside/bside_311/repository/PostRepositoryImpl.java b/server/src/main/java/com/bside/bside_311/repository/PostRepositoryImpl.java new file mode 100644 index 0000000..3f447de --- /dev/null +++ b/server/src/main/java/com/bside/bside_311/repository/PostRepositoryImpl.java @@ -0,0 +1,82 @@ +package com.bside.bside_311.repository; + +import com.bside.bside_311.dto.PostSearchCondition; +import com.bside.bside_311.entity.Post; +import com.bside.bside_311.entity.YesOrNo; +import com.bside.bside_311.repository.support.Querydsl4RepositorySupport; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import io.jsonwebtoken.lang.Collections; +import jakarta.persistence.EntityManager; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.ObjectUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.util.StringUtils; + +import java.util.List; +import java.util.function.Function; + +import static com.bside.bside_311.entity.QComment.comment; +import static com.bside.bside_311.entity.QPost.post; +import static com.bside.bside_311.entity.QPostLike.postLike; +import static com.bside.bside_311.entity.QUser.user; + +public class PostRepositoryImpl extends Querydsl4RepositorySupport + implements PostRepositoryCustom { + private final JPAQueryFactory queryFactory; + + public PostRepositoryImpl(EntityManager em) { + super(Post.class); + this.queryFactory = new JPAQueryFactory(em); + } + + @Override + public Page searchPageSimple(PostSearchCondition condition, Pageable pageable) { + //sort 지원. + + Function jpaQueryFactoryJPAQueryFunction = + query -> { + JPAQuery jpaQuery = query.select(post) + .from(post) + .leftJoin(user).on(post.createdBy.eq(user.id).and( + user.delYn.eq(YesOrNo.N))); + if (ObjectUtils.isNotEmpty(condition.getMyUserNo()) && + BooleanUtils.isTrue(condition.getIsLikedByMe())) { + jpaQuery.innerJoin(postLike).on(postLike.post.eq(post).and(postLike.user.id.eq( + condition.getMyUserNo())).and(postLike.delYn.eq(YesOrNo.N))); + } + return jpaQuery + .where(contentLike(condition.getSearchKeyword()), + createdByIn(condition.getSearchUserNoList()), + isCommentedByMe(condition.getMyUserNo(), condition.getIsCommentedByMe()), + notDeleted()); + }; + return applyPagination(pageable, jpaQueryFactoryJPAQueryFunction + ); + } + + private BooleanExpression contentLike(String searchKeyword) { + return StringUtils.hasText(searchKeyword) ? post.content.contains(searchKeyword) : null; + } + + private BooleanExpression createdByIn(List searchUserNos) { + return !Collections.isEmpty(searchUserNos) ? post.createdBy.in(searchUserNos) : null; + } + + private BooleanExpression notDeleted() { + return post.delYn.eq(YesOrNo.N); + } + + private BooleanExpression isCommentedByMe(Long myUserNo, Boolean isCommentedByMe) { + if (ObjectUtils.isNotEmpty(myUserNo) && BooleanUtils.isTrue(isCommentedByMe)) { + return post.id.in(queryFactory.select(comment.post.id) + .from(comment) + .where(comment.createdBy.eq(myUserNo).and(comment.delYn.eq( + YesOrNo.N))) + .fetch()); + } + return null; + } +} diff --git a/server/src/main/java/com/bside/bside_311/repository/UserFollowRepository.java b/server/src/main/java/com/bside/bside_311/repository/UserFollowRepository.java index b0da4ff..cb391aa 100644 --- a/server/src/main/java/com/bside/bside_311/repository/UserFollowRepository.java +++ b/server/src/main/java/com/bside/bside_311/repository/UserFollowRepository.java @@ -5,11 +5,13 @@ import com.bside.bside_311.entity.YesOrNo; import org.springframework.data.jpa.repository.JpaRepository; -import java.util.List; import java.util.Optional; public interface UserFollowRepository extends JpaRepository { Optional findByFollowingAndFollowedAndDelYnIs(User following, User followed, YesOrNo delYn); + Long countByFollowedAndDelYnIs(User followed, YesOrNo delYn); + + Long countByFollowingAndDelYnIs(User following, YesOrNo delYn); } diff --git a/server/src/main/java/com/bside/bside_311/repository/UserRepository.java b/server/src/main/java/com/bside/bside_311/repository/UserRepository.java index 9c23e3a..490d58a 100644 --- a/server/src/main/java/com/bside/bside_311/repository/UserRepository.java +++ b/server/src/main/java/com/bside/bside_311/repository/UserRepository.java @@ -8,9 +8,12 @@ import java.util.List; import java.util.Optional; -public interface UserRepository extends JpaRepository{ +public interface UserRepository extends JpaRepository, UserRepositoryCustom { List findByEmailOrUserIdAndDelYnIs(String email, String userId, YesOrNo delYn); + Optional findByUserIdAndDelYnIs(String id, YesOrNo delYn); Optional findByIdAndDelYnIs(Long userNo, YesOrNo delYn); + + List findAllByIdInAndDelYnIs(List commentCreatedList, YesOrNo n); } diff --git a/server/src/main/java/com/bside/bside_311/repository/UserRepositoryCustom.java b/server/src/main/java/com/bside/bside_311/repository/UserRepositoryCustom.java new file mode 100644 index 0000000..a460efa --- /dev/null +++ b/server/src/main/java/com/bside/bside_311/repository/UserRepositoryCustom.java @@ -0,0 +1,11 @@ +package com.bside.bside_311.repository; + +import com.bside.bside_311.entity.User; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface UserRepositoryCustom { + Page getMyFollowingUsersPage(Long userNo, Pageable pageable); + + Page getUsersOfFollowingMePage(Long userNo, Pageable pageable); +} diff --git a/server/src/main/java/com/bside/bside_311/repository/UserRepositoryImpl.java b/server/src/main/java/com/bside/bside_311/repository/UserRepositoryImpl.java new file mode 100644 index 0000000..0d65028 --- /dev/null +++ b/server/src/main/java/com/bside/bside_311/repository/UserRepositoryImpl.java @@ -0,0 +1,75 @@ +package com.bside.bside_311.repository; + +import com.bside.bside_311.entity.User; +import com.bside.bside_311.entity.YesOrNo; +import com.bside.bside_311.repository.support.Querydsl4RepositorySupport; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.function.Function; + +import static com.bside.bside_311.entity.QUser.user; +import static com.bside.bside_311.entity.QUserFollow.userFollow; + +public class UserRepositoryImpl extends Querydsl4RepositorySupport + implements UserRepositoryCustom { + private final JPAQueryFactory queryFactory; + + public UserRepositoryImpl(EntityManager em) { + super(User.class); + this.queryFactory = new JPAQueryFactory(em); + } + + + @Override + public Page getMyFollowingUsersPage(Long myUserNo, Pageable pageable) { + Function jpaQueryFactoryJPAQueryFunction = + query -> { + JPAQuery jpaQuery = query.select(user) + .from(user) + .where(user.delYn.eq(YesOrNo.N).and( + user.id.in( + JPAExpressions.select( + userFollow.followed.id) + .from(userFollow) + .where( + userFollow.following.id.eq( + myUserNo) + .and( + userFollow.delYn.eq( + YesOrNo.N))) + ))); + return jpaQuery; + }; + + return applyPagination(pageable, jpaQueryFactoryJPAQueryFunction); + } + + @Override + public Page getUsersOfFollowingMePage(Long myUserNo, Pageable pageable) { + Function jpaQueryFactoryJPAQueryFunction = + query -> { + JPAQuery jpaQuery = query.select(user) + .from(user) + .where(user.delYn.eq(YesOrNo.N).and( + user.id.in( + JPAExpressions.select( + userFollow.following.id) + .from(userFollow) + .where( + userFollow.followed.id.eq( + myUserNo) + .and( + userFollow.delYn.eq( + YesOrNo.N))) + ))); + return jpaQuery; + }; + + return applyPagination(pageable, jpaQueryFactoryJPAQueryFunction); + } +} diff --git a/server/src/main/java/com/bside/bside_311/service/AlcoholService.java b/server/src/main/java/com/bside/bside_311/service/AlcoholService.java index b2d4a1c..d590e58 100644 --- a/server/src/main/java/com/bside/bside_311/service/AlcoholService.java +++ b/server/src/main/java/com/bside/bside_311/service/AlcoholService.java @@ -3,6 +3,7 @@ import com.bside.bside_311.dto.AddAlcoholRequestDto; import com.bside.bside_311.dto.AddAlcoholResponseDto; import com.bside.bside_311.dto.AlcoholResponseDto; +import com.bside.bside_311.dto.AlcoholSearchCondition; import com.bside.bside_311.dto.AttachDto; import com.bside.bside_311.dto.EditAlcoholRequestDto; import com.bside.bside_311.dto.GetAlcoholResponseDto; @@ -13,6 +14,7 @@ import com.bside.bside_311.entity.AlcoholNickname; import com.bside.bside_311.entity.AlcoholTag; import com.bside.bside_311.entity.AlcoholType; +import com.bside.bside_311.entity.Attach; import com.bside.bside_311.entity.AttachType; import com.bside.bside_311.entity.Tag; import com.bside.bside_311.entity.YesOrNo; @@ -26,14 +28,19 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import static com.bside.bside_311.util.ValidateUtil.resourceChangeableCheckByThisRequestToken; + @Service @Slf4j @RequiredArgsConstructor @@ -77,6 +84,7 @@ public AddAlcoholResponseDto addAlcohol(AddAlcoholRequestDto addAlcoholRequestDt public void editAlcohol(Long alcoholNo, EditAlcoholRequestDto editAlcoholRequestDto) { Alcohol alcohol = alcoholRepository.findByIdAndDelYnIs(alcoholNo, YesOrNo.N).orElseThrow( () -> new IllegalArgumentException("술이 존재하지 않습니다.")); + resourceChangeableCheckByThisRequestToken(alcohol); if (editAlcoholRequestDto != null) { if (editAlcoholRequestDto.getAlcoholName() != null) { alcohol.setName(editAlcoholRequestDto.getAlcoholName()); @@ -124,6 +132,7 @@ public void editAlcohol(Long alcoholNo, EditAlcoholRequestDto editAlcoholRequest public void deleteAlcohol(Long alcoholNo) { Alcohol alcohol = alcoholRepository.findByIdAndDelYnIs(alcoholNo, YesOrNo.N).orElseThrow( () -> new IllegalArgumentException("술이 존재하지 않습니다.")); + resourceChangeableCheckByThisRequestToken(alcohol); alcohol.setDelYn(YesOrNo.Y); alcoholRepository.save(alcohol); } @@ -182,6 +191,31 @@ public GetAlcoholResponseDto getAlcohol(Long page, Long size, String orderColumn return GetAlcoholResponseDto.of(alcoholResponseDtos, alcoholsCount); } + public Page getAlcoholV2(Pageable pageable, String searchKeyword) { + // 술 종류 fetch join + Page alcohols = alcoholRepository.searchAlcoholPage(AlcoholSearchCondition.builder() + .searchKeyword( + searchKeyword) + .build(), + pageable); + List alcoholNos = alcohols.stream().map(Alcohol::getId).toList(); + List alcoholAttachList = + attachRepository.findByRefNoInAndAttachTypeIsAndDelYnIs(alcoholNos, AttachType.ALCOHOL, + YesOrNo.N); + Map> aToAMap = new HashMap<>(); + for (Attach attach : alcoholAttachList) { + if (!aToAMap.containsKey(attach.getRefNo())) { + aToAMap.put(attach.getRefNo(), new ArrayList<>()); + } + List attachDtos = aToAMap.get(attach.getRefNo()); + attachDtos.add(AttachDto.of(attach)); + } + return alcohols.map(alcohol -> { + List attachDtos = aToAMap.getOrDefault(alcohol.getId(), new ArrayList<>()); + return AlcoholResponseDto.of(alcohol, attachDtos); + }); + } + public GetAlcoholTypesResponseDto getAlcoholTypes() { List alcoholTypes = alcoholTypeRepository.findAll(); return GetAlcoholTypesResponseDto.of(alcoholTypes); diff --git a/server/src/main/java/com/bside/bside_311/service/AttachService.java b/server/src/main/java/com/bside/bside_311/service/AttachService.java index b95aecf..dafb54d 100644 --- a/server/src/main/java/com/bside/bside_311/service/AttachService.java +++ b/server/src/main/java/com/bside/bside_311/service/AttachService.java @@ -13,11 +13,12 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.ObjectUtils; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; +import static com.bside.bside_311.util.ValidateUtil.resourceChangeableCheckByThisRequestToken; + @Service @Slf4j @RequiredArgsConstructor @@ -73,10 +74,7 @@ public void deleteAttach(Long attachNo, Long myUserNo) { Attach attach = attachRepository.findByIdAndDelYnIs(attachNo, YesOrNo.N) .orElseThrow( () -> new IllegalArgumentException("존재하지 않는 첨부파일입니다.")); - - if (!ObjectUtils.nullSafeEquals(myUserNo, attach.getCreatedBy())) { - throw new IllegalArgumentException("자신의 자원만 삭제할 수 있습니다."); - } + resourceChangeableCheckByThisRequestToken(attach); // delete attach attach.setDelYn(YesOrNo.Y); diff --git a/server/src/main/java/com/bside/bside_311/service/PostService.java b/server/src/main/java/com/bside/bside_311/service/PostService.java index 0997997..c4674f7 100644 --- a/server/src/main/java/com/bside/bside_311/service/PostService.java +++ b/server/src/main/java/com/bside/bside_311/service/PostService.java @@ -1,7 +1,5 @@ package com.bside.bside_311.service; -import com.bside.bside_311.dto.AddAlcoholRequestDto; -import com.bside.bside_311.dto.AddAlcoholResponseDto; import com.bside.bside_311.dto.AddCommentRequestDto; import com.bside.bside_311.dto.AddCommentResponseDto; import com.bside.bside_311.dto.AddPostResponseDto; @@ -12,9 +10,12 @@ import com.bside.bside_311.dto.GetPostResponseDto; import com.bside.bside_311.dto.GetPostVo; import com.bside.bside_311.dto.GetPostsMvo; +import com.bside.bside_311.dto.GetPostsToOneMvo; import com.bside.bside_311.dto.GetQuotesByPostResponseDto; import com.bside.bside_311.dto.PostResponseDto; +import com.bside.bside_311.dto.PostSearchCondition; import com.bside.bside_311.entity.Alcohol; +import com.bside.bside_311.entity.Attach; import com.bside.bside_311.entity.AttachType; import com.bside.bside_311.entity.Comment; import com.bside.bside_311.entity.Post; @@ -36,23 +37,29 @@ import com.bside.bside_311.repository.UserFollowRepository; import com.bside.bside_311.repository.UserRepository; import com.bside.bside_311.util.AuthUtil; +import com.bside.bside_311.util.MessageUtil; import io.micrometer.common.util.StringUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.ObjectUtils; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; -import java.util.stream.Collectors; +import java.util.Map; + +import static com.bside.bside_311.util.ValidateUtil.resourceChangeableCheckByThisRequestToken; @Service @Slf4j @RequiredArgsConstructor @Transactional public class PostService { - private final AlcoholService alcoholService; private final TagService tagService; private final UserRepository userRepository; private final PostLikeRepository postLikeRepository; @@ -68,7 +75,7 @@ public class PostService { public AddPostResponseDto addPost( Post post, Long alcoholNo, String alcoholFeature, - List tagStrList, AddAlcoholRequestDto alcoholInfo) { + List tagStrList) { log.info(">>> PostService.addPost"); postRepository.save(post); @@ -78,15 +85,8 @@ public AddPostResponseDto addPost( } postRepository.save(post); - if (alcoholInfo != null) { - if (StringUtils.isEmpty(alcoholInfo.getAlcoholName())) { - throw new IllegalArgumentException("술 이름은 필수입니다."); - } - AddAlcoholResponseDto addAlcoholResponseDto = alcoholService.addAlcohol(alcoholInfo); - alcoholNo = addAlcoholResponseDto.getAlcoholNo(); - } - if (alcoholNo != null && alcoholFeature != null) { + if (alcoholNo != null) { Alcohol alcohol = alcoholRepository.findByIdAndDelYnIs(alcoholNo, YesOrNo.N).orElseThrow( () -> new IllegalArgumentException("술이 존재하지 않습니다.")); PostAlcohol postAlcohol = PostAlcohol.of(post, alcohol, alcoholFeature); @@ -100,7 +100,17 @@ public AddPostResponseDto addPost( public void editPost(Long postNo, EditPostRequestDto editPostRequestDto) { Post post = postRepository.findByIdAndDelYnIs(postNo, YesOrNo.N).orElseThrow( - () -> new IllegalArgumentException("게시글이 존재하지 않습니다.")); + () -> new IllegalArgumentException(MessageUtil.POST_NOT_FOUND_MSG)); + resourceChangeableCheckByThisRequestToken(post); + if (StringUtils.isNotEmpty(editPostRequestDto.getPostContent())) { + post.setContent(editPostRequestDto.getPostContent()); + } + if (StringUtils.isNotEmpty(editPostRequestDto.getPostType())) { + post.setContent(editPostRequestDto.getPostType()); + } + if (StringUtils.isNotEmpty(editPostRequestDto.getPositionInfo())) { + post.setContent(editPostRequestDto.getPositionInfo()); + } postRepository.save(post); List tagStrList = editPostRequestDto.getTagList(); @@ -114,15 +124,6 @@ public void editPost(Long postNo, EditPostRequestDto editPostRequestDto) { } postRepository.save(post); - AddAlcoholRequestDto alcoholInfo = editPostRequestDto.getAlcoholInfo(); - if (alcoholInfo != null) { - if (StringUtils.isEmpty(alcoholInfo.getAlcoholName())) { - throw new IllegalArgumentException("술 이름은 필수입니다."); - } - AddAlcoholResponseDto addAlcoholResponseDto = alcoholService.addAlcohol(alcoholInfo); - alcoholNo = addAlcoholResponseDto.getAlcoholNo(); - } - if (alcoholNo != null && alcoholFeature != null) { Alcohol alcohol = alcoholRepository.findByIdAndDelYnIs(alcoholNo, YesOrNo.N).orElseThrow( () -> new IllegalArgumentException("술이 존재하지 않습니다.")); @@ -133,47 +134,29 @@ public void editPost(Long postNo, EditPostRequestDto editPostRequestDto) { public void deletePost(Long postNo) { Post post = postRepository.findByIdAndDelYnIs(postNo, YesOrNo.N).orElseThrow( - () -> new IllegalArgumentException("게시글이 존재하지 않습니다.")); + () -> new IllegalArgumentException(MessageUtil.POST_NOT_FOUND_MSG)); + resourceChangeableCheckByThisRequestToken(post); post.setDelYn(YesOrNo.Y); } -// private void addOrSetPost(Post post, Long alcoholNo, String alcoholFeature, -// List tagList) { -// postRepository.save(post); -// if (CollectionUtils.isNotEmpty(tagList)) { -// for (String tagName : tagList) { -// Tag tag = tagRepository.findByNameAndDelYnIs(tagName, YesOrNo.N).orElse(Tag.of()); -// tagRepository.save(tag); -// } -// } -// if (alcoholNo != null && alcoholFeature != null) { -// Alcohol alcohol = alcoholRepository.findByIdAndDelYnIs(alcoholNo, YesOrNo.N).orElseThrow( -// () -> new IllegalArgumentException("술이 존재하지 않습니다.")); -// PostAlcohol postAlcohol = PostAlcohol.of(post, alcohol, alcoholFeature); -// post.addPostAlcohol(postAlcohol); -// alcohol.addPostAlcohol(postAlcohol); -// } -// } - - public PostResponseDto getPostDetail(Long postNo, List attachDtos) { Post post = postRepository.findByIdAndDelYnIs(postNo, YesOrNo.N).orElseThrow( - () -> new IllegalArgumentException("게시글이 존재하지 않습니다.")); + () -> new IllegalArgumentException(MessageUtil.POST_NOT_FOUND_MSG)); Long userNo = post.getCreatedBy(); User user = null; if (userNo != null) { user = userRepository.findByIdAndDelYnIs(userNo, YesOrNo.N).orElseThrow( - () -> new IllegalArgumentException("유저가 존재하지 않습니다.")); + () -> new IllegalArgumentException(MessageUtil.USER_NOT_FOUND_MSG)); } Alcohol alcohol = null; List postAlcohols = post.getPostAlcohols(); if (CollectionUtils.isNotEmpty(postAlcohols)) { - alcohol = postAlcohols.get(0).getAlcohol(); + alcohol = postAlcohols.stream().filter(postAlcohol -> postAlcohol.getDelYn() == YesOrNo.N) + .map(PostAlcohol::getAlcohol).findFirst().orElse(null); } List nonDeletedPostTags = - post.getPostTags().stream().filter(postTag -> postTag.getDelYn() == YesOrNo.N).collect( - Collectors.toList()); + post.getPostTags().stream().filter(postTag -> postTag.getDelYn() == YesOrNo.N).toList(); List tags = tagRepository.findByPostTagsInAndDelYnIs(nonDeletedPostTags, YesOrNo.N); List comments = commentRepository.findByPostAndDelYnIs(post, YesOrNo.N); @@ -208,7 +191,7 @@ public PostResponseDto getPostDetail(Long postNo) { } public GetPostResponseDto getPosts(Long page, Long size, String orderColumn, String orderType, - String searchKeyword) { + String searchKeyword, List searchUserNoList) { GetPostVo getPostVo = GetPostVo.builder() .page(page) .offset(page * size) @@ -216,20 +199,74 @@ public GetPostResponseDto getPosts(Long page, Long size, String orderColumn, Str .orderColumn(orderColumn) .orderType(orderType) .searchKeyword(searchKeyword) + .searchUserNoList(searchUserNoList) .build(); List getPostsMvos = postMybatisRepository.getPosts(getPostVo); Long totalCount = postMybatisRepository.getPostsCount(getPostVo); - List posts = getPostsMvos.stream().map(Post::of).collect(Collectors.toList()); + List posts = getPostsMvos.stream().map(Post::of).toList(); List list = - posts.stream().map(post -> getPostDetail(post.getId())).collect(Collectors.toList()); + posts.stream().map(post -> getPostDetail(post.getId())).toList(); // FIXME // 추구 여기서 첨부파일 개수 쿼리 최적화. return GetPostResponseDto.of(list, totalCount); } + public Page getPostsV2(Pageable pageable, + String searchKeyword, List searchUserNoList, + Boolean isLikedByMe, Boolean isCommentedByMe) { + Page posts = + postRepository.searchPageSimple(PostSearchCondition.builder().searchKeyword(searchKeyword) + .searchUserNoList(searchUserNoList) + .isLikedByMe(isLikedByMe) + .isCommentedByMe(isCommentedByMe) + .myUserNo( + AuthUtil.getUserNoFromAuthentication()) + .build(), pageable); + List postNos = posts.stream().map(Post::getId).toList(); + List postsToOneList = + CollectionUtils.isNotEmpty(postNos) ? postMybatisRepository.getPostsToOne(postNos) : + List.of(); + Map postsToOneMap = new HashMap<>(); + for (GetPostsToOneMvo getPostsToOneMvo : postsToOneList) { + postsToOneMap.put(getPostsToOneMvo.getPostNo(), getPostsToOneMvo); + } + + List postAttachList = + attachRepository.findByRefNoInAndAttachTypeIsAndDelYnIs(postNos, AttachType.POST, + YesOrNo.N); + Map> pToAMap = new HashMap<>(); + for (Attach attach : postAttachList) { + if (!pToAMap.containsKey(attach.getRefNo())) { + pToAMap.put(attach.getRefNo(), new ArrayList<>()); + } + List attachDtos = pToAMap.get(attach.getRefNo()); + attachDtos.add(AttachDto.of(attach)); + } + + List postCreatedBys = posts.stream().map(Post::getCreatedBy).toList(); + List userAttachList = + attachRepository.findByRefNoInAndAttachTypeIsAndDelYnIs(postCreatedBys, AttachType.PROFILE, + YesOrNo.N); + Map> uToAMap = new HashMap<>(); + for (Attach attach : userAttachList) { + if (!uToAMap.containsKey(attach.getRefNo())) { + uToAMap.put(attach.getRefNo(), new ArrayList<>()); + } + List attachDtos = uToAMap.get(attach.getRefNo()); + attachDtos.add(AttachDto.of(attach)); + } + + return posts.map(post -> { + GetPostsToOneMvo getPostsToOneMvo = postsToOneMap.get(post.getId()); + List postAttachDtos = pToAMap.getOrDefault(post.getId(), new ArrayList<>()); + List userAttachDtos = uToAMap.getOrDefault(post.getCreatedBy(), new ArrayList<>()); + return PostResponseDto.of(post, getPostsToOneMvo, postAttachDtos, userAttachDtos); + }); + } + public AddCommentResponseDto addComment(Long postNo, AddCommentRequestDto addCommentRequestDto) { Post post = postRepository.findByIdAndDelYnIs(postNo, YesOrNo.N).orElseThrow( - () -> new IllegalArgumentException("게시글이 존재하지 않습니다.")); + () -> new IllegalArgumentException(MessageUtil.POST_NOT_FOUND_MSG)); Comment comment = Comment.of(post, addCommentRequestDto.getCommentContent()); post.addComment(comment); commentRepository.save(comment); @@ -239,19 +276,41 @@ public AddCommentResponseDto addComment(Long postNo, AddCommentRequestDto addCom public GetPostCommentsResponseDto getPostComments(Long postNo) { Post post = postRepository.findByIdAndDelYnIs(postNo, YesOrNo.N).orElseThrow( - () -> new IllegalArgumentException("게시글이 존재하지 않습니다.")); + () -> new IllegalArgumentException(MessageUtil.POST_NOT_FOUND_MSG)); List comments = - post.getComments().stream().filter(comment -> comment.getDelYn() == YesOrNo.N).collect( - Collectors.toList()); - return GetPostCommentsResponseDto.of(comments); + post.getComments().stream().filter(comment -> comment.getDelYn() == YesOrNo.N).toList(); + // FIXME 최적화. 추후 페이징 처리할것. + List commentCreatedList = comments.stream().map(Comment::getCreatedBy).toList(); + List userList = + userRepository.findAllByIdInAndDelYnIs(commentCreatedList, YesOrNo.N); + Map createdByToUser = new HashMap<>(); + for (User user : userList) { + createdByToUser.put(user.getId(), user); + } + + List attachList = + attachRepository.findByRefNoInAndAttachTypeIsAndDelYnIs(commentCreatedList, + AttachType.PROFILE, + YesOrNo.N); + Map> uToAMap = new HashMap<>(); + for (Attach attach : attachList) { + if (!uToAMap.containsKey(attach.getRefNo())) { + uToAMap.put(attach.getRefNo(), new ArrayList<>()); + } + List attachDtos = uToAMap.get(attach.getRefNo()); + attachDtos.add(AttachDto.of(attach)); + } + + return GetPostCommentsResponseDto.of(comments, createdByToUser, uToAMap); } public void editComment(Long postNo, Long commentNo, EditCommentRequestDto editCommentRequestDto) { - Post post = postRepository.findByIdAndDelYnIs(postNo, YesOrNo.N).orElseThrow( - () -> new IllegalArgumentException("게시글이 존재하지 않습니다.")); + postRepository.findByIdAndDelYnIs(postNo, YesOrNo.N).orElseThrow( + () -> new IllegalArgumentException(MessageUtil.POST_NOT_FOUND_MSG)); Comment comment = commentRepository.findByIdAndDelYnIs(commentNo, YesOrNo.N).orElseThrow( () -> new IllegalArgumentException("댓글이 존재하지 않습니다.")); + resourceChangeableCheckByThisRequestToken(comment); if (!ObjectUtils.isEmpty(editCommentRequestDto) && StringUtils.isNotBlank(editCommentRequestDto.getCommentContent())) { comment.setContent(editCommentRequestDto.getCommentContent()); @@ -259,16 +318,17 @@ public void editComment(Long postNo, Long commentNo, } public void deleteComment(Long postNo, Long commentNo) { - Post post = postRepository.findByIdAndDelYnIs(postNo, YesOrNo.N).orElseThrow( - () -> new IllegalArgumentException("게시글이 존재하지 않습니다.")); + postRepository.findByIdAndDelYnIs(postNo, YesOrNo.N).orElseThrow( + () -> new IllegalArgumentException(MessageUtil.POST_NOT_FOUND_MSG)); Comment comment = commentRepository.findByIdAndDelYnIs(commentNo, YesOrNo.N).orElseThrow( () -> new IllegalArgumentException("댓글이 존재하지 않습니다.")); + resourceChangeableCheckByThisRequestToken(comment); comment.setDelYn(YesOrNo.Y); } public PostQuote addQuote(Long postNo, Long quotedPostNo) { Post post = postRepository.findByIdAndDelYnIs(postNo, YesOrNo.N).orElseThrow( - () -> new IllegalArgumentException("게시글이 존재하지 않습니다.")); + () -> new IllegalArgumentException(MessageUtil.POST_NOT_FOUND_MSG)); Post quotedPost = postRepository.findByIdAndDelYnIs(quotedPostNo, YesOrNo.N).orElseThrow( () -> new IllegalArgumentException("인용할 게시글이 존재하지 않습니다.")); @@ -282,38 +342,39 @@ public PostQuote addQuote(Long postNo, Long quotedPostNo) { public void deleteQuote(Long quoteNo) { PostQuote postQuote = postQuoteRepository.findByIdAndDelYnIs(quoteNo, YesOrNo.N).orElseThrow( () -> new IllegalArgumentException("인용이 존재하지 않습니다.")); + resourceChangeableCheckByThisRequestToken(postQuote); postQuote.setDelYn(YesOrNo.Y); } public GetQuotesByPostResponseDto getQuotesByPost(Long postNo) { Post post = postRepository.findByIdAndDelYnIs(postNo, YesOrNo.N).orElseThrow( - () -> new IllegalArgumentException("게시글이 존재하지 않습니다.")); + () -> new IllegalArgumentException(MessageUtil.POST_NOT_FOUND_MSG)); List postQuotes = postQuoteRepository.findByPostAndDelYnIs(post, YesOrNo.N); return GetQuotesByPostResponseDto.of(postQuotes); } public void likePost(Long userNo, Long postNo) { User user = userRepository.findByIdAndDelYnIs(userNo, YesOrNo.N).orElseThrow( - () -> new IllegalArgumentException("유저가 존재하지 않습니다.")); + () -> new IllegalArgumentException(MessageUtil.USER_NOT_FOUND_MSG)); Post post = postRepository.findByIdAndDelYnIs(postNo, YesOrNo.N).orElseThrow( - () -> new IllegalArgumentException("게시글이 존재하지 않습니다.")); + () -> new IllegalArgumentException(MessageUtil.POST_NOT_FOUND_MSG)); PostLike postLike = postLikeRepository.findByUserAndPostAndDelYnIs(user, post, YesOrNo.N).orElse( PostLike.of(user, post)); postLikeRepository.save(postLike); - return; } public void likeCancelPost(Long userNo, Long postNo) { User user = userRepository.findByIdAndDelYnIs(userNo, YesOrNo.N).orElseThrow( - () -> new IllegalArgumentException("유저가 존재하지 않습니다.")); + () -> new IllegalArgumentException(MessageUtil.USER_NOT_FOUND_MSG)); Post post = postRepository.findByIdAndDelYnIs(postNo, YesOrNo.N).orElseThrow( - () -> new IllegalArgumentException("게시글이 존재하지 않습니다.")); + () -> new IllegalArgumentException(MessageUtil.POST_NOT_FOUND_MSG)); PostLike postLike = postLikeRepository.findByUserAndPostAndDelYnIs(user, post, YesOrNo.N).orElseThrow( () -> new IllegalArgumentException("좋아요가 존재하지 않습니다.")); + resourceChangeableCheckByThisRequestToken(postLike); postLike.setDelYn(YesOrNo.Y); } } diff --git a/server/src/main/java/com/bside/bside_311/service/UserService.java b/server/src/main/java/com/bside/bside_311/service/UserService.java index e22b81b..305aadf 100644 --- a/server/src/main/java/com/bside/bside_311/service/UserService.java +++ b/server/src/main/java/com/bside/bside_311/service/UserService.java @@ -6,6 +6,7 @@ import com.bside.bside_311.dto.LoginResponseDto; import com.bside.bside_311.dto.MyInfoResponseDto; import com.bside.bside_311.dto.UserLoginRequestDto; +import com.bside.bside_311.dto.UserResponseDto; import com.bside.bside_311.dto.UserSignupResponseDto; import com.bside.bside_311.dto.UserUpdateRequestDto; import com.bside.bside_311.entity.Attach; @@ -21,6 +22,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; @@ -29,11 +32,14 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; -import java.util.Optional; +import java.util.Map; import static com.bside.bside_311.util.JwtUtil.NORMAL_TOKEN; import static com.bside.bside_311.util.JwtUtil.normalValidity; +import static com.bside.bside_311.util.ValidateUtil.resourceChangeableCheckByThisRequestToken; @Service @Slf4j @@ -76,6 +82,8 @@ public LoginResponseDto login(UserLoginRequestDto userLoginRequestDto) { public void updateUser(Long userNo, UserUpdateRequestDto userUpdateRequestDto) { User user = userRepository.findByIdAndDelYnIs(userNo, YesOrNo.N) .orElseThrow(() -> new IllegalArgumentException("유저가 존재하지 않습니다.")); + // resourceChangeableCheckByThisRequestToken(user); + // 리소스 변경 예외. 유저 생성시에는 createdBy정보가 없음. if (userUpdateRequestDto != null) { if (userUpdateRequestDto.getIntroduction() != null) { user.setIntroduction(userUpdateRequestDto.getIntroduction()); @@ -92,15 +100,19 @@ public GetUserInfoResponseDto getUserInfo(Long userNo) { YesOrNo.N); Long followerCount = userFollowRepository.countByFollowedAndDelYnIs(user.of(userNo), YesOrNo.N); + Long followingCount = + userFollowRepository.countByFollowingAndDelYnIs(user.of(userNo), YesOrNo.N); Boolean isFollowing = null; Long myUserNo = AuthUtil.getUserNoFromAuthentication(); if (myUserNo != null) { isFollowing = userFollowRepository.findByFollowingAndFollowedAndDelYnIs(user.of(myUserNo), - user.of(userNo), YesOrNo.N) - .isPresent(); + user.of(userNo), YesOrNo.N) + .isPresent(); } - return GetUserInfoResponseDto.of(MyInfoResponseDto.of(user, AttachDto.of(profileAttachList), followerCount), isFollowing); + return GetUserInfoResponseDto.of( + MyInfoResponseDto.of(user, AttachDto.of(profileAttachList), followerCount, followingCount), + isFollowing); } public MyInfoResponseDto getMyInfo(Long myUserNo) { @@ -111,12 +123,17 @@ public MyInfoResponseDto getMyInfo(Long myUserNo) { YesOrNo.N); Long followerCount = userFollowRepository.countByFollowedAndDelYnIs(user.of(myUserNo), YesOrNo.N); - return MyInfoResponseDto.of(user, AttachDto.of(profileAttachList), followerCount); + Long followingCount = + userFollowRepository.countByFollowingAndDelYnIs(user.of(myUserNo), YesOrNo.N); + return MyInfoResponseDto.of(user, AttachDto.of(profileAttachList), followerCount, + followingCount); } public void withdraw(Long myUserNo) { User user = userRepository.findByIdAndDelYnIs(myUserNo, YesOrNo.N) .orElseThrow(() -> new IllegalArgumentException("유저가 존재하지 않습니다.")); + // resourceChangeableCheckByThisRequestToken(user); + // 리소스 변경 예외. 유저 생성시에는 createdBy정보가 없음. user.setDelYn(YesOrNo.Y); userRepository.save(user); } @@ -129,6 +146,8 @@ public void chagePoassword(ChangePasswordRequestDto changePasswordRequestDto) { } User me = userRepository.findByIdAndDelYnIs(myUserNo, YesOrNo.N) .orElseThrow(() -> new IllegalArgumentException("유저가 존재하지 않습니다.")); + // resourceChangeableCheckByThisRequestToken(me); + // 리소스 변경 예외. 유저 생성시에는 createdBy정보가 없음. if (!passwordEncoder.matches(changePasswordRequestDto.getPassword(), me.getPassword())) { throw new BadCredentialsException("비밀번호가 일치하지 않습니다."); } @@ -160,6 +179,45 @@ public void unfollowUser(Long myUserNo, Long followingUserNo) { UserFollow userFollow = userFollowRepository.findByFollowingAndFollowedAndDelYnIs(me, followingUser, YesOrNo.N) .orElseThrow(() -> new IllegalArgumentException("팔로우하지 않은 유저입니다.")); + resourceChangeableCheckByThisRequestToken(userFollow); userFollow.setDelYn(YesOrNo.Y); } + + public Page getMyFollowingUsers(Long myUserNo, Pageable pageable) { + Page users = userRepository.getMyFollowingUsersPage(myUserNo, pageable); + List userNos = users.stream().map(User::getId).toList(); + Map> uToAMap = getUserAttachInfos(userNos); + + return users.map(user -> { + List attachDtos = uToAMap.getOrDefault(user.getId(), List.of()); + return UserResponseDto.of(user, attachDtos); + }); + } + + public Page getUsersOfFollowingMe(Long myUserNo, Pageable pageable) { + Page users = userRepository.getUsersOfFollowingMePage(myUserNo, pageable); + List userNos = users.stream().map(User::getId).toList(); + Map> uToAMap = getUserAttachInfos(userNos); + + return users.map(user -> { + List attachDtos = uToAMap.getOrDefault(user.getId(), List.of()); + return UserResponseDto.of(user, attachDtos); + }); + } + + public Map> getUserAttachInfos(List userNos) { + + List userAttachList = + attachRepository.findByRefNoInAndAttachTypeIsAndDelYnIs(userNos, AttachType.PROFILE, + YesOrNo.N); + Map> uToAMap = new HashMap<>(); + for (Attach attach : userAttachList) { + if (!uToAMap.containsKey(attach.getRefNo())) { + uToAMap.put(attach.getRefNo(), new ArrayList<>()); + } + List attachDtos = uToAMap.get(attach.getRefNo()); + attachDtos.add(AttachDto.of(attach)); + } + return uToAMap; + } } diff --git a/server/src/main/java/com/bside/bside_311/util/MessageUtil.java b/server/src/main/java/com/bside/bside_311/util/MessageUtil.java new file mode 100644 index 0000000..9ff5a39 --- /dev/null +++ b/server/src/main/java/com/bside/bside_311/util/MessageUtil.java @@ -0,0 +1,11 @@ +package com.bside.bside_311.util; + +public class MessageUtil { + public static final String POST_NOT_FOUND_MSG = "게시글이 존재하지 않습니다."; + public static final String USER_NOT_FOUND_MSG = "유저가 존재하지 않습니다."; + + private MessageUtil() { + } + + +} diff --git a/server/src/main/java/com/bside/bside_311/util/ValidateUtil.java b/server/src/main/java/com/bside/bside_311/util/ValidateUtil.java new file mode 100644 index 0000000..ec2526b --- /dev/null +++ b/server/src/main/java/com/bside/bside_311/util/ValidateUtil.java @@ -0,0 +1,28 @@ +package com.bside.bside_311.util; + +import com.bside.bside_311.entity.BaseEntity; +import com.bside.bside_311.entity.Role; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.ObjectUtils; + +import java.util.Collection; + +public class ValidateUtil { + + public static void resourceChangeableCheckByThisRequestToken(BaseEntity base) { + if (ObjectUtils.isEmpty(base) || ObjectUtils.isEmpty(base.getCreatedBy())) { + throw new IllegalArgumentException("본인이 작성한 리소스만 변경 가능합니다."); + } + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Collection authorities = authentication.getAuthorities(); + if (authorities.stream().anyMatch(o -> o.getAuthority().equals(Role.ROLE_ADMIN.name()))) { + // 관리자는 모든 리소스에 대해 변경 가능 + return; + } + if (!base.getCreatedBy().equals(Long.parseLong(authentication.getName()))) { + throw new IllegalArgumentException("본인이 작성한 리소스만 변경 가능합니다."); + } + } +} diff --git a/server/src/main/resources/application-local.yml b/server/src/main/resources/application-local.yml index b502a16..695b1b6 100644 --- a/server/src/main/resources/application-local.yml +++ b/server/src/main/resources/application-local.yml @@ -24,9 +24,14 @@ spring: use_sql_comments: true default_batch_fetch_size: 1000 -logging.level: - org.hibernate.SQL: debug +#logging.level: +# org.hibernate.SQL: debug # org.hibernate.orm.jdbc.bind: trace -cloudinary: - url: cloudinary://884757229436341:Y9RC2dqN7SDMC3zOqs4qtDICMDo@drezugbxz \ No newline at end of file +p6spy: + detail: false + +decorator: + datasource: + p6spy: + enable-logging: true \ No newline at end of file diff --git a/server/src/main/resources/example/attach/alcohol/1.png b/server/src/main/resources/example/attach/alcohol/1.png new file mode 100644 index 0000000..c407019 Binary files /dev/null and b/server/src/main/resources/example/attach/alcohol/1.png differ diff --git a/server/src/main/resources/example/attach/alcohol/2.png b/server/src/main/resources/example/attach/alcohol/2.png new file mode 100644 index 0000000..1198ee7 Binary files /dev/null and b/server/src/main/resources/example/attach/alcohol/2.png differ diff --git a/server/src/main/resources/example/attach/alcohol/3.png b/server/src/main/resources/example/attach/alcohol/3.png new file mode 100644 index 0000000..6d91b73 Binary files /dev/null and b/server/src/main/resources/example/attach/alcohol/3.png differ diff --git a/server/src/main/resources/example/attach/alcohol/4.png b/server/src/main/resources/example/attach/alcohol/4.png new file mode 100644 index 0000000..c82fded Binary files /dev/null and b/server/src/main/resources/example/attach/alcohol/4.png differ diff --git a/server/src/main/resources/example/attach/alcohol/5.png b/server/src/main/resources/example/attach/alcohol/5.png new file mode 100644 index 0000000..ba74a36 Binary files /dev/null and b/server/src/main/resources/example/attach/alcohol/5.png differ diff --git a/server/src/main/resources/example/attach/post/1.png b/server/src/main/resources/example/attach/post/1.png new file mode 100644 index 0000000..c1d1833 Binary files /dev/null and b/server/src/main/resources/example/attach/post/1.png differ diff --git a/server/src/main/resources/example/attach/post/2.png b/server/src/main/resources/example/attach/post/2.png new file mode 100644 index 0000000..9743556 Binary files /dev/null and b/server/src/main/resources/example/attach/post/2.png differ diff --git a/server/src/main/resources/example/attach/post/3.png b/server/src/main/resources/example/attach/post/3.png new file mode 100644 index 0000000..db7caa6 Binary files /dev/null and b/server/src/main/resources/example/attach/post/3.png differ diff --git a/server/src/main/resources/example/attach/post/4.png b/server/src/main/resources/example/attach/post/4.png new file mode 100644 index 0000000..a7a1648 Binary files /dev/null and b/server/src/main/resources/example/attach/post/4.png differ diff --git a/server/src/main/resources/example/attach/post/5.png b/server/src/main/resources/example/attach/post/5.png new file mode 100644 index 0000000..356838a Binary files /dev/null and b/server/src/main/resources/example/attach/post/5.png differ diff --git a/server/src/main/resources/example/attach/profile/1.png b/server/src/main/resources/example/attach/profile/1.png new file mode 100644 index 0000000..779f553 Binary files /dev/null and b/server/src/main/resources/example/attach/profile/1.png differ diff --git a/server/src/main/resources/example/attach/profile/2.png b/server/src/main/resources/example/attach/profile/2.png new file mode 100644 index 0000000..8bc952e Binary files /dev/null and b/server/src/main/resources/example/attach/profile/2.png differ diff --git a/server/src/main/resources/example/attach/profile/3.png b/server/src/main/resources/example/attach/profile/3.png new file mode 100644 index 0000000..ace8e36 Binary files /dev/null and b/server/src/main/resources/example/attach/profile/3.png differ diff --git a/server/src/main/resources/example/attach/profile/4.png b/server/src/main/resources/example/attach/profile/4.png new file mode 100644 index 0000000..dd57aec Binary files /dev/null and b/server/src/main/resources/example/attach/profile/4.png differ diff --git a/server/src/main/resources/example/attach/profile/5.png b/server/src/main/resources/example/attach/profile/5.png new file mode 100644 index 0000000..1eed17c Binary files /dev/null and b/server/src/main/resources/example/attach/profile/5.png differ diff --git a/server/src/main/resources/mybatis/mapper/PostMapper.xml b/server/src/main/resources/mybatis/mapper/PostMapper.xml index 8fdaa2a..ff13b5a 100644 --- a/server/src/main/resources/mybatis/mapper/PostMapper.xml +++ b/server/src/main/resources/mybatis/mapper/PostMapper.xml @@ -7,6 +7,12 @@ and p.content like CONCAT('%', #{searchKeyword},'%') + + and p.created_by in + + #{userNo} + + + + \ No newline at end of file diff --git a/server/src/test/java/com/bside/bside_311/api/service/PostServiceTest.java b/server/src/test/java/com/bside/bside_311/api/service/PostServiceTest.java new file mode 100644 index 0000000..7241feb --- /dev/null +++ b/server/src/test/java/com/bside/bside_311/api/service/PostServiceTest.java @@ -0,0 +1,34 @@ +//package com.bside.bside_311.api.service; +// +//import com.bside.bside_311.dto.AddPostRequestDto; +//import com.bside.bside_311.entity.Post; +//import com.bside.bside_311.service.PostService; +//import org.junit.jupiter.api.DisplayName; +//import org.junit.jupiter.api.Test; +//import org.springframework.boot.test.context.SpringBootTest; +//import org.springframework.boot.test.mock.mockito.SpyBean; +//import org.springframework.transaction.annotation.Transactional; +// +//@SpringBootTest +//@Transactional +//public class PostServiceTest { +// +// @SpyBean +// private PostService postService; +// +// @Test("should return post") +// @DisplayName("postService.addPost") +// void addPost() { +// // given +// +// // then +// AddPostRequestDto addPostRequestDto = AddPostRequestDto.builder() +// .title("title") +// .content("content") +// .build(); +// postService.addPost(Post.of(addPostRequestDto), 1L, null, null); +// // when +// +// // then +// } +//} diff --git a/server/src/test/java/com/bside/bside_311/controller/ControllerTest.java b/server/src/test/java/com/bside/bside_311/controller/ControllerTest.java index 25fe93f..8815bba 100644 --- a/server/src/test/java/com/bside/bside_311/controller/ControllerTest.java +++ b/server/src/test/java/com/bside/bside_311/controller/ControllerTest.java @@ -2,9 +2,19 @@ import com.bside.bside_311.Bside311Application; import com.bside.bside_311.config.security.WebSecurityConfig; +import com.bside.bside_311.entity.Role; +import com.bside.bside_311.entity.User; +import com.bside.bside_311.util.JwtUtil; +import org.junit.jupiter.api.BeforeEach; import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.test.context.ContextConfiguration; +import static com.bside.bside_311.util.JwtUtil.NORMAL_TOKEN; +import static com.bside.bside_311.util.JwtUtil.normalValidity; + @ContextConfiguration(classes = { Bside311Application.class, WebSecurityConfig.class, @@ -12,30 +22,55 @@ public abstract class ControllerTest { protected static final String USER_ID = "UserId"; protected static final String ADMIN_ID = "AdminId"; -// @SpyBean + // @SpyBean // protected AccessTokenGenerator accessTokenGenerator; // @MockBean // protected AuthUserDao authUserDao; + public User normalUser; + @SpyBean + protected JwtUtil jwtUtil; protected String userAccessToken; protected String adminAccessToken; -// @SpyBean + + + // @SpyBean // private AccessTokenService accessTokenService; // -// @BeforeEach -// void setUpAccessTokenAndUserDetailsDaoForAuthentication() { -// userAccessToken = accessTokenGenerator.generate(USER_ID); -// adminAccessToken = accessTokenGenerator.generate(ADMIN_ID); -// -// AuthUser user = AuthUser.authenticated( -// USER_ID, "ROLE_USER", userAccessToken); -// -// given(authUserDao.findByAccessToken(userAccessToken)) -// .willReturn(Optional.of(user)); -// -// AuthUser admin = AuthUser.authenticated( -// ADMIN_ID, "ROLE_ADMIN", adminAccessToken); -// -// given(authUserDao.findByAccessToken(adminAccessToken)) -// .willReturn(Optional.of(admin)); -// } + @BeforeEach + void setUpAccessTokenAndUserDetailsDaoForAuthentication() { + normalUser = User.builder() + .id(1L) + .userId("normalUser") + .password("normalUserPassword") + .email("normalUser@example.com") + .nickname("normalUserNickname") + .role(Role.ROLE_USER) + .introduction("normalUserIntroduction") + .build(); + Authentication normalUserAuthentication + = new UsernamePasswordAuthenticationToken(normalUser.getId(), + null, + AuthorityUtils.createAuthorityList(normalUser.getRole().toString())); + + userAccessToken = jwtUtil.createLocalToken(normalUser, NORMAL_TOKEN, normalValidity, + normalUserAuthentication); + + + User adminUser = User.builder() + .id(2L) + .userId("adminUser") + .password("adminUserPassword") + .email("adminUser@example.com") + .nickname("adminUserNickname") + .role(Role.ROLE_USER) + .introduction("adminUserIntroduction") + .build(); + Authentication adminUserAuthentication + = new UsernamePasswordAuthenticationToken(adminUser.getId(), + null, + AuthorityUtils.createAuthorityList(adminUser.getRole().toString())); + + adminAccessToken = + jwtUtil.createLocalToken(adminUser, NORMAL_TOKEN, normalValidity, adminUserAuthentication); + } } diff --git a/server/src/test/java/com/bside/bside_311/controller/UserControllerTest.java b/server/src/test/java/com/bside/bside_311/controller/UserControllerTest.java index 683688b..cc2f54d 100644 --- a/server/src/test/java/com/bside/bside_311/controller/UserControllerTest.java +++ b/server/src/test/java/com/bside/bside_311/controller/UserControllerTest.java @@ -1,22 +1,45 @@ package com.bside.bside_311.controller; +import com.bside.bside_311.dto.LoginResponseDto; +import com.bside.bside_311.dto.MyInfoResponseDto; +import com.bside.bside_311.dto.UserLoginRequestDto; +import com.bside.bside_311.dto.UserResponseDto; +import com.bside.bside_311.dto.UserSignupRequestDto; +import com.bside.bside_311.dto.UserUpdateRequestDto; +import com.bside.bside_311.entity.User; +import com.bside.bside_311.entity.UserFollow; import com.bside.bside_311.service.UserService; +import com.bside.bside_311.util.JwtUtil; +import com.bside.bside_311.utils.UserUtils; +import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatchers; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; -import org.springframework.test.web.servlet.ResultActions; -import static org.junit.jupiter.api.Assertions.*; +import java.util.List; + +import static org.hamcrest.Matchers.containsString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(UserController.class) -class UserControllerTest extends ControllerTest{ +class UserControllerTest extends ControllerTest { @Autowired private MockMvc mockMvc; @@ -24,6 +47,9 @@ class UserControllerTest extends ControllerTest{ @MockBean private UserService userService; + @Autowired + private ObjectMapper objectMapper; + @Test @DisplayName("회원가입 성공") void signupSuccess() throws Exception { @@ -33,7 +59,7 @@ void signupSuccess() throws Exception { String name = "Newbie"; String json = String.format( - """ + """ { "email": "%s", "password": "%s", @@ -45,8 +71,8 @@ void signupSuccess() throws Exception { ); MvcResult mvcResult = mockMvc.perform(post("/user/signup") - .contentType(MediaType.APPLICATION_JSON) - .content(json)) + .contentType(MediaType.APPLICATION_JSON) + .content(json)) .andExpect(status().isCreated()) .andReturn(); String responseBody = mvcResult.getResponse().getContentAsString(); @@ -63,21 +89,187 @@ void signupFail_emptynickname() throws Exception { String json = String.format( """ - { - "email": "%s", - "password": "%s", - "id": "%s" - } - """, + { + "email": "%s", + "password": "%s", + "id": "%s" + } + """, email, password, id, name ); MvcResult mvcResult = mockMvc.perform(post("/user/signup") - .contentType(MediaType.APPLICATION_JSON) - .content(json)) + .contentType(MediaType.APPLICATION_JSON) + .content(json)) .andExpect(status().is4xxClientError()) .andReturn(); String responseBody = mvcResult.getResponse().getContentAsString(); System.out.println(responseBody); } + + @Test + @DisplayName("어드민 회원가입 성공") + void signupAdminSuccess() throws Exception { + + MvcResult mvcResult = mockMvc.perform(post("/user/signup/admin-special") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString( + UserUtils.makeSimpleUser()))) + .andExpect(status().isCreated()) + .andReturn(); + String responseBody = mvcResult.getResponse().getContentAsString(); + System.out.println(responseBody); + } + + @Test + @DisplayName("어드민 회원가입 실패_패스워드 없음.") + void signupAdminFailDueToPassword() throws Exception { + UserSignupRequestDto userSignupRequestDto = UserSignupRequestDto.builder() + .email("test@example.com") + .password(null) + .id("testid") + .nickname("nickname") + .build(); + + MvcResult mvcResult = mockMvc.perform(post("/user/signup/admin-special") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString( + userSignupRequestDto))) + .andExpect(status().is4xxClientError()) + .andReturn(); + String responseBody = mvcResult.getResponse().getContentAsString(); + System.out.println(responseBody); + } + + @Test + @DisplayName("로그인 성공") + void loginSuccess() throws Exception { + String id = "test1"; + String password = "password"; + UserLoginRequestDto userLoginRequestDto = UserLoginRequestDto.builder() + .id(id) + .password(password) + .build(); + given(userService.login(userLoginRequestDto)) + .willReturn(LoginResponseDto.of("testsuccess")); + mockMvc.perform(post("/user/login") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString( + userLoginRequestDto))) + .andExpect(status().isOk()); + } + + @Test + @DisplayName("로그인 실패. - 아이디 입력 부재.") + void loginFail_id_not() throws Exception { + String password = "password"; + UserLoginRequestDto userLoginRequestDto = UserLoginRequestDto.builder() + .id(null) + .password(password) + .build(); + given(userService.login(userLoginRequestDto)) + .willReturn(LoginResponseDto.of("testsuccess")); + mockMvc.perform(post("/user/login") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString( + userLoginRequestDto))) + .andExpect(status().is4xxClientError()); + } + + @Test + @DisplayName("유저 정보 수정 성공.") + void userEditSuccess() throws Exception { + UserUpdateRequestDto userUpdateRequestDto = new UserUpdateRequestDto("안녕하세요~~~"); + + mockMvc.perform(patch("/user") + .header(HttpHeaders.AUTHORIZATION, JwtUtil.BEARER_PREFIX + userAccessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(userUpdateRequestDto + ))) + .andExpect(status().isOk()); + } + + @Test + @DisplayName("유저 비밀번호 변경 성공.") + void userChangePassword() throws Exception { + UserUpdateRequestDto userUpdateRequestDto = new UserUpdateRequestDto("안녕하세요~~~"); + + mockMvc.perform(patch("/user/pwd/change") + .header(HttpHeaders.AUTHORIZATION, JwtUtil.BEARER_PREFIX + userAccessToken) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(userUpdateRequestDto + ))) + .andExpect(status().isOk()); + } + + @Test + @DisplayName("GET /me success") + void me() throws Exception { + given(userService.getMyInfo(normalUser.getId())) + .willReturn(MyInfoResponseDto.of(normalUser, null, 0L, + 0L)); + + mockMvc.perform(get("/user/me") + .header("Authorization", "Bearer " + userAccessToken)) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("userNo"))); + } + + @Test + @DisplayName("회원 탈퇴. 성공.") + void user_delete_success() throws Exception { + + mockMvc.perform(delete("/user") + .header("Authorization", "Bearer " + userAccessToken)) + .andExpect(status().isOk()); + } + + @Test + @DisplayName("유저 팔로우. 성공.") + void user_follow_success() throws Exception { + given(userService.followUser(normalUser.getId(), 2L)) + .willReturn(UserFollow.builder().following(normalUser).followed(User.of(2L)) + .build()); + + mockMvc.perform(post(String.format("/user/follow/%d", 2)) + .header("Authorization", "Bearer " + userAccessToken)) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("userFollowNo"))); + } + + @Test + @DisplayName("유저 언팔로우. 성공.") + void user_unfollow_success() throws Exception { + mockMvc.perform(post(String.format("/user/follow/%d", 2)) + .header("Authorization", "Bearer " + userAccessToken)) + .andExpect(status().isOk()); + } + + @Test + @DisplayName("내가 팔로잉하는 유저 조회. 성공.") + void getMyFollowingUsers_success() throws Exception { + //given + Page pagedResponse = new PageImpl<>(List.of(UserResponseDto.of(normalUser))); + given(userService.getMyFollowingUsers(eq(normalUser.getId()), + ArgumentMatchers.any(Pageable.class))) + .willReturn(pagedResponse); + mockMvc.perform(get("/user//my-following-users") + .header("Authorization", "Bearer " + userAccessToken)) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("userNo"))); + } + + @Test + @DisplayName("나를 팔로잉하는 유저 조회. 성공.") + void getUsersOfFollowingMePage_success() throws Exception { + //given + Page pagedResponse = new PageImpl<>(List.of(UserResponseDto.of(normalUser))); + given(userService.getUsersOfFollowingMe(eq(normalUser.getId()), + ArgumentMatchers.any(Pageable.class))) + .willReturn(pagedResponse); + mockMvc.perform(get("/user/users-of-following-me") + .header("Authorization", "Bearer " + userAccessToken)) + .andExpect(status().isOk()) + .andExpect(content().string(containsString("userNo"))); + } } \ No newline at end of file diff --git a/server/src/test/java/com/bside/bside_311/repository/UserRepositoryTest.java b/server/src/test/java/com/bside/bside_311/repository/UserRepositoryTest.java new file mode 100644 index 0000000..4f1623f --- /dev/null +++ b/server/src/test/java/com/bside/bside_311/repository/UserRepositoryTest.java @@ -0,0 +1,139 @@ +package com.bside.bside_311.repository; + +import com.bside.bside_311.entity.User; +import com.bside.bside_311.entity.UserFollow; +import com.bside.bside_311.entity.YesOrNo; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +@SpringBootTest +@Transactional +//@Rollback(false) +class UserRepositoryTest { + @Autowired + UserRepository userRepository; + @Autowired + UserFollowRepository userFollowRepository; + @PersistenceContext + private EntityManager em; + + @Test + public void findAllByIdInAndDelYnIs() { + // given + // when + List allByIdInAndDelYnIs = userRepository.findAllByIdInAndDelYnIs(null, YesOrNo.N); + // then + allByIdInAndDelYnIs.forEach(System.out::println); + } + + @Test + public void findAllByIdInAndDelYnIs2() { + // given + // when + Optional byIdAndDelYnIs = userRepository.findByIdAndDelYnIs(1L, YesOrNo.N); + // then + Assertions.assertThat(byIdAndDelYnIs).isEmpty(); + } + + @Test + public void getMyFollowingUsersPage_success() { + // given + int limit = 300; + + Sort sort = Sort.by(Sort.Direction.DESC, "id"); + PageRequest pageRequest = PageRequest.of(0, limit, sort); + + // + List userList = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + User test = User.builder().id(i + 1L) + .userId(String.format("test%d", i)) + .nickname(String.format("test%d", i)) + .password("test1!").build(); + userList.add(test); + userRepository.save(test); + } + + em.flush(); + // 1L -> 2L follow + userFollowRepository.save( + UserFollow.of(userList.get(0), userList.get(1)) + ); + + // 1L -> 3L follow + userFollowRepository.save( + UserFollow.of(userList.get(0), userList.get(2)) + ); + + // 3L -> 4L follow + userFollowRepository.save( + UserFollow.of(userList.get(2), userList.get(3)) + ); + + + // when + Page myFollowingUsersPage = userRepository.getMyFollowingUsersPage(1L, pageRequest); + // then + Assertions.assertThat(myFollowingUsersPage.getContent().size()).isEqualTo(2); + } + + @Test + public void getUsersOfFollowingMePage_success() { + // given + int limit = 300; + + Sort sort = Sort.by(Sort.Direction.DESC, "id"); + PageRequest pageRequest = PageRequest.of(0, limit, sort); + + // + List userList = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + User test = User.builder().id(i + 1L) + .userId(String.format("test%d", i)) + .nickname(String.format("test%d", i)) + .password("test1!").build(); + userList.add(test); + userRepository.save(test); + } + + em.flush(); + // 1L -> 2L follow + userFollowRepository.save( + UserFollow.of(userList.get(0), userList.get(1)) + ); + + // 1L -> 3L follow + userFollowRepository.save( + UserFollow.of(userList.get(0), userList.get(2)) + ); + + // 3L -> 4L follow + userFollowRepository.save( + UserFollow.of(userList.get(2), userList.get(3)) + ); + + // 2L -> 4L follow + userFollowRepository.save( + UserFollow.of(userList.get(1), userList.get(3)) + ); + + + // when + Page myFollowingUsersPage = userRepository.getUsersOfFollowingMePage(4L, pageRequest); + // then + Assertions.assertThat(myFollowingUsersPage.getContent().size()).isEqualTo(2); + } + +} \ No newline at end of file diff --git a/server/src/test/java/com/bside/bside_311/service/UserServiceTest.java b/server/src/test/java/com/bside/bside_311/service/UserServiceTest.java new file mode 100644 index 0000000..5097c8a --- /dev/null +++ b/server/src/test/java/com/bside/bside_311/service/UserServiceTest.java @@ -0,0 +1,61 @@ +package com.bside.bside_311.service; + +import com.bside.bside_311.dto.UserResponseDto; +import com.bside.bside_311.entity.User; +import com.bside.bside_311.repository.UserRepository; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; + +@SpringBootTest +@Transactional +class UserServiceTest { + @Autowired + private UserService userService; + + @MockBean + private UserRepository userRepository; + + @Test + void getMyFollowingUsers() { + // given + int limit = 3; + + Sort sort = Sort.by(Sort.Direction.DESC, "username"); + PageRequest pageRequest = PageRequest.of(0, limit, sort); + Page pagedResponse = new PageImpl<>(List.of(User.of(1L))); + given(userRepository.getMyFollowingUsersPage(eq(1L), any())).willReturn(pagedResponse); + // when + Page myFollowingUsers = userService.getMyFollowingUsers(1L, pageRequest); + // then + Assertions.assertThat(myFollowingUsers.getContent().size()).isEqualTo(1); + } + + @Test + void getUsersOfFollowingMePage() { + // given + int limit = 3; + + Sort sort = Sort.by(Sort.Direction.DESC, "username"); + PageRequest pageRequest = PageRequest.of(0, limit, sort); + Page pagedResponse = new PageImpl<>(List.of(User.of(1L))); + given(userRepository.getUsersOfFollowingMePage(eq(1L), any())).willReturn(pagedResponse); + // when + Page myFollowingUsers = userService.getUsersOfFollowingMe(1L, pageRequest); + // then + Assertions.assertThat(myFollowingUsers.getContent().size()).isEqualTo(1); + } +} \ No newline at end of file diff --git a/server/src/test/java/com/bside/bside_311/utils/UserUtils.java b/server/src/test/java/com/bside/bside_311/utils/UserUtils.java new file mode 100644 index 0000000..0f80117 --- /dev/null +++ b/server/src/test/java/com/bside/bside_311/utils/UserUtils.java @@ -0,0 +1,20 @@ +package com.bside.bside_311.utils; + +import com.bside.bside_311.dto.UserSignupRequestDto; + +public class UserUtils { + public static UserSignupRequestDto makeSimpleUser() { + String email = "newbie@example.com"; + String password = "password"; + String id = "newbie"; + String name = "Newbie"; + return UserSignupRequestDto.builder() + .email(email) + .password(password) + .id(id) + .nickname(name) + .build(); + } + + +} diff --git a/server/src/test/resources/application-test.yml b/server/src/test/resources/application-test.yml index 47c9334..2328190 100644 --- a/server/src/test/resources/application-test.yml +++ b/server/src/test/resources/application-test.yml @@ -1,21 +1,31 @@ spring: datasource: - url: jdbc:h2:tcp://localhost/~/bside_311 - username: sa - password: - driver-class-name: org.h2.Driver + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://localhost:3306/bside_test?serverTimezone=UTC&characterEncoding=UTF-8 + username: root + password: root + minimum-idle: 10 + maximum-pool-size: 10 + idle-timeout: 30000 + connection-timeout: 10000 + validation-timeout: 10000 + max-lifetime: 30000 + connection-test-query: SELECT 1 jpa: + database: mysql + database-platform: org.hibernate.dialect.MySQLDialect hibernate: - ddl-auto: create + ddl-auto: 'create' properties: hibernate: # show_sql: true format_sql: true use_sql_comments: true + default_batch_fetch_size: 1000 -logging.level: - org.hibernate.SQL: debug +#logging.level: +# org.hibernate.SQL: debug # org.hibernate.orm.jdbc.bind: trace p6spy: diff --git a/server/src/test/resources/application-testbackup.yml b/server/src/test/resources/application-testbackup.yml new file mode 100644 index 0000000..47c9334 --- /dev/null +++ b/server/src/test/resources/application-testbackup.yml @@ -0,0 +1,27 @@ +spring: + datasource: + url: jdbc:h2:tcp://localhost/~/bside_311 + username: sa + password: + driver-class-name: org.h2.Driver + + jpa: + hibernate: + ddl-auto: create + properties: + hibernate: + # show_sql: true + format_sql: true + use_sql_comments: true + +logging.level: + org.hibernate.SQL: debug +# org.hibernate.orm.jdbc.bind: trace + +p6spy: + detail: false + +decorator: + datasource: + p6spy: + enable-logging: true \ No newline at end of file diff --git a/server/src/test/resources/application.yml b/server/src/test/resources/application.yml new file mode 100644 index 0000000..e9aeefc --- /dev/null +++ b/server/src/test/resources/application.yml @@ -0,0 +1,35 @@ +spring: + profiles: + active: test + +mybatis: + type-aliases-package: com.bside.bside_311.entity;com.bside.bside_311.dto;java.lang; + mapper-locations: mybatis/mapper/**/*.xml + configuration: + map-underscore-to-camel-case: true + +p6spy: + detail: false + +decorator: + datasource: + p6spy: + enable-logging: true + +springdoc: + version: '0.0.1' + api-docs: + groups: + enabled: true + default-consumes-media-type: application/json + default-produces-media-type: application/json + swagger-ui: + path: / + disable-swagger-default-url: true + display-request-duration: true + operations-sorter: method + tags-sorter: alpha + displayRequestDuration: true + # doc-expansion: none + groups-order: DESC + persistAuthorization: true \ No newline at end of file