├── .github ├── ISSUE_TEMPLATE │ └── selab-issue-template.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── gradle.yml ├── .gitignore ├── .gitmodules ├── Dockerfile ├── LICENSE ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── script └── sql │ ├── DDL.sql │ └── DML.sql ├── settings.gradle └── src ├── main ├── java │ └── kr │ │ └── ac │ │ └── hs │ │ └── selab │ │ ├── SelabApplication.java │ │ ├── auth │ │ ├── application │ │ │ └── UserService.java │ │ ├── converter │ │ │ └── AuthConverter.java │ │ ├── dto │ │ │ ├── request │ │ │ │ └── AuthLoginRequest.java │ │ │ └── response │ │ │ │ └── AuthLoginResponse.java │ │ ├── jwt │ │ │ ├── JwtAccessDeniedHandler.java │ │ │ ├── JwtAuthenticationEntryPoint.java │ │ │ ├── JwtFilter.java │ │ │ ├── JwtSecurityConfig.java │ │ │ └── JwtTokenProvider.java │ │ └── presentation │ │ │ ├── AuthController.java │ │ │ └── AuthSdk.java │ │ ├── board │ │ ├── application │ │ │ ├── BoardService.java │ │ │ ├── CommentLikeService.java │ │ │ ├── CommentService.java │ │ │ ├── PostLikeService.java │ │ │ └── PostService.java │ │ ├── converter │ │ │ ├── BoardConverter.java │ │ │ ├── CommentConverter.java │ │ │ ├── CommentLikeConverter.java │ │ │ ├── PostConverter.java │ │ │ └── PostLikeConverter.java │ │ ├── domain │ │ │ ├── Board.java │ │ │ ├── Comment.java │ │ │ ├── CommentLike.java │ │ │ ├── Post.java │ │ │ ├── PostLike.java │ │ │ └── event │ │ │ │ ├── BoardEvent.java │ │ │ │ └── PostEvent.java │ │ ├── dto │ │ │ ├── BoardCreateDto.java │ │ │ ├── BoardUpdateDto.java │ │ │ ├── CommentCreateDto.java │ │ │ ├── CommentFindByPostIdAndPageDto.java │ │ │ ├── CommentLikeDto.java │ │ │ ├── CommentLikeFindDto.java │ │ │ ├── CommentUpdateDto.java │ │ │ ├── PostCreateDto.java │ │ │ ├── PostFindByBoardAndPageDto.java │ │ │ ├── PostLikeDto.java │ │ │ ├── PostLikeFindDto.java │ │ │ ├── PostUpdateDto.java │ │ │ ├── request │ │ │ │ ├── BoardRequest.java │ │ │ │ ├── CommentRequest.java │ │ │ │ └── PostRequest.java │ │ │ └── response │ │ │ │ ├── BoardFindAllResponse.java │ │ │ │ ├── BoardFindResponse.java │ │ │ │ ├── BoardResponse.java │ │ │ │ ├── CommentFindByPostIdAndPageResponse.java │ │ │ │ ├── CommentFindResponse.java │ │ │ │ ├── CommentLikeFindResponse.java │ │ │ │ ├── CommentLikeResponse.java │ │ │ │ ├── CommentResponse.java │ │ │ │ ├── PostFindByBoardIdAndPageResponse.java │ │ │ │ ├── PostFindResponse.java │ │ │ │ ├── PostLikeFindResponse.java │ │ │ │ ├── PostLikeResponse.java │ │ │ │ └── PostResponse.java │ │ ├── facade │ │ │ ├── BoardEventListener.java │ │ │ ├── BoardFacade.java │ │ │ ├── CommentFacade.java │ │ │ ├── CommentLikeFacade.java │ │ │ ├── PostEventListener.java │ │ │ ├── PostFacade.java │ │ │ └── PostLikeFacade.java │ │ ├── infrastructure │ │ │ ├── BoardRepository.java │ │ │ ├── CommentLikeRepository.java │ │ │ ├── CommentRepository.java │ │ │ ├── PostLikeRepository.java │ │ │ └── PostRepository.java │ │ └── presentation │ │ │ ├── BoardController.java │ │ │ ├── BoardSdk.java │ │ │ ├── CommentController.java │ │ │ ├── CommentLikeController.java │ │ │ ├── CommentLikeSdk.java │ │ │ ├── CommentSdk.java │ │ │ ├── PostController.java │ │ │ ├── PostLikeController.java │ │ │ ├── PostLikeSdk.java │ │ │ └── PostSdk.java │ │ ├── common │ │ ├── config │ │ │ ├── AsyncConfig.java │ │ │ ├── CorsConfig.java │ │ │ ├── JpaConfig.java │ │ │ ├── SecurityConfig.java │ │ │ └── SwaggerConfig.java │ │ ├── domain │ │ │ └── BaseEntity.java │ │ ├── presentation │ │ │ ├── HealthController.java │ │ │ └── SwaggerController.java │ │ ├── properties │ │ │ ├── CorsProperties.java │ │ │ └── JwtProperties.java │ │ ├── template │ │ │ ├── PageResponseTemplate.java │ │ │ ├── ResponseMessage.java │ │ │ ├── ResponseTemplate.java │ │ │ ├── ResponseWrapper.java │ │ │ └── SwaggerNote.java │ │ └── utils │ │ │ ├── Constants.java │ │ │ └── SecurityUtils.java │ │ ├── core_qa │ │ ├── application │ │ │ └── CoreQaService.java │ │ ├── converter │ │ │ └── CoreQaConverter.java │ │ ├── domain │ │ │ └── CoreQa.java │ │ ├── dto │ │ │ ├── bundle │ │ │ │ ├── CoreQaCreateBundle.java │ │ │ │ └── CoreQaFindByPageBundle.java │ │ │ ├── request │ │ │ │ └── CoreQaCreateRequest.java │ │ │ └── response │ │ │ │ ├── CoreQaFindByIdResponse.java │ │ │ │ ├── CoreQaFindByPageResponse.java │ │ │ │ └── CoreQaResponse.java │ │ ├── facade │ │ │ └── CoreQaFacade.java │ │ ├── infrastructure │ │ │ └── CoreQaRepository.java │ │ └── presentation │ │ │ ├── CoreQaController.java │ │ │ └── CoreQaSdk.java │ │ ├── error │ │ ├── exception │ │ │ ├── BusinessException.java │ │ │ └── common │ │ │ │ ├── DuplicationException.java │ │ │ │ ├── InvalidArgumentException.java │ │ │ │ └── NonExitsException.java │ │ ├── handler │ │ │ └── GlobalExceptionHandler.java │ │ └── template │ │ │ ├── ErrorMessage.java │ │ │ └── ErrorTemplate.java │ │ ├── free_post │ │ ├── application │ │ │ ├── FreePostCommentService.java │ │ │ └── FreePostService.java │ │ ├── converter │ │ │ ├── FreePostCommentConverter.java │ │ │ └── FreePostConverter.java │ │ ├── domain │ │ │ ├── FreePost.java │ │ │ └── FreePostComment.java │ │ ├── dto │ │ │ ├── FreePostCommentCreateDto.java │ │ │ ├── FreePostCommentFindByFreePostIdAndPageDto.java │ │ │ ├── FreePostCommentUpdateDto.java │ │ │ ├── FreePostCreateDto.java │ │ │ ├── FreePostFindByPageDto.java │ │ │ ├── FreePostUpdateDto.java │ │ │ ├── request │ │ │ │ ├── FreePostCommentRequest.java │ │ │ │ └── FreePostRequest.java │ │ │ └── response │ │ │ │ ├── FreePostCommentFindByFreePostIdAndPageResponse.java │ │ │ │ ├── FreePostCommentFindResponse.java │ │ │ │ ├── FreePostCommentResponse.java │ │ │ │ ├── FreePostFindByIdResponse.java │ │ │ │ ├── FreePostFindByPageResponse.java │ │ │ │ └── FreePostResponse.java │ │ ├── facade │ │ │ ├── FreePostCommentFacade.java │ │ │ └── FreePostFacade.java │ │ ├── infrastructure │ │ │ ├── FreePostCommentRepository.java │ │ │ └── FreePostRepository.java │ │ └── presentation │ │ │ ├── FreePostCommentController.java │ │ │ ├── FreePostCommentSdk.java │ │ │ ├── FreePostController.java │ │ │ └── FreePostSdk.java │ │ ├── member │ │ ├── application │ │ │ └── MemberService.java │ │ ├── converter │ │ │ └── MemberConverter.java │ │ ├── domain │ │ │ ├── Member.java │ │ │ └── vo │ │ │ │ ├── Avatar.java │ │ │ │ ├── Password.java │ │ │ │ └── Role.java │ │ ├── dto │ │ │ ├── bundle │ │ │ │ └── MemberCreateBundle.java │ │ │ ├── request │ │ │ │ ├── MemberCreateRequest.java │ │ │ │ └── MemberExistRequest.java │ │ │ └── response │ │ │ │ ├── MemberCreateResponse.java │ │ │ │ └── MemberExistResponse.java │ │ ├── facade │ │ │ └── MemberFacade.java │ │ ├── infrastructure │ │ │ └── MemberRepository.java │ │ └── presentation │ │ │ ├── MemberController.java │ │ │ └── MemberSdk.java │ │ ├── notice │ │ ├── application │ │ │ ├── NoticeCommentService.java │ │ │ ├── NoticeLikeService.java │ │ │ └── NoticeService.java │ │ ├── converter │ │ │ ├── NoticeCommentConverter.java │ │ │ ├── NoticeConverter.java │ │ │ └── NoticeLikeConverter.java │ │ ├── domain │ │ │ ├── Notice.java │ │ │ ├── NoticeComment.java │ │ │ └── NoticeLike.java │ │ ├── dto │ │ │ ├── NoticeCommentCreateDto.java │ │ │ ├── NoticeCommentFindByNoticeIdAndPageDto.java │ │ │ ├── NoticeCommentUpdateDto.java │ │ │ ├── NoticeCreateDto.java │ │ │ ├── NoticeFindByPageDto.java │ │ │ ├── NoticeLikeDto.java │ │ │ ├── NoticeLikeFindDto.java │ │ │ ├── NoticeUpdateDto.java │ │ │ ├── request │ │ │ │ ├── NoticeCommentRequest.java │ │ │ │ └── NoticeRequest.java │ │ │ └── response │ │ │ │ ├── NoticeCommentFindByNoticeIdAndPageResponse.java │ │ │ │ ├── NoticeCommentFindResponse.java │ │ │ │ ├── NoticeCommentResponse.java │ │ │ │ ├── NoticeFindByIdResponse.java │ │ │ │ ├── NoticeFindByPageResponse.java │ │ │ │ ├── NoticeLikeFindResponse.java │ │ │ │ ├── NoticeLikeResponse.java │ │ │ │ └── NoticeResponse.java │ │ ├── facade │ │ │ ├── NoticeCommentFacade.java │ │ │ ├── NoticeFacade.java │ │ │ └── NoticeLikeFacade.java │ │ ├── infrastructure │ │ │ ├── NoticeCommentRepository.java │ │ │ ├── NoticeLikeRepository.java │ │ │ └── NoticeRepository.java │ │ └── presentation │ │ │ ├── NoticeCommentController.java │ │ │ ├── NoticeCommentSdk.java │ │ │ ├── NoticeController.java │ │ │ ├── NoticeLikeController.java │ │ │ ├── NoticeLikeSdk.java │ │ │ └── NoticeSdk.java │ │ └── terms │ │ ├── application │ │ └── TermsService.java │ │ ├── domain │ │ ├── Terms.java │ │ └── vo │ │ │ └── Category.java │ │ └── infrastructure │ │ └── TermsRepository.java └── resources │ ├── application-local.properties │ └── banner.txt └── test └── java └── kr └── ac └── hs └── selab ├── SelabApplicationTests.java ├── auth └── application │ └── UserServiceTest.java ├── board ├── application │ ├── BoardServiceTest.java │ ├── CommentLikeServiceTest.java │ ├── CommentServiceTest.java │ ├── PostLikeServiceTest.java │ └── PostServiceTest.java ├── domain │ ├── BoardTest.java │ ├── CommentTest.java │ └── PostTest.java ├── facade │ ├── BoardEventListenerTest.java │ ├── BoardFacadeTest.java │ ├── CommentFacadeTest.java │ ├── CommentLikeFacadeTest.java │ ├── PostEventListenerTest.java │ ├── PostFacadeTest.java │ └── PostLikeFacadeTest.java └── presentation │ ├── BoardControllerTest.java │ ├── CommentControllerTest.java │ ├── CommentLikeControllerTest.java │ ├── PostControllerTest.java │ └── PostLikeControllerTest.java ├── common └── presentation │ └── HealthControllerTest.java ├── free_post ├── application │ ├── FreePostCommentServiceTest.java │ └── FreePostServiceTest.java ├── domain │ ├── FreePostCommentTest.java │ └── FreePostTest.java ├── facade │ ├── FreePostCommentFacadeTest.java │ └── FreePostFacadeTest.java └── presentation │ ├── FreePostCommentControllerTest.java │ └── FreePostControllerTest.java ├── member ├── application │ └── MemberServiceTest.java ├── facade │ └── MemberFacadeTest.java └── presentation │ └── MemberControllerTest.java └── notice ├── application ├── NoticeCommentServiceTest.java ├── NoticeLikeServiceTest.java └── NoticeServiceTest.java ├── domain ├── NoticeCommentTest.java └── NoticeTest.java ├── facade ├── NoticeCommentFacadeTest.java ├── NoticeFacadeTest.java └── NoticeLikeFacadeTest.java └── presentation ├── NoticeCommentControllerTest.java ├── NoticeControllerTest.java └── NoticeLikeControllerTest.java /.github/ISSUE_TEMPLATE/selab-issue-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: selab issue template 3 | about: selab issue template 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 16 | 17 | 18 | ## 📑 작업 사항 19 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 22 | 23 | 24 | ## 💡 개요 25 | 26 | - resolved #0 27 | 28 | 29 | ## 📑 작업 사항 30 | 31 | 32 | 33 | 34 | ## ✒️ 코드 리뷰 요청 사항 35 | 36 | 37 | 38 | ## ✔️ 코드 리뷰 반영 사항 39 | 40 | -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: SE.LAB BLOG API SERVER 2 | 3 | on: 4 | push: 5 | branches: [ develop ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | # Actions Sequence 13 | - name: Start action 14 | uses: actions/checkout@v2 15 | 16 | # Set JDK Version 17 | - name: Set up jdk 17 18 | uses: actions/setup-java@v1 19 | with: 20 | java-version: 17 21 | 22 | # Modify Permission 23 | - name: Grant execute permission for gradlew 24 | run: chmod +x gradlew 25 | 26 | # Build Gradle 27 | - name: Build with gradle 28 | run: ./gradlew build -Dspring.profiles.active=dev 29 | 30 | # Login DockerHub 31 | - name: Login to dockerHub 32 | uses: docker/login-action@v1 33 | with: 34 | username: ${{ secrets.DOCKERHUB_USERNAME }} 35 | password: ${{ secrets.DOCKERHUB_PASSWORD }} 36 | 37 | # Build Docker Container 38 | - name: Docker image build and push 39 | uses: docker/build-push-action@v2 40 | with: 41 | context: . 42 | push: true 43 | tags: selabhs/selab-api:latest 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | .properties 39 | .gitmodules -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "selab-blog-api-secret-key"] 2 | path = src/main/resources/config 3 | url = https://github.com/selab-hs/selab-blog-api-secret-key 4 | branch = main -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11-jdk 2 | ARG JAR_FILE=build/libs/*.jar 3 | COPY ${JAR_FILE} app.jar 4 | ENTRYPOINT ["java", "-jar", "/app.jar"] 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SELAB REST API 2 | 3 | > 현재 간단한 PDF 파일로 SE.LAB 학부 연구실 공고 및 소개를 진행하고 있습니다. 랩원 모두가 쉽고 빠르게 공지와 정보를 4 | > 확인할 수 있는 웹 페이지가 필요하다고 생각했습니다. 이번 프로젝트에서는 SE.LAB 연구생들이 언제든지 이용할 수 5 | > 있도록 편리한 웹사이트를 구현하는 것이 목표입니다. 6 | 7 | ### 📘 기능 요구사항 8 | 9 | * 회원 10 | * 회원 가입을 할 수 있다. 11 | * 로그인할 수 있다. 12 | * 회원 정보를 수정할 수 있다. 13 | * 회원 탈퇴를 할 수 있다. 14 | * 회원 가입 여부를 조회할 수 있다. 15 | 16 | 17 | * 게시판 18 | * 게시판을 생성할 수 있다. 19 | * 게시판을 조회활 수 있다. 20 | * 게시판을 수정할 수 있다. 21 | * 게시판을 삭제할 수 있다. 22 | 23 | 24 | * 게시글 25 | * 게시글을 생성할 수 있다. 26 | * 게시글을 조회할 수 있다. 27 | * 게시글을 수정할 수 있다. 28 | * 게시글을 삭제할 수 있다. 29 | 30 | 31 | * 게시글 좋아요 32 | * 게시글에 관하여 좋아요할 수 있다. 33 | * 게시글에 관하여 좋아요를 조회할 수 있다. 34 | * 게시글에 관하여 좋아요 취소할 수 있다. 35 | 36 | 37 | * 댓글 38 | * 댓글을 생성할 수 있다. 39 | * 댓글을 조회할 수 있다. 40 | * 댓글을 수정할 수 있다. 41 | * 댓글을 삭제할 수 있다. 42 | 43 | 44 | * 댓글 좋아요 45 | * 댓글에 관하여 좋아요할 수 있다. 46 | * 댓글에 관하여 좋아요를 조회할 수 있다. 47 | * 댓글에 관하여 좋아요 취소할 수 있다. 48 | 49 | 50 | * 공지사항 51 | * 공지사항을 생성할 수 있다. 52 | * 공지사항을 조회할 수 있다. 53 | * 공지사항을 수정할 수 있다. 54 | * 공지사항을 삭제할 수 있다. 55 | 56 | 57 | * 공지사항 좋아요 58 | * 댓글에 관하여 좋아요할 수 있다. 59 | * 댓글에 관하여 좋아요를 조회할 수 있다. 60 | * 댓글에 관하여 좋아요 취소할 수 있다. 61 | 62 | 63 | * 공지사항 댓글 64 | * 공지사항에 댓글을 생성할 수 있다. 65 | * 공지사항에 댓글을 조회할 수 있다. 66 | * 공지사항에 댓글을 수정할 수 있다. 67 | * 공지사항에 댓글을 삭제할 수 있다. 68 | 69 | 70 | * 공지사항 댓글 좋아요 71 | * 공지사항 댓글에 관하여 좋아요할 수 있다. 72 | * 공지사항 댓글에 관하여 좋아요를 조회할 수 있다. 73 | * 공지사항 댓글에 관하여 좋아요 취소할 수 있다. 74 | 75 | 76 | * 자유게시글 77 | * 자유게시글을 생성할 수 있다. 78 | * 자유게시글을 조회할 수 있다. 79 | * 자유게시글을 수정할 수 있다. 80 | * 자유게시글을 삭제할 수 있다. 81 | 82 | 83 | * 자유게시글 좋아요 84 | * 자유게시글에 관하여 좋아요할 수 있다. 85 | * 자유게시글에 관하여 좋아요를 조회할 수 있다. 86 | * 자유게시글에 관하여 좋아요 취소할 수 있다. 87 | 88 | 89 | * 자유게시글 댓글 90 | * 자유게시글에 댓글을 생성할 수 있다. 91 | * 자유게시글에 댓글을 조회할 수 있다. 92 | * 자유게시글에 댓글을 수정할 수 있다. 93 | * 자유게시글에 댓글을 삭제할 수 있다. 94 | 95 | 96 | * 자유게시글 댓글 좋아요 97 | * 자유게시글 댓글에 관하여 좋아요할 수 있다. 98 | * 자유게시글 댓글에 관하여 좋아요를 조회할 수 있다. 99 | * 자유게시글 댓글에 관하여 좋아요 취소할 수 있다. 100 | 101 | ### 📗 비기능 요구사항 102 | 103 | * 내용 104 | 105 | ### 📌 Branch Rule 106 | 107 | * {issue label}/{domain}-{issue number} 108 | * Ex) feature/member-45 109 | 110 | ### 📎 Commit Rule 111 | 112 | * :Gitmoji" [{issue label}/{domain}-{issue number}] {commit message} 113 | * Ex) ✨ [feature/member-45] 회원 서비스 구현 114 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'org.springframework.boot' version '2.5.6' 3 | id 'io.spring.dependency-management' version '1.0.11.RELEASE' 4 | id 'java' 5 | id 'jacoco' 6 | } 7 | 8 | jacoco { 9 | toolVersion = "0.8.7" 10 | reportsDirectory = layout.buildDirectory.dir('customJacocoReportDir') 11 | } 12 | 13 | group = 'kr.ac.hs' 14 | version = 'v0.0.1' 15 | java.sourceCompatibility = JavaVersion.VERSION_17 16 | 17 | configurations { 18 | compileOnly { 19 | extendsFrom annotationProcessor 20 | } 21 | } 22 | 23 | repositories { 24 | mavenCentral() 25 | } 26 | 27 | dependencies { 28 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 29 | implementation 'org.springframework.boot:spring-boot-starter-web' 30 | implementation 'org.springframework.boot:spring-boot-starter-security' 31 | implementation 'org.springframework.boot:spring-boot-starter-validation' 32 | 33 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 34 | annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' 35 | developmentOnly 'org.springframework.boot:spring-boot-devtools' 36 | 37 | implementation 'io.jsonwebtoken:jjwt-api:0.11.2' 38 | implementation 'io.jsonwebtoken:jjwt-impl:0.11.2' 39 | implementation 'io.jsonwebtoken:jjwt-jackson:0.11.2' 40 | 41 | compileOnly 'org.projectlombok:lombok' 42 | annotationProcessor 'org.projectlombok:lombok' 43 | 44 | implementation 'io.springfox:springfox-boot-starter:3.0.0' 45 | runtimeOnly 'mysql:mysql-connector-java' 46 | 47 | testImplementation 'org.mockito:mockito-core:4.3.1' 48 | testImplementation 'com.navercorp.fixturemonkey:fixture-monkey-starter:0.3.1' 49 | } 50 | 51 | test { 52 | useJUnitPlatform() 53 | jacoco { 54 | destinationFile = layout.buildDirectory.file('jacoco/jacocoTest.exec').get().asFile 55 | classDumpDir = layout.buildDirectory.dir('jacoco/classpathdumps').get().asFile 56 | } 57 | finalizedBy 'jacocoTestReport' 58 | } 59 | 60 | jacocoTestReport { 61 | dependsOn test 62 | reports { 63 | xml.required = false 64 | csv.required = false 65 | html.outputLocation = layout.buildDirectory.dir('jacocoHtml') 66 | } 67 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/selab-hs/selab-core-api-java/334647260115b96f814c14cd296fce7484f5d36a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'selab' 2 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/SelabApplication.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.boot.context.event.ApplicationReadyEvent; 8 | import org.springframework.context.ApplicationListener; 9 | import org.springframework.core.env.Environment; 10 | 11 | import java.util.Arrays; 12 | 13 | @Slf4j 14 | @RequiredArgsConstructor 15 | @SpringBootApplication 16 | public class SelabApplication implements ApplicationListener { 17 | private final Environment environment; 18 | 19 | public static void main(String[] args) { 20 | SpringApplication.run(SelabApplication.class, args); 21 | } 22 | 23 | @Override 24 | public void onApplicationEvent(ApplicationReadyEvent event) { 25 | log.info("profiles = {}", Arrays.toString(environment.getActiveProfiles())); 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/auth/application/UserService.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.auth.application; 2 | 3 | import kr.ac.hs.selab.auth.converter.AuthConverter; 4 | import kr.ac.hs.selab.error.exception.common.NonExitsException; 5 | import kr.ac.hs.selab.error.template.ErrorMessage; 6 | import kr.ac.hs.selab.member.infrastructure.MemberRepository; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.security.core.userdetails.UserDetailsService; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | @Service 14 | @RequiredArgsConstructor 15 | public class UserService implements UserDetailsService { 16 | 17 | private final MemberRepository memberRepository; 18 | 19 | @Override 20 | @Transactional(readOnly = true) 21 | public UserDetails loadUserByUsername(final String email) { 22 | return memberRepository.findByEmail(email) 23 | .map(AuthConverter::toUser) 24 | .orElseThrow(() -> new NonExitsException(ErrorMessage.MEMBER_NOT_EXISTS_ERROR)); 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/auth/converter/AuthConverter.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.auth.converter; 2 | 3 | import kr.ac.hs.selab.auth.dto.response.AuthLoginResponse; 4 | import kr.ac.hs.selab.member.domain.Member; 5 | import org.springframework.security.core.Authentication; 6 | import org.springframework.security.core.userdetails.User; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | 9 | public class AuthConverter { 10 | 11 | public static AuthLoginResponse toAuthLoginResponse(Authentication authentication, String token) { 12 | return new AuthLoginResponse(authentication.getName(), token); 13 | } 14 | 15 | public static UserDetails toUser(Member member) { 16 | return User.builder() 17 | .username(member.getEmail()) 18 | .password(member.getPasswordValue()) 19 | .authorities(member.getAuthority()) 20 | .build(); 21 | } 22 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/auth/dto/request/AuthLoginRequest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.auth.dto.request; 2 | 3 | import lombok.Getter; 4 | 5 | import javax.validation.constraints.Email; 6 | import javax.validation.constraints.NotBlank; 7 | import javax.validation.constraints.NotNull; 8 | 9 | @Getter 10 | public class AuthLoginRequest { 11 | @NotNull 12 | @Email 13 | private String email; 14 | 15 | @NotBlank 16 | private String password; 17 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/auth/dto/response/AuthLoginResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.auth.dto.response; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @Getter 7 | @RequiredArgsConstructor 8 | public class AuthLoginResponse { 9 | 10 | private final String email; 11 | private final String token; 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/auth/jwt/JwtAccessDeniedHandler.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.auth.jwt; 2 | 3 | import org.springframework.security.access.AccessDeniedException; 4 | import org.springframework.security.web.access.AccessDeniedHandler; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | import java.io.IOException; 9 | 10 | public class JwtAccessDeniedHandler implements AccessDeniedHandler { 11 | 12 | @Override 13 | public void handle( 14 | HttpServletRequest request, 15 | HttpServletResponse response, 16 | AccessDeniedException accessDeniedException 17 | ) throws IOException { 18 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED); 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/auth/jwt/JwtAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.auth.jwt; 2 | 3 | import org.springframework.security.core.AuthenticationException; 4 | import org.springframework.security.web.AuthenticationEntryPoint; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | import java.io.IOException; 9 | 10 | public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { 11 | 12 | @Override 13 | public void commence( 14 | HttpServletRequest request, 15 | HttpServletResponse response, 16 | AuthenticationException authException 17 | ) throws IOException { 18 | response.sendError(HttpServletResponse.SC_FORBIDDEN); 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/auth/jwt/JwtSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.auth.jwt; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.security.config.annotation.SecurityConfigurerAdapter; 5 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 6 | import org.springframework.security.web.DefaultSecurityFilterChain; 7 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 8 | 9 | import javax.validation.constraints.NotNull; 10 | 11 | @RequiredArgsConstructor 12 | public class JwtSecurityConfig extends SecurityConfigurerAdapter { 13 | 14 | @NotNull 15 | private final JwtTokenProvider jwtTokenProvider; 16 | 17 | @Override 18 | public void configure(HttpSecurity http) { 19 | http.addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class); 20 | } 21 | 22 | private JwtFilter jwtFilter() { 23 | return new JwtFilter(jwtTokenProvider); 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/auth/presentation/AuthController.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.auth.presentation; 2 | 3 | import kr.ac.hs.selab.auth.converter.AuthConverter; 4 | import kr.ac.hs.selab.auth.dto.request.AuthLoginRequest; 5 | import kr.ac.hs.selab.auth.dto.response.AuthLoginResponse; 6 | import kr.ac.hs.selab.auth.jwt.JwtTokenProvider; 7 | import kr.ac.hs.selab.common.template.ResponseMessage; 8 | import kr.ac.hs.selab.common.template.ResponseTemplate; 9 | import lombok.RequiredArgsConstructor; 10 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 11 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 12 | import org.springframework.security.core.context.SecurityContextHolder; 13 | import org.springframework.validation.annotation.Validated; 14 | import org.springframework.web.bind.annotation.*; 15 | 16 | @RestController 17 | @RequestMapping("/api/v1") 18 | @RequiredArgsConstructor 19 | public class AuthController implements AuthSdk { 20 | 21 | private final JwtTokenProvider tokenProvider; 22 | private final AuthenticationManagerBuilder authenticationManagerBuilder; 23 | 24 | // Redis를 통해 token data 관리 진행 25 | @Override 26 | @PostMapping("/auth/login") 27 | public ResponseTemplate login(@Validated @RequestBody AuthLoginRequest request) { 28 | final var authenticationToken = new UsernamePasswordAuthenticationToken( 29 | request.getEmail(), 30 | request.getPassword() 31 | ); 32 | 33 | final var authentication = authenticationManagerBuilder.getObject() 34 | .authenticate(authenticationToken); 35 | SecurityContextHolder.getContext().setAuthentication(authentication); 36 | 37 | final var token = tokenProvider.createToken(authentication); 38 | 39 | final var response = AuthConverter.toAuthLoginResponse( 40 | authentication, token); 41 | 42 | return ResponseTemplate.ok(ResponseMessage.AUTH_LOGIN_SUCCESS, response); 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/auth/presentation/AuthSdk.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.auth.presentation; 2 | 3 | 4 | import io.swagger.annotations.Api; 5 | import io.swagger.annotations.ApiOperation; 6 | import io.swagger.annotations.ApiParam; 7 | import io.swagger.annotations.ApiResponse; 8 | import io.swagger.annotations.ApiResponses; 9 | import kr.ac.hs.selab.auth.dto.request.AuthLoginRequest; 10 | import kr.ac.hs.selab.auth.dto.response.AuthLoginResponse; 11 | import kr.ac.hs.selab.common.template.ResponseTemplate; 12 | import kr.ac.hs.selab.common.template.SwaggerNote; 13 | 14 | @Api(tags = "로그인 API", description = "Auth Controller (MVP)") 15 | public interface AuthSdk { 16 | @ApiOperation(value = "로그인", notes = SwaggerNote.AUTH_LOGIN) 17 | @ApiResponses({ 18 | @ApiResponse(code = 200, message = "로그인 성공"), 19 | @ApiResponse(code = 401, message = "로그인 실패") 20 | }) 21 | ResponseTemplate login(@ApiParam(value = "이메일과 비밀번호") AuthLoginRequest request); 22 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/application/BoardService.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.application; 2 | 3 | import kr.ac.hs.selab.board.converter.BoardConverter; 4 | import kr.ac.hs.selab.board.domain.Board; 5 | import kr.ac.hs.selab.board.dto.BoardCreateDto; 6 | import kr.ac.hs.selab.board.dto.BoardUpdateDto; 7 | import kr.ac.hs.selab.board.infrastructure.BoardRepository; 8 | import kr.ac.hs.selab.common.utils.Constants; 9 | import kr.ac.hs.selab.error.exception.common.NonExitsException; 10 | import kr.ac.hs.selab.error.template.ErrorMessage; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.transaction.annotation.Transactional; 14 | 15 | import java.util.List; 16 | 17 | @RequiredArgsConstructor 18 | @Transactional(readOnly = true) 19 | @Service 20 | public class BoardService { 21 | private final BoardRepository boardRepository; 22 | 23 | @Transactional 24 | public Board create(BoardCreateDto dto) { 25 | return boardRepository.save(BoardConverter.toBoard(dto)); 26 | } 27 | 28 | public Board findById(Long id) { 29 | return boardRepository.findByIdAndDeleteFlag(id, Constants.NOT_DELETED) 30 | .orElseThrow(() -> new NonExitsException(ErrorMessage.BOARD_NOT_EXISTS_ERROR)); 31 | } 32 | 33 | public List findAll() { 34 | return boardRepository.findByDeleteFlag(Constants.NOT_DELETED); 35 | } 36 | 37 | @Transactional 38 | public Board update(BoardUpdateDto dto) { 39 | return findById(dto.getId()).update(dto.getTitle(), dto.getDescription()); 40 | } 41 | 42 | @Transactional 43 | public Board delete(Long id) { 44 | return findById(id).delete(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/application/CommentLikeService.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.application; 2 | 3 | import kr.ac.hs.selab.board.domain.Comment; 4 | import kr.ac.hs.selab.board.domain.CommentLike; 5 | import kr.ac.hs.selab.board.infrastructure.CommentLikeRepository; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import java.util.List; 11 | 12 | @RequiredArgsConstructor 13 | @Transactional(readOnly = true) 14 | @Service 15 | public class CommentLikeService { 16 | private final CommentLikeRepository commentLikeRepository; 17 | 18 | @Transactional 19 | public CommentLike create(Long memberId, Long commentId) { 20 | return commentLikeRepository.save(new CommentLike(memberId, commentId)); 21 | } 22 | 23 | public List find(Long commentId) { 24 | return commentLikeRepository.findByCommentId(commentId); 25 | } 26 | 27 | @Transactional 28 | public void deleteById(Long id) { 29 | commentLikeRepository.deleteById(id); 30 | } 31 | 32 | @Transactional 33 | public void deleteByCommentId(Long commentId) { 34 | commentLikeRepository.deleteAll(commentLikeRepository.findByCommentId(commentId)); 35 | } 36 | 37 | @Transactional 38 | public void deleteByComments(List comments) { 39 | comments.forEach(comment -> deleteByCommentId(comment.getId())); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/application/PostLikeService.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.application; 2 | 3 | import kr.ac.hs.selab.board.domain.PostLike; 4 | import kr.ac.hs.selab.board.infrastructure.PostLikeRepository; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | import java.util.List; 10 | 11 | @RequiredArgsConstructor 12 | @Transactional(readOnly = true) 13 | @Service 14 | public class PostLikeService { 15 | private final PostLikeRepository postLikeRepository; 16 | 17 | @Transactional 18 | public PostLike create(Long memberId, Long postId) { 19 | return postLikeRepository.save(new PostLike(memberId, postId)); 20 | } 21 | 22 | public List find(Long postId) { 23 | return postLikeRepository.findByPostId(postId); 24 | } 25 | 26 | @Transactional 27 | public void delete(Long id) { 28 | postLikeRepository.deleteById(id); 29 | } 30 | 31 | @Transactional 32 | public void deleteByPostId(Long postId) { 33 | postLikeRepository.deleteAll(postLikeRepository.findByPostId(postId)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/application/PostService.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.application; 2 | 3 | import kr.ac.hs.selab.board.converter.PostConverter; 4 | import kr.ac.hs.selab.board.domain.Post; 5 | import kr.ac.hs.selab.board.dto.PostCreateDto; 6 | import kr.ac.hs.selab.board.dto.PostUpdateDto; 7 | import kr.ac.hs.selab.board.infrastructure.PostRepository; 8 | import kr.ac.hs.selab.common.utils.Constants; 9 | import kr.ac.hs.selab.error.exception.common.NonExitsException; 10 | import kr.ac.hs.selab.error.template.ErrorMessage; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.data.domain.Page; 13 | import org.springframework.data.domain.Pageable; 14 | import org.springframework.stereotype.Service; 15 | import org.springframework.transaction.annotation.Transactional; 16 | 17 | import java.util.List; 18 | 19 | @RequiredArgsConstructor 20 | @Transactional(readOnly = true) 21 | @Service 22 | public class PostService { 23 | private final PostRepository postRepository; 24 | 25 | @Transactional 26 | public Post create(PostCreateDto dto, Long memberId, Long boardId) { 27 | return postRepository.save(PostConverter.toPost(dto, memberId, boardId)); 28 | } 29 | 30 | public Long count(Long boardId) { 31 | return postRepository.countByBoardIdAndDeleteFlag(boardId, Constants.NOT_DELETED); 32 | } 33 | 34 | public Post findPostById(Long id) { 35 | return postRepository.findByIdAndDeleteFlag(id, Constants.NOT_DELETED) 36 | .orElseThrow(() -> new NonExitsException(ErrorMessage.POST_NOT_EXISTS_ERROR)); 37 | } 38 | 39 | public List findPostsByBoardId(Long boardId) { 40 | return postRepository.findByBoardIdAndDeleteFlag(boardId, Constants.NOT_DELETED); 41 | } 42 | 43 | public Page findPostsByBoardIdAndPage(Long boardId, Pageable pageable) { 44 | return postRepository.findByBoardIdAndDeleteFlag(boardId, Constants.NOT_DELETED, pageable); 45 | } 46 | 47 | @Transactional 48 | public Post update(PostUpdateDto dto) { 49 | return findPostById(dto.getId()).update(dto.getTitle(), dto.getContent()); 50 | } 51 | 52 | @Transactional 53 | public Post deleteById(Long id) { 54 | return findPostById(id).delete(); 55 | } 56 | 57 | @Transactional 58 | public void deleteByBoardId(Long boardId) { 59 | postRepository.findByBoardIdAndDeleteFlag(boardId, Constants.NOT_DELETED) 60 | .forEach(Post::delete); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/converter/BoardConverter.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.converter; 2 | 3 | import kr.ac.hs.selab.board.domain.Board; 4 | import kr.ac.hs.selab.board.dto.BoardCreateDto; 5 | import kr.ac.hs.selab.board.dto.BoardUpdateDto; 6 | import kr.ac.hs.selab.board.dto.request.BoardRequest; 7 | import kr.ac.hs.selab.board.dto.response.BoardFindAllResponse; 8 | import kr.ac.hs.selab.board.dto.response.BoardFindResponse; 9 | import lombok.experimental.UtilityClass; 10 | 11 | import java.util.List; 12 | import java.util.stream.Collectors; 13 | 14 | @UtilityClass 15 | public class BoardConverter { 16 | public Board toBoard(BoardCreateDto dto) { 17 | return Board.builder() 18 | .title(dto.getTitle()) 19 | .description(dto.getDescription()) 20 | .build(); 21 | } 22 | 23 | public BoardFindResponse toBoardFindResponse(Board board) { 24 | return BoardFindResponse.builder() 25 | .title(board.getTitle()) 26 | .description(board.getDescription()) 27 | .createdAt(board.getCreatedAt()) 28 | .modifiedAt(board.getModifiedAt()) 29 | .build(); 30 | } 31 | 32 | public BoardFindAllResponse toBoardFindAllResponse(List boards) { 33 | List boardResponses = boards.stream() 34 | .map(BoardConverter::toBoardFindResponse) 35 | .collect(Collectors.toList()); 36 | return new BoardFindAllResponse((long) boards.size(), boardResponses); 37 | } 38 | 39 | public BoardUpdateDto toBoardUpdateDto(Long id, BoardRequest request) { 40 | return BoardUpdateDto.builder() 41 | .id(id) 42 | .title(request.getTitle()) 43 | .description(request.getDescription()) 44 | .build(); 45 | } 46 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/converter/CommentLikeConverter.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.converter; 2 | 3 | import kr.ac.hs.selab.board.domain.CommentLike; 4 | import kr.ac.hs.selab.board.dto.response.CommentLikeFindResponse; 5 | import lombok.experimental.UtilityClass; 6 | 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | @UtilityClass 11 | public class CommentLikeConverter { 12 | public CommentLikeFindResponse toCommentLikeFindResponse(Long commentId, List likes) { 13 | return CommentLikeFindResponse.builder() 14 | .commentId(commentId) 15 | .totalCount((long) likes.size()) 16 | .commentLikes( 17 | likes.stream() 18 | .map(CommentLikeConverter::toCommentLikeInnerResponse) 19 | .collect(Collectors.toList()) 20 | ) 21 | .build(); 22 | } 23 | 24 | private CommentLikeFindResponse.CommentLikeInnerResponse toCommentLikeInnerResponse(CommentLike like) { 25 | return new CommentLikeFindResponse.CommentLikeInnerResponse(like.getId(), like.getMemberId()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/converter/PostLikeConverter.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.converter; 2 | 3 | import kr.ac.hs.selab.board.domain.PostLike; 4 | import kr.ac.hs.selab.board.dto.response.PostLikeFindResponse; 5 | import lombok.experimental.UtilityClass; 6 | 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | @UtilityClass 11 | public class PostLikeConverter { 12 | public PostLikeFindResponse toPostLikeFindResponse(Long postId, List likes) { 13 | return PostLikeFindResponse.builder() 14 | .postId(postId) 15 | .totalCount((long) likes.size()) 16 | .postLikes( 17 | likes.stream() 18 | .map(PostLikeConverter::toPostLikeInnerResponse) 19 | .collect(Collectors.toList()) 20 | ) 21 | .build(); 22 | } 23 | 24 | private PostLikeFindResponse.PostLikeInnerResponse toPostLikeInnerResponse(PostLike like) { 25 | return new PostLikeFindResponse.PostLikeInnerResponse(like.getId(), like.getMemberId()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/domain/Board.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.domain; 2 | 3 | import kr.ac.hs.selab.common.domain.BaseEntity; 4 | import lombok.AccessLevel; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.persistence.Column; 10 | import javax.persistence.Entity; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.GenerationType; 13 | import javax.persistence.Id; 14 | import javax.persistence.Transient; 15 | 16 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 17 | @Getter 18 | @Entity 19 | public class Board extends BaseEntity { 20 | @Id 21 | @GeneratedValue(strategy = GenerationType.IDENTITY) 22 | @Column(name = "board_id", nullable = false) 23 | private Long id; 24 | 25 | @Column(name = "board_title", unique = true, nullable = false) 26 | private String title; 27 | 28 | @Column(name = "board_description", nullable = false) 29 | private String description; 30 | 31 | @Column(name = "board_delete_flag", nullable = false) 32 | private boolean deleteFlag; 33 | 34 | @Transient 35 | private static final String HYPHEN = "-"; 36 | 37 | @Builder 38 | private Board(String title, String description) { 39 | this.title = title; 40 | this.description = description; 41 | this.deleteFlag = false; 42 | } 43 | 44 | public Board update(String title, String description) { 45 | this.title = title; 46 | this.description = description; 47 | return this; 48 | } 49 | 50 | public Board delete() { 51 | this.title = this.title + HYPHEN + this.id; 52 | this.deleteFlag = true; 53 | return this; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/domain/Comment.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.domain; 2 | 3 | import kr.ac.hs.selab.common.domain.BaseEntity; 4 | import lombok.AccessLevel; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.persistence.Column; 10 | import javax.persistence.Entity; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.GenerationType; 13 | import javax.persistence.Id; 14 | 15 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 16 | @Getter 17 | @Entity 18 | public class Comment extends BaseEntity { 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | @Column(name = "comment_id", nullable = false) 22 | private Long id; 23 | 24 | @Column(name = "member_id", nullable = false) 25 | private Long memberId; 26 | 27 | @Column(name = "post_id", nullable = false) 28 | private Long postId; 29 | 30 | @Column(name = "comment_content", nullable = false) 31 | private String content; 32 | 33 | @Column(name = "comment_delete_flag", nullable = false) 34 | private boolean deleteFlag; 35 | 36 | @Builder 37 | public Comment(Long memberId, Long postId, String content) { 38 | this.memberId = memberId; 39 | this.postId = postId; 40 | this.content = content; 41 | this.deleteFlag = false; 42 | } 43 | 44 | public Comment update(String content) { 45 | this.content = content; 46 | return this; 47 | } 48 | 49 | public Comment delete() { 50 | this.deleteFlag = true; 51 | return this; 52 | } 53 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/domain/CommentLike.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.domain; 2 | 3 | import kr.ac.hs.selab.common.domain.BaseEntity; 4 | import lombok.AccessLevel; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.GenerationType; 12 | import javax.persistence.Id; 13 | import javax.persistence.Table; 14 | import javax.persistence.UniqueConstraint; 15 | 16 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 17 | @Getter 18 | @Entity 19 | @Table( 20 | name = "comment_like", 21 | uniqueConstraints = { 22 | @UniqueConstraint( 23 | columnNames = {"member_id", "comment_id"} 24 | ) 25 | } 26 | ) 27 | public class CommentLike extends BaseEntity { 28 | @Id 29 | @GeneratedValue(strategy = GenerationType.IDENTITY) 30 | @Column(name = "comment_like_id", nullable = false) 31 | private Long id; 32 | 33 | @Column(name = "member_id", nullable = false) 34 | private Long memberId; 35 | 36 | @Column(name = "comment_id", nullable = false) 37 | private Long commentId; 38 | 39 | public CommentLike(Long memberId, Long commentId) { 40 | this.memberId = memberId; 41 | this.commentId = commentId; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/domain/Post.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.domain; 2 | 3 | import kr.ac.hs.selab.common.domain.BaseEntity; 4 | import lombok.AccessLevel; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.persistence.Column; 10 | import javax.persistence.Entity; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.GenerationType; 13 | import javax.persistence.Id; 14 | 15 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 16 | @Getter 17 | @Entity 18 | public class Post extends BaseEntity { 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | @Column(name = "post_id", nullable = false) 22 | private Long id; 23 | 24 | @Column(name = "member_id", nullable = false) 25 | private Long memberId; 26 | 27 | @Column(name = "board_id", nullable = false) 28 | private Long boardId; 29 | 30 | @Column(name = "post_title", nullable = false) 31 | private String title; 32 | 33 | @Column(name = "post_content", nullable = false) 34 | private String content; 35 | 36 | @Column(name = "post_delete_flag", nullable = false) 37 | private boolean deleteFlag; 38 | 39 | @Builder 40 | private Post(Long memberId, Long boardId, String title, String content) { 41 | this.memberId = memberId; 42 | this.boardId = boardId; 43 | this.title = title; 44 | this.content = content; 45 | this.deleteFlag = false; 46 | } 47 | 48 | public Post update(String title, String content) { 49 | this.title = title; 50 | this.content = content; 51 | return this; 52 | } 53 | 54 | public Post delete() { 55 | this.deleteFlag = true; 56 | return this; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/domain/PostLike.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.domain; 2 | 3 | import kr.ac.hs.selab.common.domain.BaseEntity; 4 | import lombok.AccessLevel; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.GenerationType; 12 | import javax.persistence.Id; 13 | import javax.persistence.Table; 14 | import javax.persistence.UniqueConstraint; 15 | 16 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 17 | @Getter 18 | @Entity 19 | @Table( 20 | name = "post_like", 21 | uniqueConstraints = { 22 | @UniqueConstraint( 23 | columnNames = {"member_id", "post_id"} 24 | ) 25 | } 26 | ) 27 | public class PostLike extends BaseEntity { 28 | @Id 29 | @GeneratedValue(strategy = GenerationType.IDENTITY) 30 | @Column(name = "post_like_id", nullable = false) 31 | private Long id; 32 | 33 | @Column(name = "member_id", nullable = false) 34 | private Long memberId; 35 | 36 | @Column(name = "post_id", nullable = false) 37 | private Long postId; 38 | 39 | public PostLike(Long memberId, Long postId) { 40 | this.memberId = memberId; 41 | this.postId = postId; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/domain/event/BoardEvent.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.domain.event; 2 | 3 | import kr.ac.hs.selab.board.domain.Board; 4 | import lombok.Value; 5 | 6 | @Value(staticConstructor = "of") 7 | public class BoardEvent { 8 | Board board; 9 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/domain/event/PostEvent.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.domain.event; 2 | 3 | import kr.ac.hs.selab.board.domain.Post; 4 | import lombok.Value; 5 | 6 | @Value(staticConstructor = "of") 7 | public class PostEvent { 8 | Post post; 9 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/BoardCreateDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | @Getter 8 | public class BoardCreateDto { 9 | private final String title; 10 | private final String description; 11 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/BoardUpdateDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | @Builder 7 | @Getter 8 | public class BoardUpdateDto { 9 | private final Long id; 10 | private final String title; 11 | private final String description; 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/CommentCreateDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | @Builder 7 | @Getter 8 | public class CommentCreateDto { 9 | private final String memberEmail; 10 | private final Long postId; 11 | private final String content; 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/CommentFindByPostIdAndPageDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.data.domain.Pageable; 6 | 7 | @RequiredArgsConstructor 8 | @Getter 9 | public class CommentFindByPostIdAndPageDto { 10 | private final Long postId; 11 | private final Pageable pageable; 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/CommentLikeDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | @Getter 8 | public class CommentLikeDto { 9 | private final String memberEmail; 10 | private final Long commentId; 11 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/CommentLikeFindDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | @Getter 8 | public class CommentLikeFindDto { 9 | private final Long commentId; 10 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/CommentUpdateDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | @Getter 8 | public class CommentUpdateDto { 9 | private final Long id; 10 | private final String content; 11 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/PostCreateDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | @Builder 7 | @Getter 8 | public class PostCreateDto { 9 | private final String memberEmail; 10 | private final Long boardId; 11 | private final String title; 12 | private final String content; 13 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/PostFindByBoardAndPageDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.data.domain.Pageable; 6 | 7 | @RequiredArgsConstructor 8 | @Getter 9 | public class PostFindByBoardAndPageDto { 10 | private final Long boardId; 11 | private final Pageable pageable; 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/PostLikeDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | @Getter 8 | public class PostLikeDto { 9 | private final String memberEmail; 10 | private final Long postId; 11 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/PostLikeFindDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | @Getter 8 | public class PostLikeFindDto { 9 | private final Long postId; 10 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/PostUpdateDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | @Builder 7 | @Getter 8 | public class PostUpdateDto { 9 | private final Long id; 10 | private final String title; 11 | private final String content; 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/request/BoardRequest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto.request; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.validation.constraints.Pattern; 9 | import javax.validation.constraints.Size; 10 | 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Getter 14 | public class BoardRequest { 15 | @Schema(description = "게시판 제목") 16 | @Pattern(regexp = "^[가-힣]{2,10}$", message = "게시판 제목 형식이 맞지 않습니다.") 17 | private String title; 18 | 19 | @Schema(description = "게시판 설명") 20 | @Size(max = 30, message = "게시판 설명이 너무 깁니다.") 21 | private String description; 22 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/request/CommentRequest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto.request; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.validation.constraints.NotBlank; 9 | 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Getter 13 | public class CommentRequest { 14 | @Schema(description = "댓글 내용") 15 | @NotBlank(message = "댓글에 내용을 작성해야합니다.") 16 | private String content; 17 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/request/PostRequest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto.request; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.validation.constraints.NotBlank; 9 | import javax.validation.constraints.NotNull; 10 | 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Getter 14 | public class PostRequest { 15 | @Schema(description = "게시글 제목") 16 | @NotNull(message = "게시글 제목 형식이 맞지 않습니다.") 17 | private String title; 18 | 19 | @Schema(description = "게시글 내용") 20 | @NotBlank(message = "게시글에 내용을 작성해야합니다.") 21 | private String content; 22 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/response/BoardFindAllResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | import java.util.List; 8 | 9 | @RequiredArgsConstructor 10 | @Getter 11 | public class BoardFindAllResponse { 12 | @Schema(description = "게시판 전체 개수") 13 | private final Long totalCount; 14 | 15 | @Schema(description = "게시판 전체 리스트") 16 | private final List boards; 17 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/response/BoardFindResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | @Builder 10 | @Getter 11 | public class BoardFindResponse { 12 | @Schema(description = "게시판 제목") 13 | private final String title; 14 | 15 | @Schema(description = "게시판 설명") 16 | private final String description; 17 | 18 | @Schema(description = "게시판 생성 시간") 19 | private final LocalDateTime createdAt; 20 | 21 | @Schema(description = "게시판 수정 시간") 22 | private final LocalDateTime modifiedAt; 23 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/response/BoardResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | @RequiredArgsConstructor 8 | @Getter 9 | public class BoardResponse { 10 | @Schema(description = "게시판 ID") 11 | private final Long id; 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/response/CommentFindByPostIdAndPageResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.List; 9 | 10 | @Builder 11 | @Getter 12 | public class CommentFindByPostIdAndPageResponse { 13 | @Schema(description = "게시글 id") 14 | private final Long postId; 15 | 16 | @Schema(description = "댓글 전체 개수") 17 | private final Long totalCount; 18 | 19 | @Schema(description = "댓글 페이지") 20 | private final int pageNumber; 21 | 22 | @Schema(description = "한 페이지에 가져올 댓글 수") 23 | private final int pageSize; 24 | 25 | @Schema(description = "댓글 정렬 기준") 26 | private final String sort; 27 | 28 | @Schema(description = "댓글 전체 리스트") 29 | private final List comments; 30 | 31 | @Builder 32 | @Getter 33 | public static class CommentInnerResponse { 34 | @Schema(description = "작성자 id") 35 | private final Long memberId; 36 | 37 | @Schema(description = "댓글 id") 38 | private final Long commentId; 39 | 40 | @Schema(description = "댓글 내용") 41 | private final String content; 42 | 43 | @Schema(description = "댓글 생성 시간") 44 | private final LocalDateTime createdAt; 45 | 46 | @Schema(description = "댓글 수정 시간") 47 | private final LocalDateTime modifiedAt; 48 | } 49 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/response/CommentFindResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | @Builder 10 | @Getter 11 | public class CommentFindResponse { 12 | @Schema(description = "게시글 id") 13 | private final Long postId; 14 | 15 | @Schema(description = "작성자 id") 16 | private final Long memberId; 17 | 18 | @Schema(description = "댓글 id") 19 | private final Long commentId; 20 | 21 | @Schema(description = "댓글 내용") 22 | private final String content; 23 | 24 | @Schema(description = "댓글 생성 시간") 25 | private final LocalDateTime createdAt; 26 | 27 | @Schema(description = "댓글 수정 시간") 28 | private final LocalDateTime modifiedAt; 29 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/response/CommentLikeFindResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | @Builder 11 | @Getter 12 | public class CommentLikeFindResponse { 13 | @Schema(description = "댓글 id") 14 | private final Long commentId; 15 | 16 | @Schema(description = "댓글 좋아요 전체 개수") 17 | private final Long totalCount; 18 | 19 | @Schema(description = "댓글 좋아요 전체") 20 | private final List commentLikes; 21 | 22 | @RequiredArgsConstructor 23 | @Getter 24 | public static class CommentLikeInnerResponse { 25 | @Schema(description = "댓글 좋아요 id") 26 | private final Long id; 27 | 28 | @Schema(description = "댓글 좋아요한 회원 id") 29 | private final Long memberId; 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/response/CommentLikeResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto.response; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | @Getter 8 | public class CommentLikeResponse { 9 | private final Long id; 10 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/response/CommentResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto.response; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | @Getter 8 | public class CommentResponse { 9 | private final Long commentId; 10 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/response/PostFindByBoardIdAndPageResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.List; 9 | 10 | @Builder 11 | @Getter 12 | public class PostFindByBoardIdAndPageResponse { 13 | @Schema(description = "게시판 id") 14 | private final Long boardId; 15 | 16 | @Schema(description = "게시글 전체 개수") 17 | private final Long totalCount; 18 | 19 | @Schema(description = "게시글 페이지") 20 | private final int pageNumber; 21 | 22 | @Schema(description = "한 페이지에 가져올 게시글 수") 23 | private final int pageSize; 24 | 25 | @Schema(description = "게시글 정렬 기준") 26 | private final String sort; 27 | 28 | @Schema(description = "게시글 전체 리스트") 29 | private final List posts; 30 | 31 | @Builder 32 | @Getter 33 | public static class PostInnerResponse { 34 | @Schema(description = "작성자 이메일") 35 | private final Long memberId; 36 | 37 | @Schema(description = "게시글 id") 38 | private final Long postId; 39 | 40 | @Schema(description = "게시글 제목") 41 | private final String title; 42 | 43 | @Schema(description = "게시글 내용") 44 | private final String content; 45 | 46 | @Schema(description = "게시글 생성 시간") 47 | private final LocalDateTime createdAt; 48 | 49 | @Schema(description = "게시글 수정 시간") 50 | private final LocalDateTime modifiedAt; 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/response/PostFindResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | @Builder 10 | @Getter 11 | public class PostFindResponse { 12 | @Schema(description = "게시판 id") 13 | private final Long boardId; 14 | 15 | @Schema(description = "작성자 id") 16 | private final Long memberId; 17 | 18 | @Schema(description = "게시글 id") 19 | private final Long postId; 20 | 21 | @Schema(description = "게시글 제목") 22 | private final String title; 23 | 24 | @Schema(description = "게시글 내용") 25 | private final String content; 26 | 27 | @Schema(description = "게시글 생성 시간") 28 | private final LocalDateTime createdAt; 29 | 30 | @Schema(description = "게시글 수정 시간") 31 | private final LocalDateTime modifiedAt; 32 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/response/PostLikeFindResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | @Builder 11 | @Getter 12 | public class PostLikeFindResponse { 13 | @Schema(description = "게시글 id") 14 | private final Long postId; 15 | 16 | @Schema(description = "게시글 좋아요 전체 개수") 17 | private final Long totalCount; 18 | 19 | @Schema(description = "게시글 좋아요 전체") 20 | private final List postLikes; 21 | 22 | @RequiredArgsConstructor 23 | @Getter 24 | public static class PostLikeInnerResponse { 25 | @Schema(description = "게시글 좋아요 id") 26 | private final Long id; 27 | 28 | @Schema(description = "게시글 좋아요한 유저 id") 29 | private final Long memberId; 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/response/PostLikeResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto.response; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | @Getter 8 | public class PostLikeResponse { 9 | private final Long id; 10 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/dto/response/PostResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | @RequiredArgsConstructor 8 | @Getter 9 | public class PostResponse { 10 | @Schema(description = "게시글 id") 11 | private final Long postId; 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/facade/BoardEventListener.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.facade; 2 | 3 | import kr.ac.hs.selab.board.application.CommentLikeService; 4 | import kr.ac.hs.selab.board.application.CommentService; 5 | import kr.ac.hs.selab.board.application.PostLikeService; 6 | import kr.ac.hs.selab.board.application.PostService; 7 | import kr.ac.hs.selab.board.domain.Comment; 8 | import kr.ac.hs.selab.board.domain.Post; 9 | import kr.ac.hs.selab.board.domain.event.BoardEvent; 10 | import lombok.RequiredArgsConstructor; 11 | import org.springframework.scheduling.annotation.Async; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.transaction.event.TransactionalEventListener; 14 | 15 | import javax.transaction.Transactional; 16 | import java.util.List; 17 | 18 | @RequiredArgsConstructor 19 | @Component 20 | public class BoardEventListener { 21 | private final PostService postService; 22 | private final CommentService commentService; 23 | private final PostLikeService postLikeService; 24 | private final CommentLikeService commentLikeService; 25 | 26 | @Async 27 | @Transactional(value = Transactional.TxType.REQUIRES_NEW) 28 | @TransactionalEventListener 29 | public void deleteByBoard(BoardEvent boardEvent) { 30 | var board = boardEvent.getBoard(); 31 | List posts = postService.findPostsByBoardId(board.getId()); 32 | 33 | posts.forEach(post -> { 34 | List comments = commentService.findCommentsByPostId(post.getId()); 35 | commentLikeService.deleteByComments(comments); 36 | }); 37 | commentService.deleteByPosts(posts); 38 | 39 | posts.forEach(post -> postLikeService.deleteByPostId(post.getId())); 40 | postService.deleteByBoardId(board.getId()); 41 | } 42 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/facade/BoardFacade.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.facade; 2 | 3 | import kr.ac.hs.selab.board.application.BoardService; 4 | import kr.ac.hs.selab.board.converter.BoardConverter; 5 | import kr.ac.hs.selab.board.domain.event.BoardEvent; 6 | import kr.ac.hs.selab.board.dto.BoardCreateDto; 7 | import kr.ac.hs.selab.board.dto.BoardUpdateDto; 8 | import kr.ac.hs.selab.board.dto.response.BoardFindAllResponse; 9 | import kr.ac.hs.selab.board.dto.response.BoardFindResponse; 10 | import kr.ac.hs.selab.board.dto.response.BoardResponse; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.context.ApplicationEventPublisher; 13 | import org.springframework.stereotype.Component; 14 | import org.springframework.transaction.annotation.Transactional; 15 | 16 | @RequiredArgsConstructor 17 | @Component 18 | public class BoardFacade { 19 | private final ApplicationEventPublisher publisher; 20 | private final BoardService boardService; 21 | 22 | @Transactional 23 | public BoardResponse create(BoardCreateDto dto) { 24 | var board = boardService.create(dto); 25 | return new BoardResponse(board.getId()); 26 | } 27 | 28 | @Transactional 29 | public BoardFindResponse findBoardResponseById(Long id) { 30 | var board = boardService.findById(id); 31 | return BoardConverter.toBoardFindResponse(board); 32 | } 33 | 34 | public BoardFindAllResponse findBoardFindAllResponse() { 35 | var boards = boardService.findAll(); 36 | return BoardConverter.toBoardFindAllResponse(boards); 37 | } 38 | 39 | @Transactional 40 | public BoardResponse update(BoardUpdateDto dto) { 41 | var board = boardService.update(dto); 42 | return new BoardResponse(board.getId()); 43 | } 44 | 45 | @Transactional 46 | public BoardResponse delete(Long id) { 47 | var board = boardService.delete(id); 48 | publisher.publishEvent(BoardEvent.of(board)); 49 | 50 | return new BoardResponse(board.getId()); 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/facade/CommentLikeFacade.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.facade; 2 | 3 | import kr.ac.hs.selab.board.application.CommentLikeService; 4 | import kr.ac.hs.selab.board.application.CommentService; 5 | import kr.ac.hs.selab.board.converter.CommentLikeConverter; 6 | import kr.ac.hs.selab.board.dto.CommentLikeDto; 7 | import kr.ac.hs.selab.board.dto.CommentLikeFindDto; 8 | import kr.ac.hs.selab.board.dto.response.CommentLikeFindResponse; 9 | import kr.ac.hs.selab.board.dto.response.CommentLikeResponse; 10 | import kr.ac.hs.selab.member.application.MemberService; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.transaction.annotation.Transactional; 14 | 15 | @RequiredArgsConstructor 16 | @Transactional(readOnly = true) 17 | @Component 18 | public class CommentLikeFacade { 19 | private final MemberService memberService; 20 | private final CommentService commentService; 21 | private final CommentLikeService commentLikeService; 22 | 23 | @Transactional 24 | public CommentLikeResponse create(CommentLikeDto dto) { 25 | var member = memberService.findByEmail(dto.getMemberEmail()); 26 | var comment = commentService.findCommentById(dto.getCommentId()); 27 | 28 | var like = commentLikeService.create(member.getId(), comment.getId()); 29 | return new CommentLikeResponse(like.getId()); 30 | } 31 | 32 | public CommentLikeFindResponse find(CommentLikeFindDto dto) { 33 | var comment = commentService.findCommentById(dto.getCommentId()); 34 | var likes = commentLikeService.find(comment.getId()); 35 | 36 | return CommentLikeConverter.toCommentLikeFindResponse(comment.getId(), likes); 37 | } 38 | 39 | @Transactional 40 | public CommentLikeResponse delete(Long id) { 41 | commentLikeService.deleteById(id); 42 | return new CommentLikeResponse(id); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/facade/PostEventListener.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.facade; 2 | 3 | import kr.ac.hs.selab.board.application.CommentLikeService; 4 | import kr.ac.hs.selab.board.application.CommentService; 5 | import kr.ac.hs.selab.board.application.PostLikeService; 6 | import kr.ac.hs.selab.board.domain.event.PostEvent; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.scheduling.annotation.Async; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.transaction.event.TransactionalEventListener; 11 | 12 | import javax.transaction.Transactional; 13 | 14 | @RequiredArgsConstructor 15 | @Component 16 | public class PostEventListener { 17 | private final CommentService commentService; 18 | private final CommentLikeService commentLikeService; 19 | private final PostLikeService postLikeService; 20 | 21 | @Async 22 | @Transactional(value = Transactional.TxType.REQUIRES_NEW) 23 | @TransactionalEventListener 24 | public void deleteByPost(PostEvent postEvent) { 25 | var post = postEvent.getPost(); 26 | postLikeService.deleteByPostId(post.getId()); 27 | commentLikeService.deleteByComments( 28 | commentService.findCommentsByPostId(post.getId()) 29 | ); 30 | commentService.deleteByPost(post.getId()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/facade/PostLikeFacade.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.facade; 2 | 3 | import kr.ac.hs.selab.board.application.PostLikeService; 4 | import kr.ac.hs.selab.board.application.PostService; 5 | import kr.ac.hs.selab.board.converter.PostLikeConverter; 6 | import kr.ac.hs.selab.board.dto.PostLikeDto; 7 | import kr.ac.hs.selab.board.dto.PostLikeFindDto; 8 | import kr.ac.hs.selab.board.dto.response.PostLikeFindResponse; 9 | import kr.ac.hs.selab.board.dto.response.PostLikeResponse; 10 | import kr.ac.hs.selab.member.application.MemberService; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.transaction.annotation.Transactional; 14 | 15 | @RequiredArgsConstructor 16 | @Transactional(readOnly = true) 17 | @Component 18 | public class PostLikeFacade { 19 | private final MemberService memberService; 20 | private final PostService postService; 21 | private final PostLikeService postLikeService; 22 | 23 | @Transactional 24 | public PostLikeResponse create(PostLikeDto dto) { 25 | var member = memberService.findByEmail(dto.getMemberEmail()); 26 | var post = postService.findPostById(dto.getPostId()); 27 | 28 | var like = postLikeService.create(member.getId(), post.getId()); 29 | return new PostLikeResponse(like.getId()); 30 | } 31 | 32 | public PostLikeFindResponse find(PostLikeFindDto dto) { 33 | var post = postService.findPostById(dto.getPostId()); 34 | var likes = postLikeService.find(post.getId()); 35 | 36 | return PostLikeConverter.toPostLikeFindResponse(post.getId(), likes); 37 | } 38 | 39 | @Transactional 40 | public PostLikeResponse delete(Long id) { 41 | postLikeService.delete(id); 42 | return new PostLikeResponse(id); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/infrastructure/BoardRepository.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.infrastructure; 2 | 3 | import kr.ac.hs.selab.board.domain.Board; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | public interface BoardRepository extends JpaRepository { 10 | List findByDeleteFlag(boolean deleteFlag); 11 | 12 | Optional findByIdAndDeleteFlag(Long id, boolean deleteFlag); 13 | 14 | Long countByDeleteFlag(boolean deleteFlag); 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/infrastructure/CommentLikeRepository.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.infrastructure; 2 | 3 | import kr.ac.hs.selab.board.domain.CommentLike; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.List; 7 | 8 | public interface CommentLikeRepository extends JpaRepository { 9 | List findByCommentId(Long commentId); 10 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/infrastructure/CommentRepository.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.infrastructure; 2 | 3 | import kr.ac.hs.selab.board.domain.Comment; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | public interface CommentRepository extends JpaRepository { 12 | Optional findByIdAndDeleteFlag(Long id, boolean deleteFlag); 13 | 14 | List findByPostIdAndDeleteFlag(Long postId, boolean deleteFlag); 15 | 16 | Page findByPostIdAndDeleteFlag(Long postId, boolean deleteFlag, Pageable pageable); 17 | 18 | Long countByPostIdAndDeleteFlag(Long postId, boolean deleteFlag); 19 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/infrastructure/PostLikeRepository.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.infrastructure; 2 | 3 | import kr.ac.hs.selab.board.domain.PostLike; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.List; 7 | 8 | public interface PostLikeRepository extends JpaRepository { 9 | List findByPostId(Long postId); 10 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/infrastructure/PostRepository.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.infrastructure; 2 | 3 | import kr.ac.hs.selab.board.domain.Post; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | public interface PostRepository extends JpaRepository { 12 | boolean existsById(Long id); 13 | 14 | Optional findByIdAndDeleteFlag(Long id, boolean deleteFlag); 15 | 16 | List findByBoardIdAndDeleteFlag(Long boardId, boolean deleteFlag); 17 | 18 | Page findByBoardIdAndDeleteFlag(Long boardId, boolean deleteFlag, Pageable pageable); 19 | 20 | Long countByBoardIdAndDeleteFlag(Long boardId, boolean deleteFlag); 21 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/presentation/BoardSdk.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.presentation; 2 | 3 | import io.swagger.annotations.Api; 4 | import io.swagger.annotations.ApiResponse; 5 | import io.swagger.annotations.ApiResponses; 6 | import io.swagger.v3.oas.annotations.Operation; 7 | import io.swagger.v3.oas.annotations.Parameter; 8 | import kr.ac.hs.selab.board.dto.request.BoardRequest; 9 | import kr.ac.hs.selab.board.dto.response.BoardFindAllResponse; 10 | import kr.ac.hs.selab.board.dto.response.BoardFindResponse; 11 | import kr.ac.hs.selab.board.dto.response.BoardResponse; 12 | import kr.ac.hs.selab.common.template.ResponseTemplate; 13 | 14 | @Api(tags = "게시판 API", description = "Board Controller (미사용)") 15 | public interface BoardSdk { 16 | @Operation(summary = "게시판 생성", description = "게시판 정보를 이용해서 게시판을 생성한다.") 17 | @ApiResponses({ 18 | @ApiResponse(code = 201, message = "게시판 생성 성공"), 19 | @ApiResponse(code = 400, message = "게시판 생성 실패") 20 | }) 21 | ResponseTemplate create(@Parameter(description = "게시판 정보") BoardRequest request); 22 | 23 | @Operation(summary = "전체 게시판 조회", description = "전체 게시판을 조회한다.") 24 | @ApiResponses({ 25 | @ApiResponse(code = 201, message = "게시판 조회 성공"), 26 | @ApiResponse(code = 400, message = "게시판 조회 실패") 27 | }) 28 | ResponseTemplate findAll(); 29 | 30 | @Operation(summary = "게시판 조회", description = "게시판 정보를 이용해서 게시판을 조회한다.") 31 | @ApiResponses({ 32 | @ApiResponse(code = 201, message = "게시판 조회 성공"), 33 | @ApiResponse(code = 400, message = "게시판 조회 실패") 34 | }) 35 | ResponseTemplate find(@Parameter(description = "게시판 id 값") Long id); 36 | 37 | @Operation(summary = "게시판 수정", description = "새로운 게시판 정보를 이용해서 게시판을 수정한다.") 38 | @ApiResponses({ 39 | @ApiResponse(code = 201, message = "게시판 수정 성공"), 40 | @ApiResponse(code = 400, message = "게시판 수정 실패") 41 | }) 42 | ResponseTemplate update(@Parameter(description = "게시판 id 값") Long id, 43 | @Parameter(description = "게시판 정보") BoardRequest request); 44 | 45 | @Operation(summary = "게시판 삭제", description = "게시판을 지정하여 게시판을 삭제한다.") 46 | @ApiResponses({ 47 | @ApiResponse(code = 201, message = "게시판 삭제 성공"), 48 | @ApiResponse(code = 400, message = "게시판 삭제 실패") 49 | }) 50 | ResponseTemplate delete(@Parameter(description = "게시판 id 값") Long id) throws InterruptedException; 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/presentation/CommentLikeController.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.presentation; 2 | 3 | import kr.ac.hs.selab.board.dto.CommentLikeDto; 4 | import kr.ac.hs.selab.board.dto.CommentLikeFindDto; 5 | import kr.ac.hs.selab.board.dto.response.CommentLikeFindResponse; 6 | import kr.ac.hs.selab.board.dto.response.CommentLikeResponse; 7 | import kr.ac.hs.selab.board.facade.CommentLikeFacade; 8 | import kr.ac.hs.selab.common.template.ResponseMessage; 9 | import kr.ac.hs.selab.common.template.ResponseTemplate; 10 | import kr.ac.hs.selab.common.utils.SecurityUtils; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.web.bind.annotation.DeleteMapping; 13 | import org.springframework.web.bind.annotation.GetMapping; 14 | import org.springframework.web.bind.annotation.PathVariable; 15 | import org.springframework.web.bind.annotation.PostMapping; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RestController; 18 | 19 | @RequiredArgsConstructor 20 | @RequestMapping("/api/v1/comments") 21 | @RestController 22 | public class CommentLikeController implements CommentLikeSdk { 23 | private final CommentLikeFacade commentLikeFacade; 24 | 25 | @Override 26 | @PostMapping("/{commentId}/likes") 27 | public ResponseTemplate create(@PathVariable Long commentId) { 28 | var memberEmail = SecurityUtils.getCurrentUsername(); 29 | var dto = new CommentLikeDto(memberEmail, commentId); 30 | 31 | var response = commentLikeFacade.create(dto); 32 | return ResponseTemplate.created(ResponseMessage.COMMENT_LIKE_CREATE_SUCCESS, response); 33 | } 34 | 35 | @Override 36 | @GetMapping("/{commentId}/likes") 37 | public ResponseTemplate find(@PathVariable Long commentId) { 38 | var dto = new CommentLikeFindDto(commentId); 39 | var response = commentLikeFacade.find(dto); 40 | 41 | return ResponseTemplate.ok(ResponseMessage.COMMENT_LIKE_FIND_SUCCESS, response); 42 | } 43 | 44 | @Override 45 | @DeleteMapping("/likes/{id}") 46 | public ResponseTemplate delete(@PathVariable Long id) { 47 | var response = commentLikeFacade.delete(id); 48 | return ResponseTemplate.ok(ResponseMessage.COMMENT_LIKE_DELETE_SUCCESS, response); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/presentation/CommentLikeSdk.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.presentation; 2 | 3 | import io.swagger.annotations.Api; 4 | import io.swagger.annotations.ApiResponse; 5 | import io.swagger.annotations.ApiResponses; 6 | import io.swagger.v3.oas.annotations.Operation; 7 | import kr.ac.hs.selab.board.dto.response.CommentLikeFindResponse; 8 | import kr.ac.hs.selab.board.dto.response.CommentLikeResponse; 9 | import kr.ac.hs.selab.common.template.ResponseTemplate; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | 12 | @Api(tags = "댓글 좋아요 API", description = "Comment Like Controller (미사용)") 13 | public interface CommentLikeSdk { 14 | @Operation(summary = "댓글 좋아요 생성", description = "댓글에 좋아요를 한다.") 15 | @ApiResponses({ 16 | @ApiResponse(code = 201, message = "댓글 좋아요 성공"), 17 | @ApiResponse(code = 400, message = "댓글 좋아요 실패") 18 | }) 19 | ResponseTemplate create(@PathVariable Long commentId); 20 | 21 | @Operation(summary = "댓글에 모든 좋아요 조회", description = "댓글에 모든 좋아요를 조회한다.") 22 | @ApiResponses({ 23 | @ApiResponse(code = 201, message = "댓글 좋아요 조회 성공"), 24 | @ApiResponse(code = 400, message = "댓글 좋아요 조회 실패") 25 | }) 26 | ResponseTemplate find(@PathVariable Long commentId); 27 | 28 | @Operation(summary = "댓글 좋아요 삭제", description = "댓글에 좋아요를 취소한다.") 29 | @ApiResponses({ 30 | @ApiResponse(code = 201, message = "댓글 좋아요 삭제 성공"), 31 | @ApiResponse(code = 400, message = "댓글 좋아요 삭제 실패") 32 | }) 33 | ResponseTemplate delete(@PathVariable Long id); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/presentation/PostLikeController.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.presentation; 2 | 3 | import kr.ac.hs.selab.board.dto.PostLikeDto; 4 | import kr.ac.hs.selab.board.dto.PostLikeFindDto; 5 | import kr.ac.hs.selab.board.dto.response.PostLikeFindResponse; 6 | import kr.ac.hs.selab.board.dto.response.PostLikeResponse; 7 | import kr.ac.hs.selab.board.facade.PostLikeFacade; 8 | import kr.ac.hs.selab.common.template.ResponseMessage; 9 | import kr.ac.hs.selab.common.template.ResponseTemplate; 10 | import kr.ac.hs.selab.common.utils.SecurityUtils; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.web.bind.annotation.DeleteMapping; 13 | import org.springframework.web.bind.annotation.GetMapping; 14 | import org.springframework.web.bind.annotation.PathVariable; 15 | import org.springframework.web.bind.annotation.PostMapping; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RestController; 18 | 19 | @RequiredArgsConstructor 20 | @RequestMapping("/api/v1/posts") 21 | @RestController 22 | public class PostLikeController implements PostLikeSdk { 23 | private final PostLikeFacade postLikeFacade; 24 | 25 | @Override 26 | @PostMapping("/{postId}/likes") 27 | public ResponseTemplate create(@PathVariable Long postId) { 28 | var memberEmail = SecurityUtils.getCurrentUsername(); 29 | var dto = new PostLikeDto(memberEmail, postId); 30 | 31 | var response = postLikeFacade.create(dto); 32 | return ResponseTemplate.created(ResponseMessage.POST_LIKE_CREATE_SUCCESS, response); 33 | } 34 | 35 | @Override 36 | @GetMapping("/{postId}/likes") 37 | public ResponseTemplate find(@PathVariable Long postId) { 38 | var dto = new PostLikeFindDto(postId); 39 | var response = postLikeFacade.find(dto); 40 | 41 | return ResponseTemplate.ok(ResponseMessage.POST_LIKE_FIND_SUCCESS, response); 42 | } 43 | 44 | @Override 45 | @DeleteMapping("/likes/{id}") 46 | public ResponseTemplate delete(@PathVariable Long id) { 47 | var response = postLikeFacade.delete(id); 48 | return ResponseTemplate.ok(ResponseMessage.POST_LIKE_DELETE_SUCCESS, response); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/board/presentation/PostLikeSdk.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.presentation; 2 | 3 | import io.swagger.annotations.Api; 4 | import io.swagger.annotations.ApiResponse; 5 | import io.swagger.annotations.ApiResponses; 6 | import io.swagger.v3.oas.annotations.Operation; 7 | import kr.ac.hs.selab.board.dto.response.PostLikeFindResponse; 8 | import kr.ac.hs.selab.board.dto.response.PostLikeResponse; 9 | import kr.ac.hs.selab.common.template.ResponseTemplate; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | 12 | @Api(tags = "게시글 좋아요 API", description = "Post Like Controller (미사용)") 13 | public interface PostLikeSdk { 14 | @Operation(summary = "게시글 좋아요 생성", description = "게시글에 좋아요를 한다.") 15 | @ApiResponses({ 16 | @ApiResponse(code = 201, message = "게시글 좋아요 성공"), 17 | @ApiResponse(code = 400, message = "게시글 좋아요 실패") 18 | }) 19 | ResponseTemplate create(@PathVariable Long postId); 20 | 21 | @Operation(summary = "게시글에 모든 좋아요 조회", description = "게시글에 모든 좋아요를 조회한다.") 22 | @ApiResponses({ 23 | @ApiResponse(code = 201, message = "게시글 좋아요 조회 성공"), 24 | @ApiResponse(code = 400, message = "게시글 좋아요 조회 실패") 25 | }) 26 | ResponseTemplate find(@PathVariable Long postId); 27 | 28 | @Operation(summary = "게시글 좋아요 삭제", description = "게시글에 좋아요를 취소한다.") 29 | @ApiResponses({ 30 | @ApiResponse(code = 201, message = "게시글 좋아요 삭제 성공"), 31 | @ApiResponse(code = 400, message = "게시글 좋아요 삭제 실패") 32 | }) 33 | ResponseTemplate delete(@PathVariable Long id); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/common/config/AsyncConfig.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.common.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.scheduling.annotation.AsyncConfigurerSupport; 5 | import org.springframework.scheduling.annotation.EnableAsync; 6 | 7 | @Configuration 8 | @EnableAsync 9 | public class AsyncConfig extends AsyncConfigurerSupport { 10 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/common/config/CorsConfig.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.common.config; 2 | 3 | import kr.ac.hs.selab.common.properties.CorsProperties; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.web.cors.CorsConfiguration; 9 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource; 10 | 11 | import javax.validation.constraints.NotNull; 12 | 13 | @Configuration 14 | @EnableConfigurationProperties(CorsProperties.class) 15 | @RequiredArgsConstructor 16 | public class CorsConfig { 17 | 18 | @NotNull 19 | private final CorsProperties corsProperties; 20 | 21 | @Bean 22 | public UrlBasedCorsConfigurationSource corsConfigurationSource() { 23 | var corsConfig = makeCorsConfiguration(); 24 | return makeUrlBasedCorsConfigurationSource(corsConfig); 25 | } 26 | 27 | private CorsConfiguration makeCorsConfiguration() { 28 | return registerCorsConfiguration(new CorsConfiguration()); 29 | } 30 | 31 | private CorsConfiguration registerCorsConfiguration( 32 | @NotNull final CorsConfiguration corsConfig 33 | ) { 34 | corsConfig.setAllowedHeaders(corsProperties.getAllowedHeaders()); 35 | corsConfig.setAllowedMethods(corsProperties.getAllowedMethods()); 36 | corsConfig.setAllowedOrigins(corsProperties.getAllowedOrigins()); 37 | corsConfig.setAllowCredentials(true); 38 | corsConfig.setMaxAge(corsConfig.getMaxAge()); 39 | return corsConfig; 40 | } 41 | 42 | private UrlBasedCorsConfigurationSource makeUrlBasedCorsConfigurationSource( 43 | @NotNull final CorsConfiguration corsConfig 44 | ) { 45 | return addCorsConfiguration(corsConfig, new UrlBasedCorsConfigurationSource()); 46 | } 47 | 48 | private UrlBasedCorsConfigurationSource addCorsConfiguration( 49 | @NotNull final CorsConfiguration corsConfig, 50 | @NotNull final UrlBasedCorsConfigurationSource corsConfigSource 51 | ) { 52 | corsConfigSource.registerCorsConfiguration(corsProperties.getApplyUrlRange(), corsConfig); 53 | return corsConfigSource; 54 | } 55 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/common/config/JpaConfig.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.common.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing; 5 | 6 | @Configuration 7 | @EnableJpaAuditing 8 | public class JpaConfig { 9 | 10 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/common/domain/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.common.domain; 2 | 3 | import lombok.Getter; 4 | import org.springframework.data.annotation.CreatedDate; 5 | import org.springframework.data.annotation.LastModifiedDate; 6 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 7 | 8 | import javax.persistence.EntityListeners; 9 | import javax.persistence.MappedSuperclass; 10 | import java.time.LocalDateTime; 11 | 12 | @Getter 13 | @MappedSuperclass 14 | @EntityListeners(AuditingEntityListener.class) 15 | public abstract class BaseEntity { 16 | 17 | @CreatedDate 18 | private LocalDateTime createdAt; 19 | 20 | @LastModifiedDate 21 | private LocalDateTime modifiedAt; 22 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/common/presentation/HealthController.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.common.presentation; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | @RestController 8 | @RequestMapping("/health") 9 | public class HealthController { 10 | @GetMapping 11 | public String health() { 12 | return "Health Good!"; 13 | } 14 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/common/presentation/SwaggerController.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.common.presentation; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | 6 | @Controller 7 | public class SwaggerController { 8 | @GetMapping("/swagger") 9 | public String swagger() { 10 | return "redirect:/swagger-ui/index.html"; 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/common/properties/CorsProperties.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.common.properties; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import org.springframework.boot.context.properties.ConfigurationProperties; 7 | 8 | import javax.validation.constraints.Max; 9 | import javax.validation.constraints.NotBlank; 10 | import javax.validation.constraints.NotNull; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | @Setter 15 | @NoArgsConstructor 16 | @ConfigurationProperties(prefix = "cors") 17 | public class CorsProperties { 18 | 19 | private static final String COMMA = ","; 20 | @NotBlank 21 | private String allowedOrigins; 22 | @NotBlank 23 | private String allowedMethods; 24 | @NotBlank 25 | private String allowedHeaders; 26 | @Getter 27 | @NotNull 28 | @Max(3600) 29 | private Long maxAge; 30 | @Getter 31 | @NotBlank 32 | private String applyUrlRange; 33 | 34 | public List getAllowedOrigins() { 35 | return asListWithSplitRegex(allowedOrigins); 36 | } 37 | 38 | public List getAllowedMethods() { 39 | return asListWithSplitRegex(allowedMethods); 40 | } 41 | 42 | public List getAllowedHeaders() { 43 | return asListWithSplitRegex(allowedHeaders); 44 | } 45 | 46 | private List asListWithSplitRegex(final String str) { 47 | return Arrays.asList(str.split(COMMA)); 48 | } 49 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/common/properties/JwtProperties.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.common.properties; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import org.springframework.boot.context.properties.ConfigurationProperties; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | import javax.validation.constraints.NotBlank; 10 | import javax.validation.constraints.NotNull; 11 | 12 | @Configuration 13 | @Setter 14 | @NoArgsConstructor 15 | @ConfigurationProperties(prefix = "jwt") 16 | public class JwtProperties { 17 | @Getter 18 | @NotBlank 19 | private String issuer; 20 | @Getter 21 | @NotBlank 22 | private String secret; 23 | @Getter 24 | @NotNull 25 | private Long tokenValidityInSeconds; 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/common/template/PageResponseTemplate.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.common.template; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import lombok.Getter; 5 | import org.springframework.data.domain.Page; 6 | import org.springframework.http.HttpStatus; 7 | 8 | import java.io.Serializable; 9 | import java.time.LocalDateTime; 10 | import java.util.List; 11 | 12 | @Getter 13 | public class PageResponseTemplate implements Serializable { 14 | @JsonIgnore 15 | private final transient HttpStatus status; 16 | private final String message; 17 | private final String code; 18 | private final LocalDateTime serverDateTime; 19 | private final List data; 20 | private final int page; 21 | private final int size; 22 | private final int totalPages; 23 | private final long totalElements; 24 | private final boolean firstPage; 25 | private final boolean lastPage; 26 | 27 | protected PageResponseTemplate(HttpStatus status, ResponseMessage message, Page page) { 28 | this.status = status; 29 | this.message = message.name(); 30 | this.code = message.getCode(); 31 | this.serverDateTime = LocalDateTime.now(); 32 | this.data = page.getContent(); 33 | this.page = page.getNumber(); 34 | this.size = page.getSize(); 35 | this.totalPages = page.getTotalPages(); 36 | this.totalElements = page.getTotalElements(); 37 | this.firstPage = page.isFirst(); 38 | this.lastPage = page.isLast(); 39 | } 40 | 41 | public static PageResponseTemplate ok(final ResponseMessage message, final Page page) { 42 | return new PageResponseTemplate(HttpStatus.OK, message, page); 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/common/template/ResponseTemplate.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.common.template; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import io.swagger.v3.oas.annotations.media.Schema; 5 | import lombok.Getter; 6 | import org.springframework.http.HttpStatus; 7 | 8 | import javax.validation.constraints.NotNull; 9 | import java.io.Serializable; 10 | import java.time.LocalDateTime; 11 | 12 | @Getter 13 | public class ResponseTemplate implements Serializable { 14 | @JsonIgnore 15 | private final transient HttpStatus status; 16 | @Schema(description = "응답 메세지") 17 | private final String message; 18 | @Schema(description = "응답 코드") 19 | private final String code; 20 | @Schema(description = "서버시간") 21 | private final LocalDateTime serverDateTime; 22 | @Schema(description = "응답 데이터") 23 | private T data; 24 | 25 | private ResponseTemplate(@NotNull final ResponseMessage message, @NotNull final T data, 26 | @NotNull final HttpStatus status) { 27 | this.message = message.name(); 28 | this.code = message.getCode(); 29 | this.serverDateTime = LocalDateTime.now(); 30 | this.data = data; 31 | this.status = status; 32 | } 33 | 34 | private ResponseTemplate(@NotNull final ResponseMessage message, 35 | @NotNull final HttpStatus status) { 36 | this.message = message.name(); 37 | this.code = message.getCode(); 38 | this.serverDateTime = LocalDateTime.now(); 39 | this.status = status; 40 | } 41 | 42 | public static ResponseTemplate ok(final ResponseMessage message, final T data) { 43 | return new ResponseTemplate<>(message, data, HttpStatus.OK); 44 | } 45 | 46 | public static ResponseTemplate created(final ResponseMessage message, final T data) { 47 | return new ResponseTemplate<>(message, data, HttpStatus.CREATED); 48 | } 49 | 50 | public static ResponseTemplate noContent(final ResponseMessage message) { 51 | return new ResponseTemplate<>(message, HttpStatus.NO_CONTENT); 52 | } 53 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/common/template/ResponseWrapper.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.common.template; 2 | 3 | import org.springframework.core.GenericTypeResolver; 4 | import org.springframework.core.MethodParameter; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.http.converter.HttpMessageConverter; 7 | import org.springframework.http.server.ServerHttpRequest; 8 | import org.springframework.http.server.ServerHttpResponse; 9 | import org.springframework.web.bind.annotation.RestControllerAdvice; 10 | import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; 11 | 12 | import java.util.Objects; 13 | 14 | @RestControllerAdvice 15 | public class ResponseWrapper implements ResponseBodyAdvice { 16 | 17 | @Override 18 | public boolean supports(final MethodParameter returnType, 19 | final Class> converterType) { 20 | 21 | if (Objects.isNull(returnType.getMethod())) { 22 | return false; 23 | } 24 | 25 | final var genericType = GenericTypeResolver.resolveReturnType( 26 | returnType.getMethod(), 27 | ResponseTemplate.class 28 | ); 29 | 30 | return genericType.equals(ResponseTemplate.class); 31 | } 32 | 33 | @Override 34 | public Object beforeBodyWrite(final Object body, final MethodParameter returnType, 35 | final MediaType selectedContentType, 36 | final Class> selectedConverterType, 37 | final ServerHttpRequest request, 38 | final ServerHttpResponse response) { 39 | final var status = ((ResponseTemplate) body).getStatus(); 40 | 41 | response.setStatusCode(status); 42 | 43 | return body; 44 | } 45 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/common/utils/Constants.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.common.utils; 2 | 3 | import lombok.experimental.UtilityClass; 4 | 5 | @UtilityClass 6 | public class Constants { 7 | public final boolean DELETED = true; 8 | public final boolean NOT_DELETED = false; 9 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/common/utils/SecurityUtils.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.common.utils; 2 | 3 | import kr.ac.hs.selab.error.exception.common.NonExitsException; 4 | import kr.ac.hs.selab.error.template.ErrorMessage; 5 | import lombok.experimental.UtilityClass; 6 | import org.springframework.security.core.Authentication; 7 | import org.springframework.security.core.context.SecurityContextHolder; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | 10 | import java.util.Objects; 11 | 12 | @UtilityClass 13 | public class SecurityUtils { 14 | 15 | // TODO : 메서드 역할 분리 작업 진행 필요 16 | public String getCurrentUsername() { 17 | var authentication = getAuthentication(); 18 | 19 | if (authentication.getPrincipal() instanceof UserDetails) { 20 | return getUserDetails(authentication).getUsername(); 21 | } 22 | return getAuthenticationPrincipal(authentication); 23 | } 24 | 25 | private Authentication getAuthentication() { 26 | var authentication = SecurityContextHolder.getContext().getAuthentication(); 27 | isNullAuthentication(authentication); 28 | return authentication; 29 | } 30 | 31 | private void isNullAuthentication(final Authentication authentication) { 32 | if (Objects.isNull(authentication)) { 33 | throw new NonExitsException(ErrorMessage.MEMBER_NOT_EXISTS_ERROR); 34 | } 35 | } 36 | 37 | private UserDetails getUserDetails(final Authentication authentication) { 38 | var userDetails = (UserDetails) authentication.getPrincipal(); 39 | isNullUserDetails(userDetails); 40 | return userDetails; 41 | } 42 | 43 | private void isNullUserDetails(final UserDetails userDetails) { 44 | if (Objects.isNull(userDetails.getUsername())) { 45 | throw new NonExitsException(ErrorMessage.MEMBER_NOT_EXISTS_ERROR); 46 | } 47 | } 48 | 49 | private String getAuthenticationPrincipal(final Authentication authentication) { 50 | isNullAuthenticationPrincipal(authentication); 51 | return authentication.getPrincipal().toString(); 52 | } 53 | 54 | private void isNullAuthenticationPrincipal(final Authentication authentication) { 55 | if (Objects.isNull(authentication.getPrincipal())) { 56 | throw new NonExitsException(ErrorMessage.MEMBER_NOT_EXISTS_ERROR); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/core_qa/application/CoreQaService.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.core_qa.application; 2 | 3 | import kr.ac.hs.selab.core_qa.converter.CoreQaConverter; 4 | import kr.ac.hs.selab.core_qa.domain.CoreQa; 5 | import kr.ac.hs.selab.core_qa.dto.bundle.CoreQaCreateBundle; 6 | import kr.ac.hs.selab.core_qa.infrastructure.CoreQaRepository; 7 | import kr.ac.hs.selab.error.exception.common.NonExitsException; 8 | import kr.ac.hs.selab.error.template.ErrorMessage; 9 | import lombok.RequiredArgsConstructor; 10 | import org.springframework.data.domain.Page; 11 | import org.springframework.data.domain.Pageable; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.transaction.annotation.Transactional; 14 | 15 | @Transactional(readOnly = true) 16 | @RequiredArgsConstructor 17 | @Service 18 | public class CoreQaService { 19 | private final CoreQaRepository coreQaRepository; 20 | 21 | @Transactional 22 | public CoreQa create(CoreQaCreateBundle bundle) { 23 | return coreQaRepository.save(CoreQaConverter.toCoreQa(bundle)); 24 | } 25 | 26 | public Long count() { 27 | return coreQaRepository.count(); 28 | } 29 | 30 | public CoreQa findById(Long id) { 31 | return coreQaRepository.findById(id) 32 | .orElseThrow(() -> new NonExitsException(ErrorMessage.CORE_QA_NOT_EXISTS_ERROR)); 33 | } 34 | 35 | public Page findByPage(Pageable pageable) { 36 | return coreQaRepository.findAll(pageable); 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/core_qa/converter/CoreQaConverter.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.core_qa.converter; 2 | 3 | import kr.ac.hs.selab.core_qa.domain.CoreQa; 4 | import kr.ac.hs.selab.core_qa.dto.bundle.CoreQaCreateBundle; 5 | import kr.ac.hs.selab.core_qa.dto.bundle.CoreQaFindByPageBundle; 6 | import kr.ac.hs.selab.core_qa.dto.response.CoreQaFindByIdResponse; 7 | import kr.ac.hs.selab.core_qa.dto.response.CoreQaFindByPageResponse; 8 | import lombok.experimental.UtilityClass; 9 | 10 | @UtilityClass 11 | public class CoreQaConverter { 12 | public CoreQa toCoreQa(CoreQaCreateBundle bundle) { 13 | return CoreQa.builder() 14 | .title(bundle.title()) 15 | .content(bundle.content()) 16 | .memberId(bundle.memberId()) 17 | .build(); 18 | } 19 | 20 | public CoreQaFindByIdResponse toCoreQaFindByIdResponse(CoreQa qa) { 21 | return CoreQaFindByIdResponse.builder() 22 | .id(qa.getId()) 23 | .title(qa.getTitle()) 24 | .content(qa.getContent()) 25 | .memberId(qa.getMemberId()) 26 | .build(); 27 | } 28 | 29 | public CoreQaFindByPageResponse toCoreQaFindByPageResponse(CoreQaFindByPageBundle bundle) { 30 | var coreQaFindByIdResponses = bundle.getCoreQas() 31 | .stream() 32 | .map(CoreQaConverter::toCoreQaFindByIdResponse) 33 | .toList(); 34 | 35 | return CoreQaFindByPageResponse.builder() 36 | .totalCount(bundle.getTotalCount()) 37 | .pageNumber(bundle.getPageable().getPageNumber()) 38 | .pageSize(bundle.getPageable().getPageSize()) 39 | .sort(bundle.getPageable().getSort().toString()) 40 | .coreQas(coreQaFindByIdResponses) 41 | .build(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/core_qa/domain/CoreQa.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.core_qa.domain; 2 | 3 | import kr.ac.hs.selab.common.domain.BaseEntity; 4 | import lombok.AccessLevel; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.persistence.Column; 10 | import javax.persistence.Entity; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.GenerationType; 13 | import javax.persistence.Id; 14 | 15 | @Getter 16 | @Entity 17 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 18 | public class CoreQa extends BaseEntity { 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | @Column(name = "core_qa_id", nullable = false) 22 | private Long id; 23 | 24 | @Column(name = "core_qa_title") 25 | private String title; 26 | 27 | @Column(name = "core_qa_content") 28 | private String content; 29 | 30 | @Column(name = "member_id") 31 | private Long memberId; 32 | 33 | @Builder 34 | public CoreQa(String title, String content, Long memberId) { 35 | this.title = title; 36 | this.content = content; 37 | this.memberId = memberId; 38 | } 39 | 40 | public void update(String title, String content) { 41 | this.title = title; 42 | this.content = content; 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/core_qa/dto/bundle/CoreQaCreateBundle.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.core_qa.dto.bundle; 2 | 3 | public record CoreQaCreateBundle(Long memberId, String title, String content) { 4 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/core_qa/dto/bundle/CoreQaFindByPageBundle.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.core_qa.dto.bundle; 2 | 3 | import kr.ac.hs.selab.core_qa.domain.CoreQa; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.data.domain.Pageable; 8 | 9 | @Builder 10 | @Getter 11 | public class CoreQaFindByPageBundle { 12 | private final Long totalCount; 13 | private final Pageable pageable; 14 | private final Page coreQas; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/core_qa/dto/request/CoreQaCreateRequest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.core_qa.dto.request; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.validation.constraints.NotBlank; 9 | import javax.validation.constraints.Size; 10 | 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Getter 14 | public class CoreQaCreateRequest { 15 | @Schema(description = "질의응답 제목") 16 | @Size(max = 30, message = "QA 제목이 너무 깁니다.") 17 | private String title; 18 | 19 | @Schema(description = "질의응답 내용") 20 | @NotBlank(message = "QA의 내용을 입력 해주세요.") 21 | private String content; 22 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/core_qa/dto/response/CoreQaFindByIdResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.core_qa.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | @Builder 8 | @Getter 9 | public class CoreQaFindByIdResponse { 10 | @Schema(description = "작성자 아이디") 11 | private final Long id; 12 | 13 | @Schema(description = "질의응답 제목") 14 | private final String title; 15 | 16 | @Schema(description = "질의응답 내용") 17 | private final String content; 18 | 19 | @Schema(description = "질의응답 아이디") 20 | private final Long memberId; 21 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/core_qa/dto/response/CoreQaFindByPageResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.core_qa.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | import java.util.List; 8 | 9 | @Builder 10 | @Getter 11 | public class CoreQaFindByPageResponse { 12 | @Schema(description = "질의응답 전체 개수") 13 | private final Long totalCount; 14 | 15 | @Schema(description = "질의응답 페이지 번호") 16 | private final int pageNumber; 17 | 18 | @Schema(description = "한 페이지에 가져올 질의응답 수") 19 | private final int pageSize; 20 | 21 | @Schema(description = "질의응답 조회 정렬 기준") 22 | private final String sort; 23 | 24 | @Schema(description = "질의응답 전체 리스트") 25 | private final List coreQas; 26 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/core_qa/dto/response/CoreQaResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.core_qa.dto.response; 2 | 3 | public record CoreQaResponse(Long id) { 4 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/core_qa/facade/CoreQaFacade.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.core_qa.facade; 2 | 3 | import kr.ac.hs.selab.core_qa.application.CoreQaService; 4 | import kr.ac.hs.selab.core_qa.converter.CoreQaConverter; 5 | import kr.ac.hs.selab.core_qa.dto.bundle.CoreQaCreateBundle; 6 | import kr.ac.hs.selab.core_qa.dto.bundle.CoreQaFindByPageBundle; 7 | import kr.ac.hs.selab.core_qa.dto.request.CoreQaCreateRequest; 8 | import kr.ac.hs.selab.core_qa.dto.response.CoreQaFindByIdResponse; 9 | import kr.ac.hs.selab.core_qa.dto.response.CoreQaFindByPageResponse; 10 | import kr.ac.hs.selab.core_qa.dto.response.CoreQaResponse; 11 | import kr.ac.hs.selab.member.application.MemberService; 12 | import lombok.RequiredArgsConstructor; 13 | import org.springframework.data.domain.Pageable; 14 | import org.springframework.stereotype.Component; 15 | import org.springframework.transaction.annotation.Transactional; 16 | 17 | @Transactional(readOnly = true) 18 | @RequiredArgsConstructor 19 | @Component 20 | public class CoreQaFacade { 21 | private final MemberService memberService; 22 | private final CoreQaService coreQaService; 23 | 24 | @Transactional 25 | public CoreQaResponse create(String memberEmail, CoreQaCreateRequest request) { 26 | var memberId = memberService.findByEmail(memberEmail).getId(); 27 | var coreQaRequest = new CoreQaCreateBundle(memberId, request.getTitle(), request.getContent()); 28 | 29 | var qa = coreQaService.create(coreQaRequest); 30 | return new CoreQaResponse(qa.getId()); 31 | } 32 | 33 | public CoreQaFindByIdResponse findById(Long id) { 34 | var qa = coreQaService.findById(id); 35 | return CoreQaConverter.toCoreQaFindByIdResponse(qa); 36 | } 37 | 38 | public CoreQaFindByPageResponse findByPage(Pageable pageable) { 39 | var bundle = CoreQaFindByPageBundle.builder() 40 | .totalCount(coreQaService.count()) 41 | .pageable(pageable) 42 | .coreQas(coreQaService.findByPage(pageable)) 43 | .build(); 44 | return CoreQaConverter.toCoreQaFindByPageResponse(bundle); 45 | } 46 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/core_qa/infrastructure/CoreQaRepository.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.core_qa.infrastructure; 2 | 3 | import kr.ac.hs.selab.core_qa.domain.CoreQa; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface CoreQaRepository extends JpaRepository { 9 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/core_qa/presentation/CoreQaController.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.core_qa.presentation; 2 | 3 | import kr.ac.hs.selab.common.template.ResponseMessage; 4 | import kr.ac.hs.selab.common.template.ResponseTemplate; 5 | import kr.ac.hs.selab.common.utils.SecurityUtils; 6 | import kr.ac.hs.selab.core_qa.dto.request.CoreQaCreateRequest; 7 | import kr.ac.hs.selab.core_qa.dto.response.CoreQaFindByIdResponse; 8 | import kr.ac.hs.selab.core_qa.dto.response.CoreQaFindByPageResponse; 9 | import kr.ac.hs.selab.core_qa.dto.response.CoreQaResponse; 10 | import kr.ac.hs.selab.core_qa.facade.CoreQaFacade; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.data.domain.Pageable; 13 | import org.springframework.data.domain.Sort; 14 | import org.springframework.data.web.PageableDefault; 15 | import org.springframework.validation.annotation.Validated; 16 | import org.springframework.web.bind.annotation.GetMapping; 17 | import org.springframework.web.bind.annotation.PathVariable; 18 | import org.springframework.web.bind.annotation.PostMapping; 19 | import org.springframework.web.bind.annotation.RequestBody; 20 | import org.springframework.web.bind.annotation.RequestMapping; 21 | import org.springframework.web.bind.annotation.RestController; 22 | 23 | @RestController 24 | @RequestMapping("/api/v1/core-qas") 25 | @RequiredArgsConstructor 26 | public class CoreQaController implements CoreQaSdk { 27 | private final CoreQaFacade coreQaFacade; 28 | 29 | @Override 30 | @PostMapping 31 | public ResponseTemplate create(@Validated @RequestBody CoreQaCreateRequest request) { 32 | var memberEmail = SecurityUtils.getCurrentUsername(); 33 | var response = coreQaFacade.create(memberEmail, request); 34 | 35 | return ResponseTemplate.ok(ResponseMessage.CORE_QA_CREATE_SUCCESS, response); 36 | } 37 | 38 | @Override 39 | @GetMapping("/{id}") 40 | public ResponseTemplate find(@PathVariable Long id) { 41 | var response = coreQaFacade.findById(id); 42 | return ResponseTemplate.ok(ResponseMessage.CORE_QA_FIND_SUCCESS, response); 43 | } 44 | 45 | @Override 46 | @GetMapping 47 | public ResponseTemplate findByPage(@PageableDefault(size = 20, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable) { 48 | var response = coreQaFacade.findByPage(pageable); 49 | return ResponseTemplate.ok(ResponseMessage.CORE_QA_FIND_SUCCESS, response); 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/core_qa/presentation/CoreQaSdk.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.core_qa.presentation; 2 | 3 | import io.swagger.annotations.Api; 4 | import io.swagger.annotations.ApiOperation; 5 | import io.swagger.annotations.ApiResponse; 6 | import io.swagger.annotations.ApiResponses; 7 | import io.swagger.v3.oas.annotations.Parameter; 8 | import kr.ac.hs.selab.common.template.ResponseTemplate; 9 | import kr.ac.hs.selab.common.template.SwaggerNote; 10 | import kr.ac.hs.selab.core_qa.dto.request.CoreQaCreateRequest; 11 | import kr.ac.hs.selab.core_qa.dto.response.CoreQaFindByIdResponse; 12 | import kr.ac.hs.selab.core_qa.dto.response.CoreQaFindByPageResponse; 13 | import kr.ac.hs.selab.core_qa.dto.response.CoreQaResponse; 14 | import org.springframework.data.domain.Pageable; 15 | 16 | @Api(tags = "코어 질의 응답 API", description = "Core Qa Controller (MVP)") 17 | public interface CoreQaSdk { 18 | @ApiOperation(value = "CORE QA 생성", notes = SwaggerNote.CORE_QA_CREATE) 19 | @ApiResponses({ 20 | @ApiResponse(code = 201, message = "core qa 생성 성공"), 21 | @ApiResponse(code = 400, message = "core qa 생성 실패") 22 | }) 23 | ResponseTemplate create(@Parameter(description = "core qa 정보") CoreQaCreateRequest request); 24 | 25 | @ApiOperation(value = "CORE QA 조회", notes = SwaggerNote.CORE_QA_FIND) 26 | @ApiResponses({ 27 | @ApiResponse(code = 201, message = "core qa 생성 성공"), 28 | @ApiResponse(code = 400, message = "core qa 생성 실패") 29 | }) 30 | ResponseTemplate find(@Parameter Long id); 31 | 32 | @ApiOperation(value = "CORE QA 전체 조회", notes = SwaggerNote.CORE_QA_FIND_BY_PAGE) 33 | @ApiResponses({ 34 | @ApiResponse(code = 200, message = "core qa 전체 조회 성공"), 35 | @ApiResponse(code = 400, message = "core qa 전체 조회 실패") 36 | }) 37 | ResponseTemplate findByPage(Pageable pageable); 38 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/error/exception/BusinessException.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.error.exception; 2 | 3 | import kr.ac.hs.selab.error.template.ErrorMessage; 4 | import lombok.Getter; 5 | import org.springframework.http.HttpStatus; 6 | 7 | 8 | @Getter 9 | public abstract class BusinessException extends RuntimeException { 10 | 11 | private final ErrorMessage errorMessage; 12 | private final HttpStatus status; 13 | 14 | public BusinessException(final ErrorMessage message, final HttpStatus status) { 15 | super(message.getDetail()); 16 | this.errorMessage = message; 17 | this.status = status; 18 | } 19 | 20 | // TODO super에 exception을 추가하여 나중에 있을 stacktrace를 대비한다. 21 | // StackTrace를 가져갈 수 있는 구조로 만들어야 한다. 22 | // Cause까지도 관리가 가능하게... 23 | public BusinessException(final ErrorMessage message, final HttpStatus status, 24 | final Throwable cause) { 25 | super(message.getDetail(), cause); 26 | this.errorMessage = message; 27 | this.status = status; 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/error/exception/common/DuplicationException.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.error.exception.common; 2 | 3 | import kr.ac.hs.selab.error.exception.BusinessException; 4 | import kr.ac.hs.selab.error.template.ErrorMessage; 5 | import org.springframework.http.HttpStatus; 6 | 7 | public class DuplicationException extends BusinessException { 8 | 9 | public DuplicationException(final ErrorMessage message) { 10 | super(message, HttpStatus.BAD_REQUEST); 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/error/exception/common/InvalidArgumentException.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.error.exception.common; 2 | 3 | import kr.ac.hs.selab.error.exception.BusinessException; 4 | import kr.ac.hs.selab.error.template.ErrorMessage; 5 | import org.springframework.http.HttpStatus; 6 | 7 | public class InvalidArgumentException extends BusinessException { 8 | 9 | public InvalidArgumentException(final ErrorMessage message) { 10 | super(message, HttpStatus.BAD_REQUEST); 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/error/exception/common/NonExitsException.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.error.exception.common; 2 | 3 | import kr.ac.hs.selab.error.exception.BusinessException; 4 | import kr.ac.hs.selab.error.template.ErrorMessage; 5 | import org.springframework.http.HttpStatus; 6 | 7 | public class NonExitsException extends BusinessException { 8 | 9 | public NonExitsException(final ErrorMessage message) { 10 | super(message, HttpStatus.NOT_FOUND); 11 | } 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/error/template/ErrorTemplate.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.error.template; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Getter; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | 8 | import javax.validation.constraints.NotNull; 9 | import java.io.Serializable; 10 | import java.time.LocalDateTime; 11 | 12 | @Getter 13 | public class ErrorTemplate implements Serializable { 14 | @Schema(description = "응답 코드") 15 | private final String code; 16 | 17 | @Schema(description = "응답 메세지") 18 | private final String message; 19 | 20 | @Schema(description = "서버시간") 21 | private final LocalDateTime serverDateTime; 22 | 23 | public ErrorTemplate(@NotNull final ErrorMessage message) { 24 | this.message = message.name(); 25 | this.code = message.getCode(); 26 | this.serverDateTime = LocalDateTime.now(); 27 | } 28 | 29 | public ErrorTemplate(@NotNull final ErrorMessage message, @NotNull final String reason) { 30 | this.message = message.name(); 31 | this.code = reason; 32 | this.serverDateTime = LocalDateTime.now(); 33 | } 34 | 35 | public static ResponseEntity of(HttpStatus status, ErrorMessage message) { 36 | return ResponseEntity 37 | .status(status) 38 | .body(new ErrorTemplate(message)); 39 | } 40 | 41 | public static ResponseEntity of(HttpStatus status, ErrorMessage message, String reason) { 42 | return ResponseEntity 43 | .status(status) 44 | .body(new ErrorTemplate(message, reason)); 45 | } 46 | 47 | public static ResponseEntity badRequest() { 48 | return of(HttpStatus.BAD_REQUEST, ErrorMessage.CONFLICT_ERROR); 49 | } 50 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/application/FreePostService.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.application; 2 | 3 | import kr.ac.hs.selab.common.utils.Constants; 4 | import kr.ac.hs.selab.error.exception.common.NonExitsException; 5 | import kr.ac.hs.selab.error.template.ErrorMessage; 6 | import kr.ac.hs.selab.free_post.converter.FreePostConverter; 7 | import kr.ac.hs.selab.free_post.domain.FreePost; 8 | import kr.ac.hs.selab.free_post.dto.FreePostCreateDto; 9 | import kr.ac.hs.selab.free_post.dto.FreePostUpdateDto; 10 | import kr.ac.hs.selab.free_post.infrastructure.FreePostRepository; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.data.domain.Page; 13 | import org.springframework.data.domain.Pageable; 14 | import org.springframework.stereotype.Service; 15 | import org.springframework.transaction.annotation.Transactional; 16 | 17 | @RequiredArgsConstructor 18 | @Transactional(readOnly = true) 19 | @Service 20 | public class FreePostService { 21 | private final FreePostRepository freePostRepository; 22 | 23 | @Transactional 24 | public FreePost create(FreePostCreateDto dto) { 25 | return freePostRepository.save(FreePostConverter.toFreePost(dto)); 26 | } 27 | 28 | public Long count() { 29 | return freePostRepository.countByDeleteFlag(Constants.NOT_DELETED); 30 | } 31 | 32 | public FreePost findById(Long id) { 33 | return freePostRepository.findByIdAndDeleteFlag(id, Constants.NOT_DELETED) 34 | .orElseThrow(() -> new NonExitsException(ErrorMessage.FREE_POST_NOT_EXISTS_ERROR)); 35 | } 36 | 37 | public Page findByPage(Pageable pageable) { 38 | return freePostRepository.findByDeleteFlag(Constants.NOT_DELETED, pageable); 39 | } 40 | 41 | @Transactional 42 | public FreePost update(FreePostUpdateDto dto) { 43 | return findById(dto.getId()).update(dto.getTitle(), dto.getContent()); 44 | } 45 | 46 | @Transactional 47 | public FreePost delete(Long id) { 48 | return findById(id).delete(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/domain/FreePost.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.domain; 2 | 3 | import kr.ac.hs.selab.common.domain.BaseEntity; 4 | import lombok.AccessLevel; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.persistence.Column; 10 | import javax.persistence.Entity; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.GenerationType; 13 | import javax.persistence.Id; 14 | 15 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 16 | @Getter 17 | @Entity 18 | public class FreePost extends BaseEntity { 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | @Column(name = "free_post_id", nullable = false) 22 | private Long id; 23 | 24 | @Column(name = "member_id", nullable = false) 25 | private Long memberId; 26 | 27 | @Column(name = "free_post_title", nullable = false) 28 | private String title; 29 | 30 | @Column(name = "free_post_content", nullable = false) 31 | private String content; 32 | 33 | @Column(name = "free_post_delete_flag", nullable = false) 34 | private boolean deleteFlag; 35 | 36 | @Builder 37 | private FreePost(Long memberId, String title, String content) { 38 | this.title = title; 39 | this.memberId = memberId; 40 | this.content = content; 41 | this.deleteFlag = false; 42 | } 43 | 44 | public FreePost update(String title, String content) { 45 | this.title = title; 46 | this.content = content; 47 | return this; 48 | } 49 | 50 | public FreePost delete() { 51 | this.deleteFlag = true; 52 | return this; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/domain/FreePostComment.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.domain; 2 | 3 | import kr.ac.hs.selab.common.domain.BaseEntity; 4 | import lombok.AccessLevel; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.persistence.Column; 10 | import javax.persistence.Entity; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.GenerationType; 13 | import javax.persistence.Id; 14 | 15 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 16 | @Getter 17 | @Entity 18 | public class FreePostComment extends BaseEntity { 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | @Column(name = "free_post_comment_id", nullable = false) 22 | private Long id; 23 | 24 | @Column(name = "member_id", nullable = false) 25 | private Long memberId; 26 | 27 | @Column(name = "free_post_id", nullable = false) 28 | private Long freePostId; 29 | 30 | @Column(name = "free_post_comment_content", nullable = false) 31 | private String content; 32 | 33 | @Column(name = "free_post_comment_delete_flag", nullable = false) 34 | private boolean deleteFlag; 35 | 36 | @Builder 37 | public FreePostComment(Long memberId, Long freePostId, String content) { 38 | this.memberId = memberId; 39 | this.freePostId = freePostId; 40 | this.content = content; 41 | this.deleteFlag = false; 42 | } 43 | 44 | public FreePostComment update(String content) { 45 | this.content = content; 46 | return this; 47 | } 48 | 49 | public FreePostComment delete() { 50 | this.deleteFlag = true; 51 | return this; 52 | } 53 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/dto/FreePostCommentCreateDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.dto; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | @Builder 7 | @Getter 8 | public class FreePostCommentCreateDto { 9 | private final Long memberId; 10 | private final Long freePostId; 11 | private final String freePostCommentContent; 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/dto/FreePostCommentFindByFreePostIdAndPageDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.data.domain.Pageable; 6 | 7 | @RequiredArgsConstructor 8 | @Getter 9 | public class FreePostCommentFindByFreePostIdAndPageDto { 10 | private final Long freePostId; 11 | private final Pageable pageable; 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/dto/FreePostCommentUpdateDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | @Getter 8 | public class FreePostCommentUpdateDto { 9 | private final Long freePostCommentId; 10 | private final String freePostCommentContent; 11 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/dto/FreePostCreateDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.dto; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | @Builder 7 | @Getter 8 | public class FreePostCreateDto { 9 | private final Long memberId; 10 | private final String title; 11 | private final String content; 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/dto/FreePostFindByPageDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.dto; 2 | 3 | import kr.ac.hs.selab.free_post.domain.FreePost; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.data.domain.Pageable; 8 | 9 | @Builder 10 | @Getter 11 | public class FreePostFindByPageDto { 12 | private final Long totalCount; 13 | private final Pageable pageable; 14 | private final Page freePosts; 15 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/dto/FreePostUpdateDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.dto; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | @Builder 7 | @Getter 8 | public class FreePostUpdateDto { 9 | private final Long id; 10 | private final String title; 11 | private final String content; 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/dto/request/FreePostCommentRequest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.dto.request; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.validation.constraints.NotBlank; 9 | 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Getter 13 | public class FreePostCommentRequest { 14 | @Schema(description = "자유게시글의 댓글 내용") 15 | @NotBlank(message = "자유게시글의 댓글에 내용을 작성해야합니다.") 16 | private String freePostCommentContent; 17 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/dto/request/FreePostRequest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.dto.request; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.validation.constraints.NotBlank; 9 | import javax.validation.constraints.Size; 10 | 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Getter 14 | public class FreePostRequest { 15 | @Schema(description = "자유게시글 제목") 16 | @Size(max = 30, message = "자유게시글의 제목이 너무 깁니다.") 17 | private String title; 18 | 19 | @Schema(description = "자유게시글 내용") 20 | @NotBlank(message = "자유게시글의 내용을 입력해 주세요.") 21 | private String content; 22 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/dto/response/FreePostCommentFindByFreePostIdAndPageResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.List; 9 | 10 | @Builder 11 | @Getter 12 | public class FreePostCommentFindByFreePostIdAndPageResponse { 13 | @Schema(description = "자유게시판 id") 14 | private final Long freePostId; 15 | 16 | @Schema(description = "자유게시판 댓글 전체 개수") 17 | private final Long totalCount; 18 | 19 | @Schema(description = "댓글 페이지 넘버") 20 | private final int pageNumber; 21 | 22 | @Schema(description = "한 페이지에 가져올 댓글 수") 23 | private final int pageSize; 24 | 25 | @Schema(description = "댓글 정렬 기준") 26 | private final String sort; 27 | 28 | @Schema(description = "자유게시판의 댓글 목록") 29 | private final List freePostComments; 30 | 31 | @Builder 32 | @Getter 33 | public static class InnerResponse { 34 | @Schema(description = "작성자 id") 35 | private final Long memberId; 36 | 37 | @Schema(description = "자유게시판의 댓글 id") 38 | private final Long freePostCommentId; 39 | 40 | @Schema(description = "자유게시판의 댓글 내용") 41 | private final String freePostCommentContent; 42 | 43 | @Schema(description = "자유게시판의 댓글 생성 시간") 44 | private final LocalDateTime createdAt; 45 | 46 | @Schema(description = "자유게시판의 댓글 수정 시간") 47 | private final LocalDateTime modifiedAt; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/dto/response/FreePostCommentFindResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | @Builder 10 | @Getter 11 | public class FreePostCommentFindResponse { 12 | @Schema(description = "자유게시판 id") 13 | private final Long freePostId; 14 | 15 | @Schema(description = "작성자 id") 16 | private final Long memberId; 17 | 18 | @Schema(description = "공지사항의 댓글 id") 19 | private final Long freePostCommentId; 20 | 21 | @Schema(description = "공지사항의 댓글 내용") 22 | private final String freePostCommentContent; 23 | 24 | @Schema(description = "공지사항의 댓글 생성 시간") 25 | private final LocalDateTime createdAt; 26 | 27 | @Schema(description = "공지사항의 댓글 수정 시간") 28 | private final LocalDateTime modifiedAt; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/dto/response/FreePostCommentResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.dto.response; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | @Getter 8 | public class FreePostCommentResponse { 9 | private final Long freePostCommentId; 10 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/dto/response/FreePostFindByIdResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | @Builder 10 | @Getter 11 | public class FreePostFindByIdResponse { 12 | @Schema(description = "작성자 아이디") 13 | private final Long memberId; 14 | 15 | @Schema(description = "자유게시글 아이디") 16 | private final Long freePostId; 17 | 18 | @Schema(description = "자유게시글 제목") 19 | private final String title; 20 | 21 | @Schema(description = "자유게시글 내용") 22 | private final String content; 23 | 24 | @Schema(description = "자유게시글 생성 시간") 25 | private final LocalDateTime createdAt; 26 | 27 | @Schema(description = "자유게시글 수정 시간") 28 | private final LocalDateTime modifiedAt; 29 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/dto/response/FreePostFindByPageResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.List; 9 | 10 | @Builder 11 | @Getter 12 | public class FreePostFindByPageResponse { 13 | @Schema(description = "자유게시글 전체 개수") 14 | private final Long totalCount; 15 | 16 | @Schema(description = "자유게시글 페이지 번호") 17 | private final int pageNumber; 18 | 19 | @Schema(description = "한 페이지에 가져올 자유게시글 수") 20 | private final int pageSize; 21 | 22 | @Schema(description = "자유게시글 조회 정렬 기준") 23 | private final String sort; 24 | 25 | @Schema(description = "자유게시글 전체 리스트") 26 | private final List freePosts; 27 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/dto/response/FreePostResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | @RequiredArgsConstructor 8 | @Getter 9 | public class FreePostResponse { 10 | @Schema(description = "자유게시글 id") 11 | private final Long id; 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/facade/FreePostFacade.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.facade; 2 | 3 | import kr.ac.hs.selab.free_post.application.FreePostService; 4 | import kr.ac.hs.selab.free_post.converter.FreePostConverter; 5 | import kr.ac.hs.selab.free_post.dto.FreePostFindByPageDto; 6 | import kr.ac.hs.selab.free_post.dto.request.FreePostRequest; 7 | import kr.ac.hs.selab.free_post.dto.response.FreePostFindByIdResponse; 8 | import kr.ac.hs.selab.free_post.dto.response.FreePostFindByPageResponse; 9 | import kr.ac.hs.selab.free_post.dto.response.FreePostResponse; 10 | import kr.ac.hs.selab.member.application.MemberService; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.data.domain.Pageable; 13 | import org.springframework.stereotype.Component; 14 | import org.springframework.transaction.annotation.Transactional; 15 | 16 | @RequiredArgsConstructor 17 | @Component 18 | public class FreePostFacade { 19 | private final FreePostService freePostService; 20 | private final MemberService memberService; 21 | 22 | @Transactional 23 | public FreePostResponse create(String memberEmail, FreePostRequest request) { 24 | var memberId = memberService.findByEmail(memberEmail).getId(); 25 | 26 | var dto = FreePostConverter.toFreePostCreateDto(memberId, request); 27 | var post = freePostService.create(dto); 28 | 29 | return new FreePostResponse(post.getId()); 30 | } 31 | 32 | @Transactional 33 | public FreePostFindByIdResponse findById(Long id) { 34 | var post = freePostService.findById(id); 35 | return FreePostConverter.toFreePostFindByIdResponse(post); 36 | } 37 | 38 | public FreePostFindByPageResponse findByPage(Pageable pageable) { 39 | var dto = FreePostFindByPageDto.builder() 40 | .totalCount(freePostService.count()) 41 | .pageable(pageable) 42 | .freePosts(freePostService.findByPage(pageable)) 43 | .build(); 44 | return FreePostConverter.toFreePostFindByPageResponse(dto); 45 | } 46 | 47 | @Transactional 48 | public FreePostResponse update(Long id, FreePostRequest request) { 49 | var dto = FreePostConverter.toFreePostUpdateDto(id, request); 50 | var post = freePostService.update(dto); 51 | return new FreePostResponse(post.getId()); 52 | } 53 | 54 | @Transactional 55 | public FreePostResponse delete(Long id) { 56 | var post = freePostService.delete(id); 57 | return new FreePostResponse(post.getId()); 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/infrastructure/FreePostCommentRepository.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.infrastructure; 2 | 3 | import kr.ac.hs.selab.free_post.domain.FreePostComment; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | public interface FreePostCommentRepository extends JpaRepository { 12 | Optional findByIdAndDeleteFlag(Long id, boolean deleteFlag); 13 | 14 | List findByFreePostIdAndDeleteFlag(Long freePostId, boolean deleteFlag); 15 | 16 | Page findByFreePostIdAndDeleteFlag(Long freePostId, boolean deleteFlag, Pageable pageable); 17 | 18 | Long countByFreePostIdAndDeleteFlag(Long freePostId, boolean deleteFlag); 19 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/free_post/infrastructure/FreePostRepository.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.infrastructure; 2 | 3 | import kr.ac.hs.selab.free_post.domain.FreePost; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | 8 | import java.util.Optional; 9 | 10 | public interface FreePostRepository extends JpaRepository { 11 | Long countByDeleteFlag(boolean deleteFlag); 12 | 13 | Page findByDeleteFlag(boolean deleteFlag, Pageable pageable); 14 | 15 | Optional findByIdAndDeleteFlag(Long id, boolean deleteFlag); 16 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/member/application/MemberService.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.member.application; 2 | 3 | import kr.ac.hs.selab.error.exception.common.DuplicationException; 4 | import kr.ac.hs.selab.error.exception.common.NonExitsException; 5 | import kr.ac.hs.selab.error.template.ErrorMessage; 6 | import kr.ac.hs.selab.member.domain.Member; 7 | import kr.ac.hs.selab.member.dto.bundle.MemberCreateBundle; 8 | import kr.ac.hs.selab.member.infrastructure.MemberRepository; 9 | import lombok.RequiredArgsConstructor; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | @Service 14 | @RequiredArgsConstructor 15 | public class MemberService { 16 | 17 | private final MemberRepository memberRepository; 18 | 19 | @Transactional 20 | public Member save(Member member) { 21 | return memberRepository.save(member); 22 | } 23 | 24 | public Member findByEmail(String email) { 25 | return memberRepository.findByEmail(email) 26 | .orElseThrow(() -> new NonExitsException(ErrorMessage.MEMBER_NOT_EXISTS_ERROR)); 27 | } 28 | 29 | public void isDuplication(MemberCreateBundle bundle) { 30 | if (existsByEmail(bundle.getEmail())) { 31 | throw new DuplicationException(ErrorMessage.MEMBER_EMAIL_DUPLICATION_ERROR); 32 | } 33 | if (existsByStudentId(bundle.getStudentId())) { 34 | throw new DuplicationException(ErrorMessage.MEMBER_STUDENT_ID_DUPLICATION_ERROR); 35 | } 36 | if (existsByNickname(bundle.getNickname())) { 37 | throw new DuplicationException(ErrorMessage.MEMBER_NICKNAME_DUPLICATION_ERROR); 38 | } 39 | } 40 | 41 | public boolean existsByEmail(String email) { 42 | return memberRepository.existsByEmail(email); 43 | } 44 | 45 | private boolean existsByStudentId(String studentId) { 46 | return memberRepository.existsByStudentId(studentId); 47 | } 48 | 49 | private boolean existsByNickname(String nickname) { 50 | return memberRepository.existsByNickname(nickname); 51 | } 52 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/member/converter/MemberConverter.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.member.converter; 2 | 3 | import kr.ac.hs.selab.member.domain.Member; 4 | import kr.ac.hs.selab.member.domain.vo.Avatar; 5 | import kr.ac.hs.selab.member.domain.vo.Password; 6 | import kr.ac.hs.selab.member.dto.bundle.MemberCreateBundle; 7 | import kr.ac.hs.selab.member.dto.request.MemberCreateRequest; 8 | import kr.ac.hs.selab.member.dto.response.MemberCreateResponse; 9 | import org.springframework.security.crypto.password.PasswordEncoder; 10 | 11 | public class MemberConverter { 12 | 13 | public static MemberCreateBundle toCreateMemberBundle(MemberCreateRequest request) { 14 | return MemberCreateBundle.builder() 15 | .email(request.getEmail()) 16 | .password(new Password(request.getPassword())) 17 | .studentId(request.getStudentId()) 18 | .name(request.getName()) 19 | .nickname(request.getNickname()) 20 | .avatar(new Avatar(request.getAvatar())) 21 | .build(); 22 | } 23 | 24 | public static Member toMember(MemberCreateBundle bundle, PasswordEncoder passwordEncoder) { 25 | return Member.builder() 26 | .email(bundle.getEmail()) 27 | .password(bundle.getPassword().encode(passwordEncoder)) 28 | .studentId(bundle.getStudentId()) 29 | .name(bundle.getName()) 30 | .nickname(bundle.getNickname()) 31 | .avatar(bundle.getAvatar()) 32 | .build(); 33 | } 34 | 35 | public static MemberCreateResponse toCreateMemberResponse(Member member) { 36 | return new MemberCreateResponse(member.getEmail()); 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/member/domain/Member.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.member.domain; 2 | 3 | import kr.ac.hs.selab.common.domain.BaseEntity; 4 | import kr.ac.hs.selab.member.domain.vo.Avatar; 5 | import kr.ac.hs.selab.member.domain.vo.Password; 6 | import kr.ac.hs.selab.member.domain.vo.Role; 7 | import lombok.AccessLevel; 8 | import lombok.Builder; 9 | import lombok.Getter; 10 | import lombok.NoArgsConstructor; 11 | import org.springframework.security.core.GrantedAuthority; 12 | 13 | import javax.persistence.Column; 14 | import javax.persistence.Embedded; 15 | import javax.persistence.Entity; 16 | import javax.persistence.EnumType; 17 | import javax.persistence.Enumerated; 18 | import javax.persistence.GeneratedValue; 19 | import javax.persistence.GenerationType; 20 | import javax.persistence.Id; 21 | import java.util.Collection; 22 | import java.util.Collections; 23 | 24 | @Entity 25 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 26 | public class Member extends BaseEntity { 27 | 28 | @Getter 29 | @Id 30 | @Column(name = "member_id") 31 | @GeneratedValue(strategy = GenerationType.IDENTITY) 32 | private Long id; 33 | 34 | @Getter 35 | @Column(name = "member_email", unique = true) 36 | private String email; 37 | 38 | @Embedded 39 | private Password password; 40 | 41 | @Column(name = "member_student_id", unique = true) 42 | private String studentId; 43 | 44 | @Column(name = "member_name") 45 | private String name; 46 | 47 | @Getter 48 | @Column(name = "member_nickname", unique = true) 49 | private String nickname; 50 | 51 | @Column(name = "member_avatar") 52 | private Avatar avatar; 53 | 54 | @Column(name = "member_role") 55 | @Enumerated(EnumType.STRING) 56 | private Role role; 57 | 58 | @Builder 59 | private Member(String email, Password password, String studentId, String name, 60 | String nickname, Avatar avatar) { 61 | this.email = email; 62 | this.password = password; 63 | this.studentId = studentId; 64 | this.name = name; 65 | this.nickname = nickname; 66 | this.avatar = avatar; 67 | this.role = Role.USER; 68 | } 69 | 70 | public String getPasswordValue() { 71 | return password.getPassword(); 72 | } 73 | 74 | public Collection getAuthority() { 75 | return Collections 76 | .singletonList(role.getGrantedAuthority()); 77 | } 78 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/member/domain/vo/Avatar.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.member.domain.vo; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Embeddable; 5 | import javax.persistence.Transient; 6 | 7 | import lombok.AccessLevel; 8 | import lombok.NoArgsConstructor; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.util.StringUtils; 11 | 12 | @Embeddable 13 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 14 | public class Avatar { 15 | 16 | // TODO : DEFAULT_IMAGE_URL 교체 작업 필요, PROPERTIES로 관리하자! 17 | @Transient 18 | private String DEFAULT_IMAGE_URL = "https://cloudfront-ap-northeast-1.images.arcpublishing.com/chosun/2TKUKXYMQF7ASZEUJLG7L4GM4I.jpg"; 19 | 20 | @Column(name = "member_avatar") 21 | private String avatar; 22 | 23 | public Avatar(String avatar) { 24 | this.avatar = validate(avatar); 25 | } 26 | 27 | private String validate(String avatar) { 28 | if (StringUtils.hasText(avatar)) { 29 | return avatar; 30 | } 31 | return DEFAULT_IMAGE_URL; 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/member/domain/vo/Password.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.member.domain.vo; 2 | 3 | import kr.ac.hs.selab.error.exception.common.InvalidArgumentException; 4 | import kr.ac.hs.selab.error.template.ErrorMessage; 5 | import lombok.AccessLevel; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import org.springframework.security.crypto.password.PasswordEncoder; 9 | 10 | import javax.persistence.Column; 11 | import javax.persistence.Embeddable; 12 | import javax.persistence.Transient; 13 | import java.util.regex.Pattern; 14 | 15 | @Getter 16 | @Embeddable 17 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 18 | public class Password { 19 | 20 | /** 21 | * 최소 8자 ~ 최대 30자 최소 영문자 1자 최소 숫자 1자 최소 특수문자 1자 :: $@$!%*#?& ^(?=.*[~!@#$%^&*()_+`\-=\[\]\{\};':\",.\<>?])(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])\S{8,30}$ 22 | */ 23 | @Transient 24 | private static final String PASSWORD_REGEX = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[$@$!%*#?&])[가-힣A-Za-z\\d$@$!%*#?&]{8,30}$"; 25 | 26 | @Column(name = "member_password") 27 | private String password; 28 | 29 | public Password(String password) { 30 | validate(password); 31 | this.password = password; 32 | } 33 | 34 | // TODO ErrorMessage 변경 35 | // Error가 전부 Detail 해보이는데, 각 하나씩 구현하기보다는, 올바르지 않은 사용자 값 : {} 36 | // 이런식의 input값 에러메세지가 통일되는게 좋아보입니다. 37 | private void validate(String password) { 38 | if (!Pattern.matches(PASSWORD_REGEX, password)) { 39 | throw new InvalidArgumentException( 40 | ErrorMessage.MEMBER_PASSWORD_INVALID_ARGUMENT_ERROR); 41 | } 42 | } 43 | 44 | // TODO Password Encoder DI 변경 45 | // Spring Converter를 통해서 받는 것이 좋습니다! 46 | public Password encode(PasswordEncoder passwordEncoder) { 47 | this.password = passwordEncoder.encode(this.password); 48 | return this; 49 | } 50 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/member/domain/vo/Role.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.member.domain.vo; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.security.core.GrantedAuthority; 6 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 7 | 8 | import java.util.Arrays; 9 | 10 | @Getter 11 | @RequiredArgsConstructor 12 | public enum Role { 13 | ADMIN(new SimpleGrantedAuthority("ROLE_ADMIN"), "관리자"), 14 | MIDDLE_ADMIN(new SimpleGrantedAuthority("ROLE_MIDDLE_ADMIN"), "중간 관리자"), 15 | USER(new SimpleGrantedAuthority("ROLE_USER"), "일반 사용자"), 16 | GUEST(new SimpleGrantedAuthority("ROLE_GUEST"), "게스트"); 17 | 18 | private final GrantedAuthority grantedAuthority; 19 | private final String description; 20 | 21 | public static Role of(String description) { 22 | return Arrays.stream(Role.values()) 23 | .filter(r -> r.isEquals(description)) 24 | .findAny() 25 | .orElse(GUEST); 26 | } 27 | 28 | private boolean isEquals(String description) { 29 | return this.getDescription().equals(description); 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/member/dto/bundle/MemberCreateBundle.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.member.dto.bundle; 2 | 3 | import kr.ac.hs.selab.member.domain.vo.Avatar; 4 | import kr.ac.hs.selab.member.domain.vo.Password; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.RequiredArgsConstructor; 8 | 9 | @Getter 10 | @Builder 11 | @RequiredArgsConstructor 12 | public class MemberCreateBundle { 13 | 14 | private final String email; 15 | private final Password password; 16 | private final String studentId; 17 | private final String name; 18 | private final String nickname; 19 | private final Avatar avatar; 20 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/member/dto/request/MemberCreateRequest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.member.dto.request; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.validation.constraints.*; 8 | 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Getter 12 | public class MemberCreateRequest { 13 | @NotNull 14 | @Email 15 | private String email; 16 | 17 | @NotBlank 18 | private String password; 19 | 20 | @NotBlank 21 | private String studentId; 22 | 23 | @Pattern(regexp = "^[가-힣]{2,10}$") 24 | private String name; 25 | 26 | @NotBlank 27 | @Size(max = 15) 28 | private String nickname; 29 | 30 | private String avatar; 31 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/member/dto/request/MemberExistRequest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.member.dto.request; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.validation.constraints.Email; 8 | 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Getter 12 | public class MemberExistRequest { 13 | @Email(message = "이메일 양식이 아닙니다.") 14 | private String memberEmail; 15 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/member/dto/response/MemberCreateResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.member.dto.response; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @Getter 7 | @RequiredArgsConstructor 8 | public class MemberCreateResponse { 9 | 10 | private final String email; 11 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/member/dto/response/MemberExistResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.member.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Value; 5 | 6 | @Value(staticConstructor = "of") 7 | public class MemberExistResponse { 8 | @Schema(description = "회원 가입 여부") 9 | boolean isSignup; 10 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/member/facade/MemberFacade.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.member.facade; 2 | 3 | import kr.ac.hs.selab.member.application.MemberService; 4 | import kr.ac.hs.selab.member.converter.MemberConverter; 5 | import kr.ac.hs.selab.member.dto.request.MemberCreateRequest; 6 | import kr.ac.hs.selab.member.dto.request.MemberExistRequest; 7 | import kr.ac.hs.selab.member.dto.response.MemberCreateResponse; 8 | import kr.ac.hs.selab.member.dto.response.MemberExistResponse; 9 | import kr.ac.hs.selab.terms.application.TermsService; 10 | import lombok.RequiredArgsConstructor; 11 | import org.springframework.security.crypto.password.PasswordEncoder; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.transaction.annotation.Transactional; 14 | 15 | @Transactional(readOnly = true) 16 | @RequiredArgsConstructor 17 | @Component 18 | public class MemberFacade { 19 | private final MemberService memberService; 20 | private final PasswordEncoder passwordEncoder; 21 | private final TermsService termsService; 22 | 23 | @Transactional 24 | public void sign(MemberCreateRequest request) { 25 | var bundle = MemberConverter.toCreateMemberBundle(request); 26 | memberService.isDuplication(bundle); 27 | 28 | var instance = MemberConverter.toMember(bundle, passwordEncoder); 29 | var member = memberService.save(instance); 30 | 31 | termsService.sign(member.getId()); 32 | } 33 | 34 | public MemberExistResponse exist(MemberExistRequest request) { 35 | var isSignup = memberService.existsByEmail(request.getMemberEmail()); 36 | return MemberExistResponse.of(isSignup); 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/member/infrastructure/MemberRepository.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.member.infrastructure; 2 | 3 | import kr.ac.hs.selab.member.domain.Member; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | import org.springframework.transaction.annotation.Transactional; 7 | 8 | import java.util.Optional; 9 | 10 | @Repository 11 | public interface MemberRepository extends JpaRepository { 12 | 13 | @Transactional(readOnly = true) 14 | boolean existsByEmail(String email); 15 | 16 | @Transactional(readOnly = true) 17 | boolean existsByStudentId(String studentId); 18 | 19 | @Transactional(readOnly = true) 20 | boolean existsByNickname(String nickname); 21 | 22 | @Transactional(readOnly = true) 23 | Optional findByEmail(String email); 24 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/member/presentation/MemberController.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.member.presentation; 2 | 3 | import kr.ac.hs.selab.common.template.ResponseMessage; 4 | import kr.ac.hs.selab.common.template.ResponseTemplate; 5 | import kr.ac.hs.selab.member.dto.request.MemberCreateRequest; 6 | import kr.ac.hs.selab.member.dto.request.MemberExistRequest; 7 | import kr.ac.hs.selab.member.dto.response.MemberCreateResponse; 8 | import kr.ac.hs.selab.member.dto.response.MemberExistResponse; 9 | import kr.ac.hs.selab.member.facade.MemberFacade; 10 | import lombok.RequiredArgsConstructor; 11 | import org.springframework.http.MediaType; 12 | import org.springframework.validation.annotation.Validated; 13 | import org.springframework.web.bind.annotation.PostMapping; 14 | import org.springframework.web.bind.annotation.RequestBody; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RestController; 17 | 18 | @RequiredArgsConstructor 19 | @RestController 20 | @RequestMapping(value = "/api/v1/members") 21 | public class MemberController implements MemberSdk { 22 | private final MemberFacade memberFacade; 23 | 24 | @Override 25 | @PostMapping(value = "/sign", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) 26 | public ResponseTemplate create(@Validated @RequestBody MemberCreateRequest request) { 27 | memberFacade.sign(request); 28 | return ResponseTemplate.noContent(ResponseMessage.MEMBER_CREATE_SUCCESS); 29 | } 30 | 31 | @Override 32 | @PostMapping(value = "/exist") 33 | public ResponseTemplate exist(@Validated @RequestBody MemberExistRequest request) { 34 | var response = memberFacade.exist(request); 35 | return ResponseTemplate.ok(ResponseMessage.MEMBER_CHECK_SUCCESS, response); 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/member/presentation/MemberSdk.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.member.presentation; 2 | 3 | import io.swagger.annotations.Api; 4 | import io.swagger.annotations.ApiOperation; 5 | import io.swagger.annotations.ApiResponse; 6 | import io.swagger.annotations.ApiResponses; 7 | import io.swagger.v3.oas.annotations.Parameter; 8 | import kr.ac.hs.selab.common.template.ResponseTemplate; 9 | import kr.ac.hs.selab.common.template.SwaggerNote; 10 | import kr.ac.hs.selab.member.dto.request.MemberCreateRequest; 11 | import kr.ac.hs.selab.member.dto.request.MemberExistRequest; 12 | import kr.ac.hs.selab.member.dto.response.MemberCreateResponse; 13 | import kr.ac.hs.selab.member.dto.response.MemberExistResponse; 14 | 15 | @Api(tags = "회원 API", description = "Member Controller (MVP)") 16 | public interface MemberSdk { 17 | @ApiOperation(value = "회원가입", notes = SwaggerNote.MEMBER_CREATE) 18 | @ApiResponses( 19 | @ApiResponse(code = 201, message = "회원가입 성공") 20 | ) 21 | ResponseTemplate create(@Parameter(description = "회원가입 시 필요한 정보") MemberCreateRequest request); 22 | 23 | @ApiOperation(value = "회원가입 여부 조회", notes = SwaggerNote.MEMBER_EXIST) 24 | @ApiResponses( 25 | @ApiResponse(code = 201, message = "회원여부 조회 성공") 26 | ) 27 | ResponseTemplate exist(@Parameter(description = "회원 가입 여부를 확인할 이메일") MemberExistRequest request); 28 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/application/NoticeLikeService.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.application; 2 | 3 | import kr.ac.hs.selab.notice.domain.NoticeLike; 4 | import kr.ac.hs.selab.notice.dto.NoticeLikeDto; 5 | import kr.ac.hs.selab.notice.infrastructure.NoticeLikeRepository; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import java.util.List; 11 | 12 | @RequiredArgsConstructor 13 | @Transactional(readOnly = true) 14 | @Service 15 | public class NoticeLikeService { 16 | private final NoticeLikeRepository noticeLikeRepository; 17 | 18 | @Transactional 19 | public NoticeLike create(NoticeLikeDto dto) { 20 | return noticeLikeRepository.save( 21 | new NoticeLike(dto.getMemberId(), dto.getNoticeId()) 22 | ); 23 | } 24 | 25 | public List find(Long noticeId) { 26 | return noticeLikeRepository.findByNoticeId(noticeId); 27 | } 28 | 29 | @Transactional 30 | public void deleteByNoticeLikeId(Long noticeLikeId) { 31 | noticeLikeRepository.deleteById(noticeLikeId); 32 | } 33 | 34 | @Transactional 35 | public void deleteByNoticeId(Long noticeId) { 36 | noticeLikeRepository.deleteAll(noticeLikeRepository.findByNoticeId(noticeId)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/application/NoticeService.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.application; 2 | 3 | import kr.ac.hs.selab.common.utils.Constants; 4 | import kr.ac.hs.selab.error.exception.common.NonExitsException; 5 | import kr.ac.hs.selab.error.template.ErrorMessage; 6 | import kr.ac.hs.selab.notice.converter.NoticeConverter; 7 | import kr.ac.hs.selab.notice.domain.Notice; 8 | import kr.ac.hs.selab.notice.dto.NoticeCreateDto; 9 | import kr.ac.hs.selab.notice.dto.NoticeUpdateDto; 10 | import kr.ac.hs.selab.notice.infrastructure.NoticeRepository; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.data.domain.Page; 13 | import org.springframework.data.domain.Pageable; 14 | import org.springframework.stereotype.Service; 15 | import org.springframework.transaction.annotation.Transactional; 16 | 17 | @RequiredArgsConstructor 18 | @Transactional(readOnly = true) 19 | @Service 20 | public class NoticeService { 21 | private final NoticeRepository noticeRepository; 22 | 23 | @Transactional 24 | public Notice create(NoticeCreateDto dto) { 25 | return noticeRepository.save(NoticeConverter.toNotice(dto)); 26 | } 27 | 28 | public Long count() { 29 | return noticeRepository.countByDeleteFlag(Constants.NOT_DELETED); 30 | } 31 | 32 | public Notice findById(Long id) { 33 | return noticeRepository.findByIdAndDeleteFlag(id, Constants.NOT_DELETED) 34 | .orElseThrow(() -> new NonExitsException(ErrorMessage.NOTICE_NOT_EXISTS_ERROR)); 35 | } 36 | 37 | public Page findByPage(Pageable pageable) { 38 | return noticeRepository.findByDeleteFlag(Constants.NOT_DELETED, pageable); 39 | } 40 | 41 | @Transactional 42 | public Notice update(NoticeUpdateDto dto) { 43 | return findById(dto.getId()).update(dto.getTitle(), dto.getContent()); 44 | } 45 | 46 | @Transactional 47 | public Notice delete(Long id) { 48 | return findById(id).delete(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/converter/NoticeConverter.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.converter; 2 | 3 | import kr.ac.hs.selab.notice.domain.Notice; 4 | import kr.ac.hs.selab.notice.dto.NoticeCreateDto; 5 | import kr.ac.hs.selab.notice.dto.NoticeFindByPageDto; 6 | import kr.ac.hs.selab.notice.dto.NoticeUpdateDto; 7 | import kr.ac.hs.selab.notice.dto.request.NoticeRequest; 8 | import kr.ac.hs.selab.notice.dto.response.NoticeFindByIdResponse; 9 | import kr.ac.hs.selab.notice.dto.response.NoticeFindByPageResponse; 10 | import lombok.experimental.UtilityClass; 11 | 12 | import java.util.stream.Collectors; 13 | 14 | @UtilityClass 15 | public class NoticeConverter { 16 | public Notice toNotice(NoticeCreateDto dto) { 17 | return Notice.builder() 18 | .memberId(dto.getMemberId()) 19 | .title(dto.getTitle()) 20 | .content(dto.getContent()) 21 | .build(); 22 | } 23 | 24 | public NoticeFindByIdResponse toNoticeFindByIdResponse(Notice notice) { 25 | return NoticeFindByIdResponse.builder() 26 | .noticeId(notice.getId()) 27 | .memberId(notice.getMemberId()) 28 | .title(notice.getTitle()) 29 | .content(notice.getContent()) 30 | .createdAt(notice.getCreatedAt()) 31 | .modifiedAt(notice.getModifiedAt()) 32 | .build(); 33 | } 34 | 35 | public NoticeFindByPageResponse toNoticeFindByPageResponse(NoticeFindByPageDto dto) { 36 | var responses = dto.getNotices() 37 | .stream() 38 | .map(NoticeConverter::toNoticeFindByIdResponse) 39 | .collect(Collectors.toList()); 40 | 41 | return NoticeFindByPageResponse.builder() 42 | .totalCount(dto.getTotalCount()) 43 | .pageNumber(dto.getPageable().getPageNumber()) 44 | .pageSize(dto.getPageable().getPageSize()) 45 | .sort(dto.getPageable().getSort().toString()) 46 | .notices(responses) 47 | .build(); 48 | } 49 | 50 | public NoticeCreateDto toNoticeCreateDto(Long memberId, NoticeRequest request) { 51 | return NoticeCreateDto.builder() 52 | .memberId(memberId) 53 | .title(request.getTitle()) 54 | .content(request.getContent()) 55 | .build(); 56 | } 57 | 58 | public NoticeUpdateDto toNoticeUpdateDto(Long id, NoticeRequest request) { 59 | return NoticeUpdateDto.builder() 60 | .id(id) 61 | .title(request.getTitle()) 62 | .content(request.getContent()) 63 | .build(); 64 | } 65 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/converter/NoticeLikeConverter.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.converter; 2 | 3 | import kr.ac.hs.selab.notice.domain.NoticeLike; 4 | import kr.ac.hs.selab.notice.dto.response.NoticeLikeFindResponse; 5 | import lombok.experimental.UtilityClass; 6 | 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | @UtilityClass 11 | public class NoticeLikeConverter { 12 | public NoticeLikeFindResponse toNoticeLikeFindResponse(Long noticeId, List likes) { 13 | return NoticeLikeFindResponse.builder() 14 | .noticeId(noticeId) 15 | .totalCount((long) likes.size()) 16 | .noticeLikes( 17 | likes.stream() 18 | .map(NoticeLikeConverter::toNoticeLikeInnerResponse) 19 | .collect(Collectors.toList()) 20 | ) 21 | .build(); 22 | } 23 | 24 | private NoticeLikeFindResponse.NoticeLikeInnerResponse toNoticeLikeInnerResponse(NoticeLike like) { 25 | return new NoticeLikeFindResponse.NoticeLikeInnerResponse(like.getId(), like.getMemberId()); 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/domain/Notice.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.domain; 2 | 3 | import kr.ac.hs.selab.common.domain.BaseEntity; 4 | import lombok.AccessLevel; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.persistence.Column; 10 | import javax.persistence.Entity; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.GenerationType; 13 | import javax.persistence.Id; 14 | 15 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 16 | @Getter 17 | @Entity 18 | public class Notice extends BaseEntity { 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | @Column(name = "notice_id", nullable = false) 22 | private Long id; 23 | 24 | @Column(name = "member_id", nullable = false) 25 | private Long memberId; 26 | 27 | @Column(name = "notice_title", nullable = false) 28 | private String title; 29 | 30 | @Column(name = "notice_content", nullable = false) 31 | private String content; 32 | 33 | @Column(name = "notice_delete_flag", nullable = false) 34 | private boolean deleteFlag; 35 | 36 | @Builder 37 | private Notice(Long memberId, String title, String content) { 38 | this.title = title; 39 | this.memberId = memberId; 40 | this.content = content; 41 | this.deleteFlag = false; 42 | } 43 | 44 | public Notice update(String title, String content) { 45 | this.title = title; 46 | this.content = content; 47 | return this; 48 | } 49 | 50 | public Notice delete() { 51 | this.deleteFlag = true; 52 | return this; 53 | } 54 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/domain/NoticeComment.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.domain; 2 | 3 | import kr.ac.hs.selab.common.domain.BaseEntity; 4 | import lombok.AccessLevel; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.persistence.Column; 10 | import javax.persistence.Entity; 11 | import javax.persistence.GeneratedValue; 12 | import javax.persistence.GenerationType; 13 | import javax.persistence.Id; 14 | 15 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 16 | @Getter 17 | @Entity 18 | public class NoticeComment extends BaseEntity { 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | @Column(name = "notice_comment_id", nullable = false) 22 | private Long id; 23 | 24 | @Column(name = "member_id", nullable = false) 25 | private Long memberId; 26 | 27 | @Column(name = "notice_id", nullable = false) 28 | private Long noticeId; 29 | 30 | @Column(name = "notice_comment_content", nullable = false) 31 | private String content; 32 | 33 | @Column(name = "notice_comment_delete_flag", nullable = false) 34 | private boolean deleteFlag; 35 | 36 | @Builder 37 | public NoticeComment(Long memberId, Long noticeId, String content) { 38 | this.memberId = memberId; 39 | this.noticeId = noticeId; 40 | this.content = content; 41 | this.deleteFlag = false; 42 | } 43 | 44 | public NoticeComment update(String content) { 45 | this.content = content; 46 | return this; 47 | } 48 | 49 | public NoticeComment delete() { 50 | this.deleteFlag = true; 51 | return this; 52 | } 53 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/domain/NoticeLike.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.domain; 2 | 3 | import kr.ac.hs.selab.common.domain.BaseEntity; 4 | import lombok.AccessLevel; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.GenerationType; 12 | import javax.persistence.Id; 13 | import javax.persistence.Table; 14 | import javax.persistence.UniqueConstraint; 15 | 16 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 17 | @Getter 18 | @Entity 19 | @Table( 20 | name = "notice_like", 21 | uniqueConstraints = { 22 | @UniqueConstraint( 23 | columnNames = {"member_id", "notice_id"} 24 | ) 25 | } 26 | ) 27 | public class NoticeLike extends BaseEntity { 28 | @Id 29 | @GeneratedValue(strategy = GenerationType.IDENTITY) 30 | @Column(name = "notice_like_id", nullable = false) 31 | private Long id; 32 | 33 | @Column(name = "member_id", nullable = false) 34 | private Long memberId; 35 | 36 | @Column(name = "notice_id", nullable = false) 37 | private Long noticeId; 38 | 39 | public NoticeLike(Long memberId, Long noticeId) { 40 | this.memberId = memberId; 41 | this.noticeId = noticeId; 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/dto/NoticeCommentCreateDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.dto; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | @Builder 7 | @Getter 8 | public class NoticeCommentCreateDto { 9 | private final Long memberId; 10 | private final Long noticeId; 11 | private final String noticeCommentContent; 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/dto/NoticeCommentFindByNoticeIdAndPageDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import org.springframework.data.domain.Pageable; 6 | 7 | @RequiredArgsConstructor 8 | @Getter 9 | public class NoticeCommentFindByNoticeIdAndPageDto { 10 | private final Long noticeId; 11 | private final Pageable pageable; 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/dto/NoticeCommentUpdateDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | @Getter 8 | public class NoticeCommentUpdateDto { 9 | private final Long noticeCommentId; 10 | private final String noticeCommentContent; 11 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/dto/NoticeCreateDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.dto; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | @Builder 7 | @Getter 8 | public class NoticeCreateDto { 9 | private final Long memberId; 10 | private final String title; 11 | private final String content; 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/dto/NoticeFindByPageDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.dto; 2 | 3 | import kr.ac.hs.selab.notice.domain.Notice; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.data.domain.Pageable; 8 | 9 | @Builder 10 | @Getter 11 | public class NoticeFindByPageDto { 12 | private final Long totalCount; 13 | private final Pageable pageable; 14 | private final Page notices; 15 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/dto/NoticeLikeDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | @Getter 8 | public class NoticeLikeDto { 9 | private final Long memberId; 10 | private final Long noticeId; 11 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/dto/NoticeLikeFindDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.dto; 2 | 3 | import kr.ac.hs.selab.notice.domain.NoticeLike; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | import java.util.List; 8 | 9 | @RequiredArgsConstructor 10 | @Getter 11 | public class NoticeLikeFindDto { 12 | private final Long noticeId; 13 | private final List noticeLikes; 14 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/dto/NoticeUpdateDto.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.dto; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | 6 | @Builder 7 | @Getter 8 | public class NoticeUpdateDto { 9 | private final Long id; 10 | private final String title; 11 | private final String content; 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/dto/request/NoticeCommentRequest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.dto.request; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.validation.constraints.NotBlank; 9 | 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Getter 13 | public class NoticeCommentRequest { 14 | @Schema(description = "공지사항 댓글 내용") 15 | @NotBlank(message = "공지사항의 댓글에 내용을 작성해야합니다.") 16 | private String noticeCommentContent; 17 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/dto/request/NoticeRequest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.dto.request; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.validation.constraints.NotBlank; 9 | import javax.validation.constraints.Size; 10 | 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Getter 14 | public class NoticeRequest { 15 | @Schema(description = "공지사항 제목") 16 | @Size(max = 30, message = "공지사항 제목이 너무 깁니다.") 17 | private String title; 18 | 19 | @Schema(description = "공지사항 내용") 20 | @NotBlank(message = "공지사항 내용을 입력해 주세요.") 21 | private String content; 22 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/dto/response/NoticeCommentFindByNoticeIdAndPageResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | import java.time.LocalDateTime; 8 | import java.util.List; 9 | 10 | @Builder 11 | @Getter 12 | public class NoticeCommentFindByNoticeIdAndPageResponse { 13 | @Schema(description = "공지사항 id") 14 | private final Long noticeId; 15 | 16 | @Schema(description = "공지사항 댓글 전체 개수") 17 | private final Long totalCount; 18 | 19 | @Schema(description = "댓글 페이지 넘버") 20 | private final int pageNumber; 21 | 22 | @Schema(description = "한 페이지에 가져올 댓글 수") 23 | private final int pageSize; 24 | 25 | @Schema(description = "댓글 정렬 기준") 26 | private final String sort; 27 | 28 | @Schema(description = "공지사항의 댓글 전체 목록") 29 | private final List noticeComments; 30 | 31 | @Builder 32 | @Getter 33 | public static class InnerResponse { 34 | @Schema(description = "작성자 id") 35 | private final Long memberId; 36 | 37 | @Schema(description = "공지사항의 댓글 id") 38 | private final Long noticeCommentId; 39 | 40 | @Schema(description = "공지사항의 댓글 내용") 41 | private final String noticeCommentContent; 42 | 43 | @Schema(description = "공지사항의 댓글 생성 시간") 44 | private final LocalDateTime createdAt; 45 | 46 | @Schema(description = "공지사항의 댓글 수정 시간") 47 | private final LocalDateTime modifiedAt; 48 | } 49 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/dto/response/NoticeCommentFindResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | @Builder 10 | @Getter 11 | public class NoticeCommentFindResponse { 12 | @Schema(description = "공지사항 id") 13 | private final Long noticeId; 14 | 15 | @Schema(description = "작성자 id") 16 | private final Long memberId; 17 | 18 | @Schema(description = "공지사항의 댓글 id") 19 | private final Long noticeCommentId; 20 | 21 | @Schema(description = "공지사항의 댓글 내용") 22 | private final String noticeCommentContent; 23 | 24 | @Schema(description = "공지사항의 댓글 생성 시간") 25 | private final LocalDateTime createdAt; 26 | 27 | @Schema(description = "공지사항의 댓글 수정 시간") 28 | private final LocalDateTime modifiedAt; 29 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/dto/response/NoticeCommentResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.dto.response; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | 6 | @RequiredArgsConstructor 7 | @Getter 8 | public class NoticeCommentResponse { 9 | private final Long noticeCommentId; 10 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/dto/response/NoticeFindByIdResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | import java.time.LocalDateTime; 8 | 9 | @Builder 10 | @Getter 11 | public class NoticeFindByIdResponse { 12 | @Schema(description = "작성자 아이디") 13 | private final Long memberId; 14 | 15 | @Schema(description = "공지사항 아이디") 16 | private final Long noticeId; 17 | 18 | @Schema(description = "공지사항 제목") 19 | private final String title; 20 | 21 | @Schema(description = "공지사항 내용") 22 | private final String content; 23 | 24 | @Schema(description = "공지사항 생성 시간") 25 | private final LocalDateTime createdAt; 26 | 27 | @Schema(description = "공지사항 수정 시간") 28 | private final LocalDateTime modifiedAt; 29 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/dto/response/NoticeFindByPageResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | import java.util.List; 8 | 9 | @Builder 10 | @Getter 11 | public class NoticeFindByPageResponse { 12 | @Schema(description = "공지사항 전체 개수") 13 | private final Long totalCount; 14 | 15 | @Schema(description = "공지사항 페이지 번호") 16 | private final int pageNumber; 17 | 18 | @Schema(description = "한 페이지에 가져올 공지사항 수") 19 | private final int pageSize; 20 | 21 | @Schema(description = "공지사항 조회 정렬 기준") 22 | private final String sort; 23 | 24 | @Schema(description = "공지사항 전체 리스트") 25 | private final List notices; 26 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/dto/response/NoticeLikeFindResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | @Builder 11 | @Getter 12 | public class NoticeLikeFindResponse { 13 | @Schema(description = "공지사항 id") 14 | private final Long noticeId; 15 | 16 | @Schema(description = "공지사항 좋아요 전체 개수") 17 | private final Long totalCount; 18 | 19 | @Schema(description = "공지사항 좋아요 전체 목록") 20 | private final List noticeLikes; 21 | 22 | @RequiredArgsConstructor 23 | @Getter 24 | public static class NoticeLikeInnerResponse { 25 | @Schema(description = "공지사항 좋아요 id") 26 | private final Long id; 27 | 28 | @Schema(description = "공지사항 좋아요한 회원 id") 29 | private final Long memberId; 30 | } 31 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/dto/response/NoticeLikeResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | @RequiredArgsConstructor 8 | @Getter 9 | public class NoticeLikeResponse { 10 | @Schema(description = "공지사항 좋아요 id") 11 | private final Long id; 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/dto/response/NoticeResponse.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.dto.response; 2 | 3 | import io.swagger.v3.oas.annotations.media.Schema; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | @RequiredArgsConstructor 8 | @Getter 9 | public class NoticeResponse { 10 | @Schema(description = "공지사항 ID") 11 | private final Long id; 12 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/facade/NoticeFacade.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.facade; 2 | 3 | import kr.ac.hs.selab.member.application.MemberService; 4 | import kr.ac.hs.selab.notice.application.NoticeService; 5 | import kr.ac.hs.selab.notice.converter.NoticeConverter; 6 | import kr.ac.hs.selab.notice.dto.NoticeFindByPageDto; 7 | import kr.ac.hs.selab.notice.dto.request.NoticeRequest; 8 | import kr.ac.hs.selab.notice.dto.response.NoticeFindByIdResponse; 9 | import kr.ac.hs.selab.notice.dto.response.NoticeFindByPageResponse; 10 | import kr.ac.hs.selab.notice.dto.response.NoticeResponse; 11 | import lombok.RequiredArgsConstructor; 12 | import org.springframework.data.domain.Pageable; 13 | import org.springframework.stereotype.Component; 14 | import org.springframework.transaction.annotation.Transactional; 15 | 16 | @RequiredArgsConstructor 17 | @Component 18 | public class NoticeFacade { 19 | private final NoticeService noticeService; 20 | private final MemberService memberService; 21 | 22 | @Transactional 23 | public NoticeResponse create(String memberEmail, NoticeRequest request) { 24 | var memberId = memberService.findByEmail(memberEmail).getId(); 25 | 26 | var dto = NoticeConverter.toNoticeCreateDto(memberId, request); 27 | var notice = noticeService.create(dto); 28 | 29 | return new NoticeResponse(notice.getId()); 30 | } 31 | 32 | @Transactional 33 | public NoticeFindByIdResponse findById(Long id) { 34 | var notice = noticeService.findById(id); 35 | return NoticeConverter.toNoticeFindByIdResponse(notice); 36 | } 37 | 38 | public NoticeFindByPageResponse findByPage(Pageable pageable) { 39 | var dto = NoticeFindByPageDto.builder() 40 | .totalCount(noticeService.count()) 41 | .pageable(pageable) 42 | .notices(noticeService.findByPage(pageable)) 43 | .build(); 44 | return NoticeConverter.toNoticeFindByPageResponse(dto); 45 | } 46 | 47 | @Transactional 48 | public NoticeResponse update(Long noticeId, NoticeRequest request) { 49 | var dto = NoticeConverter.toNoticeUpdateDto(noticeId, request); 50 | var notice = noticeService.update(dto); 51 | return new NoticeResponse(notice.getId()); 52 | } 53 | 54 | @Transactional 55 | public NoticeResponse delete(Long id) { 56 | var notice = noticeService.delete(id); 57 | return new NoticeResponse(notice.getId()); 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/facade/NoticeLikeFacade.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.facade; 2 | 3 | import kr.ac.hs.selab.member.application.MemberService; 4 | import kr.ac.hs.selab.notice.application.NoticeLikeService; 5 | import kr.ac.hs.selab.notice.converter.NoticeLikeConverter; 6 | import kr.ac.hs.selab.notice.dto.NoticeLikeDto; 7 | import kr.ac.hs.selab.notice.dto.response.NoticeLikeFindResponse; 8 | import kr.ac.hs.selab.notice.dto.response.NoticeLikeResponse; 9 | import lombok.RequiredArgsConstructor; 10 | import org.springframework.stereotype.Component; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | @RequiredArgsConstructor 14 | @Transactional(readOnly = true) 15 | @Component 16 | public class NoticeLikeFacade { 17 | private final MemberService memberService; 18 | private final NoticeLikeService noticeLikeService; 19 | 20 | @Transactional 21 | public NoticeLikeResponse create(String memberEmail, Long noticeId) { 22 | var memberId = memberService.findByEmail(memberEmail).getId(); 23 | var dto = new NoticeLikeDto(memberId, noticeId); 24 | 25 | var like = noticeLikeService.create(dto); 26 | return new NoticeLikeResponse(like.getId()); 27 | } 28 | 29 | public NoticeLikeFindResponse find(Long noticeId) { 30 | var likes = noticeLikeService.find(noticeId); 31 | return NoticeLikeConverter.toNoticeLikeFindResponse(noticeId, likes); 32 | } 33 | 34 | @Transactional 35 | public NoticeLikeResponse delete(Long noticeId) { 36 | noticeLikeService.deleteByNoticeLikeId(noticeId); 37 | return new NoticeLikeResponse(noticeId); 38 | } 39 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/infrastructure/NoticeCommentRepository.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.infrastructure; 2 | 3 | import kr.ac.hs.selab.notice.domain.NoticeComment; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | public interface NoticeCommentRepository extends JpaRepository { 12 | Optional findByIdAndDeleteFlag(Long id, boolean deleteFlag); 13 | 14 | List findByNoticeIdAndDeleteFlag(Long noticeId, boolean deleteFlag); 15 | 16 | Page findByNoticeIdAndDeleteFlag(Long noticeId, boolean deleteFlag, Pageable pageable); 17 | 18 | Long countByNoticeIdAndDeleteFlag(Long noticeId, boolean deleteFlag); 19 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/infrastructure/NoticeLikeRepository.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.infrastructure; 2 | 3 | import kr.ac.hs.selab.notice.domain.NoticeLike; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.List; 7 | 8 | public interface NoticeLikeRepository extends JpaRepository { 9 | List findByNoticeId(Long noticeId); 10 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/infrastructure/NoticeRepository.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.infrastructure; 2 | 3 | import kr.ac.hs.selab.notice.domain.Notice; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | 8 | import java.util.Optional; 9 | 10 | public interface NoticeRepository extends JpaRepository { 11 | Long countByDeleteFlag(boolean deleteFlag); 12 | 13 | Page findByDeleteFlag(boolean deleteFlag, Pageable pageable); 14 | 15 | Optional findByIdAndDeleteFlag(Long id, boolean deleteFlag); 16 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/presentation/NoticeLikeController.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.presentation; 2 | 3 | import kr.ac.hs.selab.common.template.ResponseMessage; 4 | import kr.ac.hs.selab.common.template.ResponseTemplate; 5 | import kr.ac.hs.selab.common.utils.SecurityUtils; 6 | import kr.ac.hs.selab.notice.dto.response.NoticeLikeFindResponse; 7 | import kr.ac.hs.selab.notice.dto.response.NoticeLikeResponse; 8 | import kr.ac.hs.selab.notice.facade.NoticeLikeFacade; 9 | import lombok.RequiredArgsConstructor; 10 | import org.springframework.web.bind.annotation.DeleteMapping; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.PostMapping; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | @RequiredArgsConstructor 18 | @RequestMapping("/api/v1/notices") 19 | @RestController 20 | public class NoticeLikeController implements NoticeLikeSdk { 21 | private final NoticeLikeFacade noticeLikeFacade; 22 | 23 | @Override 24 | @PostMapping("/{noticeId}/likes") 25 | public ResponseTemplate create(@PathVariable Long noticeId) { 26 | var memberEmail = SecurityUtils.getCurrentUsername(); 27 | 28 | var response = noticeLikeFacade.create(memberEmail, noticeId); 29 | return ResponseTemplate.created(ResponseMessage.NOTICE_LIKE_CREATE_SUCCESS, response); 30 | } 31 | 32 | @Override 33 | @GetMapping("/{noticeId}/likes") 34 | public ResponseTemplate find(@PathVariable Long noticeId) { 35 | var response = noticeLikeFacade.find(noticeId); 36 | return ResponseTemplate.ok(ResponseMessage.NOTICE_LIKE_FIND_SUCCESS, response); 37 | } 38 | 39 | @Override 40 | @DeleteMapping("/likes/{noticeLikeId}") 41 | public ResponseTemplate delete(@PathVariable Long noticeLikeId) { 42 | var response = noticeLikeFacade.delete(noticeLikeId); 43 | return ResponseTemplate.ok(ResponseMessage.NOTICE_LIKE_DELETE_SUCCESS, response); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/notice/presentation/NoticeLikeSdk.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.presentation; 2 | 3 | import io.swagger.annotations.Api; 4 | import io.swagger.annotations.ApiOperation; 5 | import io.swagger.annotations.ApiResponse; 6 | import io.swagger.annotations.ApiResponses; 7 | import kr.ac.hs.selab.common.template.ResponseTemplate; 8 | import kr.ac.hs.selab.common.template.SwaggerNote; 9 | import kr.ac.hs.selab.notice.dto.response.NoticeLikeFindResponse; 10 | import kr.ac.hs.selab.notice.dto.response.NoticeLikeResponse; 11 | import org.springframework.web.bind.annotation.PathVariable; 12 | 13 | @Api(tags = "공지사항 좋아요 API", description = "Notice Like Controller (MPV)") 14 | public interface NoticeLikeSdk { 15 | @ApiOperation(value = "공지사항 좋아요 생성", notes = SwaggerNote.NOTICE_LIKE_CREATE) 16 | @ApiResponses({ 17 | @ApiResponse(code = 201, message = "공지사항 좋아요 성공"), 18 | @ApiResponse(code = 400, message = "공지사항 좋아요 실패") 19 | }) 20 | ResponseTemplate create(@PathVariable Long noticeId); 21 | 22 | @ApiOperation(value = "공지사항에 모든 좋아요 조회", notes = SwaggerNote.NOTICE_LIKE_FIND) 23 | @ApiResponses({ 24 | @ApiResponse(code = 201, message = "공지사항 좋아요 조회 성공"), 25 | @ApiResponse(code = 400, message = "공지사항 좋아요 조회 실패") 26 | }) 27 | ResponseTemplate find(@PathVariable Long noticeId); 28 | 29 | @ApiOperation(value = "공지사항 좋아요 삭제", notes = SwaggerNote.NOTICE_LIKE_DELETE) 30 | @ApiResponses({ 31 | @ApiResponse(code = 201, message = "공지사항 좋아요 삭제 성공"), 32 | @ApiResponse(code = 400, message = "공지사항 좋아요 삭제 실패") 33 | }) 34 | ResponseTemplate delete(@PathVariable Long noticeId); 35 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/terms/application/TermsService.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.terms.application; 2 | 3 | import kr.ac.hs.selab.terms.domain.Terms; 4 | import kr.ac.hs.selab.terms.domain.vo.Category; 5 | import kr.ac.hs.selab.terms.infrastructure.TermsRepository; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | @Service 11 | @RequiredArgsConstructor 12 | public class TermsService { 13 | private final TermsRepository termsRepository; 14 | 15 | @Transactional 16 | public void sign(Long memberId) { 17 | save(Category.PRIVACY, memberId); 18 | save(Category.SERVICE, memberId); 19 | } 20 | 21 | @Transactional 22 | public void save(Category category, Long memberId) { 23 | termsRepository.save(new Terms(category, memberId)); 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/terms/domain/Terms.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.terms.domain; 2 | 3 | import kr.ac.hs.selab.common.domain.BaseEntity; 4 | import kr.ac.hs.selab.terms.domain.vo.Category; 5 | import lombok.AccessLevel; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.Column; 9 | import javax.persistence.Entity; 10 | import javax.persistence.EnumType; 11 | import javax.persistence.Enumerated; 12 | import javax.persistence.GeneratedValue; 13 | import javax.persistence.GenerationType; 14 | import javax.persistence.Id; 15 | 16 | @Entity 17 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 18 | public class Terms extends BaseEntity { 19 | 20 | @Id 21 | @Column(name = "terms_id") 22 | @GeneratedValue(strategy = GenerationType.IDENTITY) 23 | private Long id; 24 | 25 | @Column(name = "terms_category") 26 | @Enumerated(EnumType.STRING) 27 | private Category category; 28 | 29 | @Column(name = "member_id") 30 | private Long memberId; 31 | 32 | public Terms(Category category, Long memberId) { 33 | this.category = category; 34 | this.memberId = memberId; 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/terms/domain/vo/Category.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.terms.domain.vo; 2 | 3 | import kr.ac.hs.selab.error.exception.common.NonExitsException; 4 | import kr.ac.hs.selab.error.template.ErrorMessage; 5 | import lombok.Getter; 6 | import lombok.RequiredArgsConstructor; 7 | 8 | import java.util.Arrays; 9 | 10 | @Getter 11 | @RequiredArgsConstructor 12 | public enum Category { 13 | SERVICE("서비스 정책"), 14 | PRIVACY("개인정보 정책"); 15 | 16 | private final String description; 17 | 18 | //TODO : EnumMap을 이용하여, cache를 하나 만들기 19 | public static Category of(String description) { 20 | return Arrays.stream(Category.values()) 21 | .filter(r -> r.isEquals(description)) 22 | .findAny() 23 | .orElseThrow(() -> new NonExitsException(ErrorMessage.TERMS_NOT_EXISTS_ERROR)); 24 | } 25 | 26 | private boolean isEquals(String description) { 27 | return this.getDescription().equals(description); 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/java/kr/ac/hs/selab/terms/infrastructure/TermsRepository.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.terms.infrastructure; 2 | 3 | import kr.ac.hs.selab.terms.domain.Terms; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface TermsRepository extends JpaRepository { 9 | } -------------------------------------------------------------------------------- /src/main/resources/application-local.properties: -------------------------------------------------------------------------------- 1 | # DDL(create, alter, drop) 2 | spring.jpa.hibernate.ddl-auto=create 3 | 4 | # JPA 5 | spring.jpa.properties.hibernate.format_sql=true 6 | 7 | # SHOW SQL 8 | spring.jpa.show-sql=true 9 | 10 | # Close Open Session In View Pattern 11 | spring.jpa.open-in-view=false 12 | 13 | # JPA Dialect 14 | spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 15 | 16 | # Database Setup 17 | spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver 18 | spring.datasource.url=jdbc:mysql://localhost:3306/selab?characterEncoding=UTF-8&serverTimezone=Asia/Seoul&allowMultiQueries=true&autoReconnection=true&characterEncoding=UTF-8 19 | spring.datasource.username=root 20 | spring.datasource.password= 21 | 22 | # CORS Properties 23 | cors.allowed-origins:http://localhost:3000 24 | cors.allowed-methods:GET,POST,PUT,PATCH,DELETE,OPTIONS 25 | cors.allowed-headers:* 26 | cors.max-age:3600 27 | cors.apply-url-range:/** 28 | 29 | # JWT 30 | jwt.issuer=selab 31 | jwt.secret=c2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQtc2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQK 32 | jwt.token-validity-in-seconds=86400 -------------------------------------------------------------------------------- /src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ${AnsiColor.BLUE} 2 | 3 | ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄ 4 | ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░▌ ▐░░░░░░░░░░░▌▐░░░░░░░░░░▌ 5 | ▐░█▀▀▀▀▀▀▀▀▀ ▐░█▀▀▀▀▀▀▀▀▀ ▐░▌ ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀█░▌ 6 | ▐░▌ ▐░▌ ▐░▌ ▐░▌ ▐░▌▐░▌ ▐░▌ 7 | ▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄▄▄ ▐░▌ ▐░█▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄█░▌ 8 | ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░▌ ▐░░░░░░░░░░░▌▐░░░░░░░░░░▌ 9 | ▀▀▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀▀▀ ▐░▌ ▐░█▀▀▀▀▀▀▀█░▌▐░█▀▀▀▀▀▀▀█░▌ 10 | ▐░▌▐░▌ ▐░▌ ▐░▌ ▐░▌▐░▌ ▐░▌ 11 | ▄▄▄▄▄▄▄▄▄█░▌▐░█▄▄▄▄▄▄▄▄▄ ▐░█▄▄▄▄▄▄▄▄▄ ▐░▌ ▐░▌▐░█▄▄▄▄▄▄▄█░▌ 12 | ▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░░░░░░░░░░░▌▐░▌ ▐░▌▐░░░░░░░░░░▌ 13 | ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀▀ ▀ ▀ ▀▀▀▀▀▀▀▀▀▀ 14 | 15 | ${AnsiColor.BRIGHT_GREEN}Spring Boot ${AnsiColor.RED}${spring-boot.formatted-version} 16 | ${AnsiColor.BRIGHT_GREEN}Made By KIM DONG GEON (wrjssmjdhappy@gmail.com) 17 | ${AnsiColor.BRIGHT_GREEN}Made By LEE HEE CHAN (leeheefull@gmail.com) 18 | 19 | ------------------------------------------------------------------------------------- 20 | ------------------------------------------------------------------------------------- -------------------------------------------------------------------------------- /src/test/java/kr/ac/hs/selab/SelabApplicationTests.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab; 2 | 3 | import org.springframework.boot.test.context.SpringBootTest; 4 | 5 | @SpringBootTest 6 | class SelabApplicationTests { 7 | } 8 | -------------------------------------------------------------------------------- /src/test/java/kr/ac/hs/selab/auth/application/UserServiceTest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.auth.application; 2 | 3 | import kr.ac.hs.selab.member.domain.Member; 4 | import kr.ac.hs.selab.member.domain.vo.Password; 5 | import kr.ac.hs.selab.member.infrastructure.MemberRepository; 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.extension.ExtendWith; 8 | import org.mockito.InjectMocks; 9 | import org.mockito.Mock; 10 | import org.mockito.junit.jupiter.MockitoExtension; 11 | import org.springframework.security.core.userdetails.UserDetails; 12 | 13 | import java.util.Optional; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertEquals; 16 | import static org.mockito.ArgumentMatchers.any; 17 | import static org.mockito.Mockito.when; 18 | 19 | @ExtendWith(MockitoExtension.class) 20 | public class UserServiceTest { 21 | @Mock 22 | private MemberRepository memberRepository; 23 | 24 | @InjectMocks 25 | private UserService userService; 26 | 27 | private final String email = "wrjs@naver.com"; 28 | 29 | @Test 30 | void loadUserByUsername_인증_인가() { 31 | // given 32 | Member member = Member.builder() 33 | .email(email) 34 | .password(new Password("!Comwldskjfo123d4")) 35 | .build(); 36 | 37 | when(memberRepository.findByEmail(any())) 38 | .thenReturn(Optional.ofNullable(member)); 39 | 40 | // when 41 | UserDetails userDetails = userService.loadUserByUsername(email); 42 | 43 | // then 44 | assertEquals(member.getEmail(), userDetails.getUsername()); 45 | } 46 | } -------------------------------------------------------------------------------- /src/test/java/kr/ac/hs/selab/board/domain/BoardTest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.domain; 2 | 3 | import com.navercorp.fixturemonkey.FixtureMonkey; 4 | import com.navercorp.fixturemonkey.generator.FieldReflectionArbitraryGenerator; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertTrue; 10 | 11 | public class BoardTest { 12 | private Board board; 13 | 14 | private static final FixtureMonkey fixtureMonkey = FixtureMonkey.builder() 15 | .defaultGenerator(FieldReflectionArbitraryGenerator.INSTANCE) 16 | .nullInject(0) 17 | .build(); 18 | 19 | @BeforeEach 20 | public void setUp() { 21 | board = fixtureMonkey.giveMeBuilder(Board.class) 22 | .set("deleteFlag", false) 23 | .sample(); 24 | } 25 | 26 | @Test 27 | public void 게시판_수정하기() { 28 | // when 29 | var newTitle = fixtureMonkey.giveMeOne(String.class); 30 | var newDescription = fixtureMonkey.giveMeOne(String.class); 31 | board.update(newTitle, newDescription); 32 | 33 | // then 34 | assertEquals(newTitle, board.getTitle()); 35 | assertEquals(newDescription, board.getDescription()); 36 | } 37 | 38 | @Test 39 | public void 게시판_소프트_삭제하기() { 40 | // given 41 | var oldTitle = board.getTitle(); 42 | 43 | // when 44 | board.delete(); 45 | 46 | // then 47 | assertEquals(oldTitle + "-" + board.getId(), board.getTitle()); 48 | assertTrue(board.isDeleteFlag()); 49 | } 50 | } -------------------------------------------------------------------------------- /src/test/java/kr/ac/hs/selab/board/domain/CommentTest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.domain; 2 | 3 | import com.navercorp.fixturemonkey.FixtureMonkey; 4 | import com.navercorp.fixturemonkey.generator.FieldReflectionArbitraryGenerator; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertTrue; 10 | 11 | class CommentTest { 12 | private Comment comment; 13 | 14 | private static final FixtureMonkey fixtureMonkey = FixtureMonkey.builder() 15 | .defaultGenerator(FieldReflectionArbitraryGenerator.INSTANCE) 16 | .nullInject(0) 17 | .build(); 18 | 19 | @BeforeEach 20 | void setUp() { 21 | comment = fixtureMonkey.giveMeBuilder(Comment.class) 22 | .set("deleteFlag", false) 23 | .sample(); 24 | } 25 | 26 | @Test 27 | void 수정_성공() { 28 | // when 29 | var newContent = fixtureMonkey.giveMeOne(String.class); 30 | comment.update(newContent); 31 | 32 | // then 33 | assertEquals(newContent, comment.getContent()); 34 | } 35 | 36 | @Test 37 | void 삭제_성공() { 38 | // when 39 | comment.delete(); 40 | 41 | // then 42 | assertTrue(comment.isDeleteFlag()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/kr/ac/hs/selab/board/domain/PostTest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.domain; 2 | 3 | import com.navercorp.fixturemonkey.FixtureMonkey; 4 | import com.navercorp.fixturemonkey.generator.FieldReflectionArbitraryGenerator; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertTrue; 10 | 11 | class PostTest { 12 | private Post post; 13 | 14 | private static final FixtureMonkey fixtureMonkey = FixtureMonkey.builder() 15 | .defaultGenerator(FieldReflectionArbitraryGenerator.INSTANCE) 16 | .nullInject(0) 17 | .build(); 18 | 19 | @BeforeEach 20 | void setUp() { 21 | post = fixtureMonkey.giveMeBuilder(Post.class) 22 | .set("deleteFlag", false) 23 | .sample(); 24 | } 25 | 26 | @Test 27 | void 수정_성공() { 28 | // when 29 | var newTitle = fixtureMonkey.giveMeOne(String.class); 30 | var newContent = fixtureMonkey.giveMeOne(String.class); 31 | post.update(newTitle, newContent); 32 | 33 | // then 34 | assertEquals(newTitle, post.getTitle()); 35 | assertEquals(newContent, post.getContent()); 36 | } 37 | 38 | @Test 39 | void 삭제_성공() { 40 | // when 41 | post.delete(); 42 | 43 | // then 44 | assertTrue(post.isDeleteFlag()); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/kr/ac/hs/selab/board/facade/PostEventListenerTest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.board.facade; 2 | 3 | import com.navercorp.fixturemonkey.FixtureMonkey; 4 | import com.navercorp.fixturemonkey.generator.FieldReflectionArbitraryGenerator; 5 | import kr.ac.hs.selab.board.application.CommentLikeService; 6 | import kr.ac.hs.selab.board.application.CommentService; 7 | import kr.ac.hs.selab.board.application.PostLikeService; 8 | import kr.ac.hs.selab.board.domain.Comment; 9 | import kr.ac.hs.selab.board.domain.Post; 10 | import kr.ac.hs.selab.board.domain.event.PostEvent; 11 | import org.junit.jupiter.api.Test; 12 | import org.junit.jupiter.api.extension.ExtendWith; 13 | import org.mockito.InjectMocks; 14 | import org.mockito.Mock; 15 | import org.mockito.Mockito; 16 | import org.mockito.junit.jupiter.MockitoExtension; 17 | 18 | @ExtendWith(MockitoExtension.class) 19 | public class PostEventListenerTest { 20 | @Mock 21 | private CommentService commentService; 22 | 23 | @Mock 24 | private CommentLikeService commentLikeService; 25 | 26 | @Mock 27 | private PostLikeService postLikeService; 28 | 29 | @InjectMocks 30 | private PostEventListener postEventListener; 31 | 32 | private static final FixtureMonkey fixtureMonkey = FixtureMonkey.builder() 33 | .defaultGenerator(FieldReflectionArbitraryGenerator.INSTANCE) 34 | .nullInject(0) 35 | .build(); 36 | 37 | @Test 38 | public void 게시글_삭제시_관련_객체_전부_삭제_성공() { 39 | // given 40 | var post = fixtureMonkey.giveMeBuilder(Post.class) 41 | .set("deleteFlag", false) 42 | .sample(); 43 | var comments = fixtureMonkey.giveMeBuilder(Comment.class) 44 | .set("postId", post.getId()) 45 | .set("deleteFlag", false) 46 | .sampleList(10); 47 | 48 | // mocking 49 | Mockito.doNothing() 50 | .when(postLikeService) 51 | .deleteByPostId(post.getId()); 52 | Mockito.when(commentService.findCommentsByPostId(post.getId())) 53 | .thenReturn(comments); 54 | Mockito.doNothing() 55 | .when(commentLikeService) 56 | .deleteByComments(comments); 57 | Mockito.doNothing() 58 | .when(commentService) 59 | .deleteByPost(post.getId()); 60 | 61 | // when 62 | postEventListener.deleteByPost(PostEvent.of(post)); 63 | 64 | // then 65 | // TODO void test 연구하기 66 | // TODO async, transaction test 연구하기 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/kr/ac/hs/selab/common/presentation/HealthControllerTest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.common.presentation; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.mockito.junit.jupiter.MockitoExtension; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 8 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 9 | import org.springframework.context.annotation.Import; 10 | import org.springframework.test.web.servlet.MockMvc; 11 | 12 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 13 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 14 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 16 | 17 | @WebMvcTest(useDefaultFilters = false) 18 | @AutoConfigureMockMvc(addFilters = false) 19 | @Import(HealthController.class) 20 | @ExtendWith(MockitoExtension.class) 21 | public class HealthControllerTest { 22 | @Autowired 23 | private MockMvc mockMvc; 24 | 25 | @Test 26 | public void 성공() throws Exception { 27 | mockMvc.perform(get("/health")) 28 | .andExpect(status().isOk()) 29 | .andExpect(content().string("Health Good!")) 30 | .andDo(print()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/kr/ac/hs/selab/free_post/domain/FreePostCommentTest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.domain; 2 | 3 | import com.navercorp.fixturemonkey.FixtureMonkey; 4 | import com.navercorp.fixturemonkey.generator.FieldReflectionArbitraryGenerator; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertTrue; 10 | 11 | class FreePostCommentTest { 12 | private FreePostComment noticeComment; 13 | 14 | private static final FixtureMonkey fixtureMonkey = FixtureMonkey.builder() 15 | .defaultGenerator(FieldReflectionArbitraryGenerator.INSTANCE) 16 | .nullInject(0) 17 | .build(); 18 | 19 | @BeforeEach 20 | void setUp() { 21 | noticeComment = fixtureMonkey.giveMeBuilder(FreePostComment.class) 22 | .set("deleteFlag", false) 23 | .sample(); 24 | } 25 | 26 | @Test 27 | void 수정하기() { 28 | // when 29 | var newContent = fixtureMonkey.giveMeOne(String.class); 30 | noticeComment.update(newContent); 31 | 32 | // then 33 | assertEquals(newContent, noticeComment.getContent()); 34 | } 35 | 36 | @Test 37 | void 삭제하기() { 38 | // when 39 | noticeComment.delete(); 40 | 41 | // then 42 | assertTrue(noticeComment.isDeleteFlag()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/kr/ac/hs/selab/free_post/domain/FreePostTest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.free_post.domain; 2 | 3 | import com.navercorp.fixturemonkey.FixtureMonkey; 4 | import com.navercorp.fixturemonkey.generator.FieldReflectionArbitraryGenerator; 5 | import kr.ac.hs.selab.notice.domain.Notice; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | import static org.junit.jupiter.api.Assertions.assertTrue; 11 | 12 | public class FreePostTest { 13 | private Notice notice; 14 | 15 | private static final FixtureMonkey fixtureMonkey = FixtureMonkey.builder() 16 | .defaultGenerator(FieldReflectionArbitraryGenerator.INSTANCE) 17 | .nullInject(0) 18 | .build(); 19 | 20 | @BeforeEach 21 | public void setUp() { 22 | notice = fixtureMonkey.giveMeBuilder(Notice.class) 23 | .set("deleteFlag", false) 24 | .sample(); 25 | } 26 | 27 | @Test 28 | public void 자유게시글_수정하기() { 29 | // when 30 | var newTitle = fixtureMonkey.giveMeOne(String.class); 31 | var newContent = fixtureMonkey.giveMeOne(String.class); 32 | notice.update(newTitle, newContent); 33 | 34 | // then 35 | assertEquals(newTitle, notice.getTitle()); 36 | assertEquals(newContent, notice.getContent()); 37 | } 38 | 39 | @Test 40 | public void 자유게시글_소프트_삭제하기() { 41 | // when 42 | notice.delete(); 43 | 44 | // then 45 | assertTrue(notice.isDeleteFlag()); 46 | } 47 | } -------------------------------------------------------------------------------- /src/test/java/kr/ac/hs/selab/member/facade/MemberFacadeTest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.member.facade; 2 | 3 | import kr.ac.hs.selab.member.application.MemberService; 4 | import kr.ac.hs.selab.member.dto.request.MemberExistRequest; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.api.extension.ExtendWith; 7 | import org.mockito.InjectMocks; 8 | import org.mockito.Mock; 9 | import org.mockito.Mockito; 10 | import org.mockito.junit.jupiter.MockitoExtension; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertTrue; 13 | import static org.mockito.ArgumentMatchers.anyString; 14 | 15 | @ExtendWith(MockitoExtension.class) 16 | public class MemberFacadeTest { 17 | @Mock 18 | private MemberService memberService; 19 | 20 | @InjectMocks 21 | private MemberFacade memberFacade; 22 | 23 | @Test 24 | public void 회원가입_여부_조회() { 25 | // given 26 | var request = new MemberExistRequest("leeheefull@gmail.com"); 27 | 28 | // mock 29 | Mockito.when(memberService.existsByEmail(anyString())) 30 | .thenReturn(true); 31 | 32 | // when 33 | var actual = memberFacade.exist(request); 34 | 35 | // then 36 | assertTrue(actual.isSignup()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/kr/ac/hs/selab/member/presentation/MemberControllerTest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.member.presentation; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import kr.ac.hs.selab.member.dto.request.MemberExistRequest; 5 | import kr.ac.hs.selab.member.dto.response.MemberExistResponse; 6 | import kr.ac.hs.selab.member.facade.MemberFacade; 7 | import org.junit.jupiter.api.Test; 8 | import org.junit.jupiter.api.extension.ExtendWith; 9 | import org.mockito.Mockito; 10 | import org.mockito.junit.jupiter.MockitoExtension; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 13 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 14 | import org.springframework.boot.test.mock.mockito.MockBean; 15 | import org.springframework.context.annotation.Import; 16 | import org.springframework.http.MediaType; 17 | import org.springframework.test.web.servlet.MockMvc; 18 | 19 | import static org.mockito.ArgumentMatchers.any; 20 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 21 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 22 | 23 | @WebMvcTest(useDefaultFilters = false) 24 | @AutoConfigureMockMvc(addFilters = false) 25 | @Import(MemberController.class) 26 | @ExtendWith(MockitoExtension.class) 27 | public class MemberControllerTest { 28 | @MockBean 29 | private MemberFacade memberFacade; 30 | 31 | @Autowired 32 | private ObjectMapper objectMapper; 33 | 34 | @Autowired 35 | private MockMvc mockMvc; 36 | 37 | @Test 38 | public void 회원가입_여부_조회() throws Exception { 39 | // given 40 | var request = new MemberExistRequest("leeheefull@gmail.com"); 41 | var response = MemberExistResponse.of(true); 42 | 43 | // mocking 44 | Mockito.when(memberFacade.exist(any())) 45 | .thenReturn(response); 46 | 47 | // when, then 48 | mockMvc.perform( 49 | post("/api/v1/members/exist") 50 | .accept(MediaType.APPLICATION_JSON) 51 | .contentType(MediaType.APPLICATION_JSON) 52 | .characterEncoding("utf-8") 53 | .content(objectMapper.writeValueAsString(request)) 54 | ) 55 | .andExpect(status().isOk()); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/kr/ac/hs/selab/notice/domain/NoticeCommentTest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.domain; 2 | 3 | import com.navercorp.fixturemonkey.FixtureMonkey; 4 | import com.navercorp.fixturemonkey.generator.FieldReflectionArbitraryGenerator; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertTrue; 10 | 11 | class NoticeCommentTest { 12 | private NoticeComment noticeComment; 13 | 14 | private static final FixtureMonkey fixtureMonkey = FixtureMonkey.builder() 15 | .defaultGenerator(FieldReflectionArbitraryGenerator.INSTANCE) 16 | .nullInject(0) 17 | .build(); 18 | 19 | @BeforeEach 20 | void setUp() { 21 | noticeComment = fixtureMonkey.giveMeBuilder(NoticeComment.class) 22 | .set("deleteFlag", false) 23 | .sample(); 24 | } 25 | 26 | @Test 27 | void 수정하기() { 28 | // when 29 | var newContent = fixtureMonkey.giveMeOne(String.class); 30 | noticeComment.update(newContent); 31 | 32 | // then 33 | assertEquals(newContent, noticeComment.getContent()); 34 | } 35 | 36 | @Test 37 | void 삭제하기() { 38 | // when 39 | noticeComment.delete(); 40 | 41 | // then 42 | assertTrue(noticeComment.isDeleteFlag()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/kr/ac/hs/selab/notice/domain/NoticeTest.java: -------------------------------------------------------------------------------- 1 | package kr.ac.hs.selab.notice.domain; 2 | 3 | import com.navercorp.fixturemonkey.FixtureMonkey; 4 | import com.navercorp.fixturemonkey.generator.FieldReflectionArbitraryGenerator; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertTrue; 10 | 11 | public class NoticeTest { 12 | private Notice notice; 13 | 14 | private static final FixtureMonkey fixtureMonkey = FixtureMonkey.builder() 15 | .defaultGenerator(FieldReflectionArbitraryGenerator.INSTANCE) 16 | .nullInject(0) 17 | .build(); 18 | 19 | @BeforeEach 20 | public void setUp() { 21 | notice = fixtureMonkey.giveMeBuilder(Notice.class) 22 | .set("deleteFlag", false) 23 | .sample(); 24 | } 25 | 26 | @Test 27 | public void 공지사항_수정하기() { 28 | // when 29 | var newTitle = fixtureMonkey.giveMeOne(String.class); 30 | var newContent = fixtureMonkey.giveMeOne(String.class); 31 | notice.update(newTitle, newContent); 32 | 33 | // then 34 | assertEquals(newTitle, notice.getTitle()); 35 | assertEquals(newContent, notice.getContent()); 36 | } 37 | 38 | @Test 39 | public void 공지사항_소프트_삭제하기() { 40 | // when 41 | notice.delete(); 42 | 43 | // then 44 | assertTrue(notice.isDeleteFlag()); 45 | } 46 | } --------------------------------------------------------------------------------