├── .github ├── ISSUE_TEMPLATE │ └── feature_request.md └── workflows │ ├── gradle-test.yml │ ├── test-and-deploy-app.yaml │ └── test-and-deploy-batch.yaml ├── .gitignore ├── HELP.md ├── LICENSE ├── README.md ├── build.gradle ├── deployment ├── deployment-batch.yml └── deployment.yml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── kustomization.yaml ├── mureng-api ├── Dockerfile ├── build.gradle └── src │ ├── main │ └── java │ │ └── net │ │ └── mureng │ │ ├── ApiBasePackage.java │ │ └── api │ │ ├── MurengApiApplication.java │ │ ├── badge │ │ ├── component │ │ │ └── BadgeMapper.java │ │ └── dto │ │ │ └── BadgeDto.java │ │ ├── core │ │ ├── advice │ │ │ ├── AbstractControllerAdvice.java │ │ │ ├── DefaultControllerAdvice.java │ │ │ └── ValidationControllerAdvice.java │ │ ├── annotation │ │ │ └── CurrentUser.java │ │ ├── component │ │ │ ├── AttendanceCheckInterceptor.java │ │ │ ├── FileSystemFileUploader.java │ │ │ ├── FileUploader.java │ │ │ ├── RequestLogFilter.java │ │ │ └── S3FileUploader.java │ │ ├── config │ │ │ ├── AmazonS3Config.java │ │ │ ├── HttpConfig.java │ │ │ ├── RestTemplateConfig.java │ │ │ ├── SecurityConfig.java │ │ │ └── SwaggerConfig.java │ │ ├── dto │ │ │ ├── ApiPageRequest.java │ │ │ ├── ApiPageResult.java │ │ │ ├── ApiResult.java │ │ │ └── UserDetailsImpl.java │ │ ├── jwt │ │ │ ├── component │ │ │ │ ├── JwtAuthenticationEntryPoint.java │ │ │ │ ├── JwtAuthenticationFilter.java │ │ │ │ ├── JwtCreator.java │ │ │ │ ├── JwtResolver.java │ │ │ │ └── JwtValidator.java │ │ │ ├── dto │ │ │ │ ├── TokenDto.java │ │ │ │ └── TokenProvider.java │ │ │ ├── service │ │ │ │ └── JwtService.java │ │ │ └── web │ │ │ │ └── JwtController.java │ │ ├── oauth2 │ │ │ ├── dto │ │ │ │ └── OAuth2Profile.java │ │ │ └── service │ │ │ │ └── OAuth2Service.java │ │ ├── util │ │ │ ├── AuthenticationHelper.java │ │ │ ├── NicknameNormalizer.java │ │ │ └── PathUtil.java │ │ └── validation │ │ │ ├── DateStringValidator.java │ │ │ ├── KorEngOnlyValidator.java │ │ │ ├── TimeStringValidator.java │ │ │ └── annotation │ │ │ ├── DateFormat.java │ │ │ ├── KorEngOnly.java │ │ │ └── TimeFormat.java │ │ ├── member │ │ ├── component │ │ │ ├── MemberAchievementMapper.java │ │ │ ├── MemberMapper.java │ │ │ └── MemberScrapMapper.java │ │ ├── dto │ │ │ ├── MemberAchievementDto.java │ │ │ ├── MemberDto.java │ │ │ └── MemberScrapDto.java │ │ ├── service │ │ │ ├── MemberBadgeService.java │ │ │ ├── MemberExpressionScrapService.java │ │ │ ├── MemberImageService.java │ │ │ ├── MemberSignupService.java │ │ │ └── UserDetailsServiceImpl.java │ │ └── web │ │ │ ├── MemberAchievementController.java │ │ │ ├── MemberAuthController.java │ │ │ ├── MemberBadgeController.java │ │ │ ├── MemberController.java │ │ │ ├── MemberImageController.java │ │ │ ├── MemberReplyController.java │ │ │ ├── MemberScrapController.java │ │ │ └── MemberSettingController.java │ │ ├── question │ │ ├── component │ │ │ ├── QuestionMapper.java │ │ │ └── WordHintMapper.java │ │ ├── dto │ │ │ ├── QuestionDto.java │ │ │ └── WordHintDto.java │ │ ├── service │ │ │ └── QuestionPaginationService.java │ │ └── web │ │ │ ├── QuestionController.java │ │ │ ├── QuestionReplyController.java │ │ │ └── TodayQuestionController.java │ │ ├── reply │ │ ├── component │ │ │ ├── ReplyLikesMapper.java │ │ │ └── ReplyMapper.java │ │ ├── dto │ │ │ ├── ReplyDto.java │ │ │ └── ReplyLikesDto.java │ │ ├── service │ │ │ ├── ReplyImageService.java │ │ │ └── ReplyPaginationService.java │ │ └── web │ │ │ ├── ReplyController.java │ │ │ ├── ReplyImageController.java │ │ │ └── ReplyLikesController.java │ │ ├── todayexpression │ │ ├── component │ │ │ └── TodayExpressionMapper.java │ │ ├── dto │ │ │ └── UsefulExpressionDto.java │ │ └── web │ │ │ └── TodayExpressionController.java │ │ └── web │ │ ├── ErrorControllerImpl.java │ │ ├── FcmTokenController.java │ │ ├── HomeController.java │ │ └── ProbeController.java │ └── test │ └── java │ └── net │ └── mureng │ └── api │ ├── MurengApiApplicationTests.java │ ├── annotation │ └── WithMockMurengUser.java │ ├── badge │ └── BadgeMapperTest.java │ ├── common │ └── DtoCreator.java │ ├── config │ └── WithMockMurengUserSecurityContextFactory.java │ ├── core │ ├── component │ │ ├── AmazonS3ClientTest.java │ │ └── FileSystemFileUploaderTest.java │ ├── jwt │ │ └── component │ │ │ ├── JwtCreatorTest.java │ │ │ ├── JwtTest.java │ │ │ └── JwtValidatorTest.java │ ├── util │ │ └── NicknameNormalizerTest.java │ └── validation │ │ ├── AbstractValidatorTest.java │ │ ├── DateStringValidatorTest.java │ │ ├── KorEngOnlyValidatorTest.java │ │ └── TimeStringValidatorTest.java │ ├── jwt │ └── JwtAuthenticationTest.java │ ├── member │ ├── component │ │ ├── MemberAchievementMapperTest.java │ │ ├── MemberMapperTest.java │ │ └── MemberScrapMapperTest.java │ └── web │ │ ├── MemberAchievementControllerTest.java │ │ ├── MemberAuthControllerTest.java │ │ ├── MemberBadgeControllerTest.java │ │ ├── MemberControllerTest.java │ │ ├── MemberImageControllerTest.java │ │ ├── MemberReplyControllerTest.java │ │ └── MemberScrapControllerTest.java │ ├── oauth │ ├── OAuthServiceTest.java │ └── OAuthTest.java │ ├── question │ ├── component │ │ ├── QuestionDtoValidationTest.java │ │ ├── QuestionMapperTest.java │ │ └── WordHintMapperTest.java │ └── web │ │ ├── QuestionControllerTest.java │ │ ├── QuestionReplyControllerTest.java │ │ └── TodayQuestionControllerTest.java │ ├── reply │ ├── component │ │ ├── ReplyLikesMapperTest.java │ │ └── ReplyMapperTest.java │ └── web │ │ ├── ReplyControllerTest.java │ │ ├── ReplyImageControllerTest.java │ │ └── ReplyLikesControllerTest.java │ ├── todayexpression │ ├── component │ │ └── UsefulExpressionMapperTest.java │ └── web │ │ └── UsefulExpressionControllerTest.java │ └── web │ ├── AbstractControllerTest.java │ ├── ErrorControllerTest.java │ ├── FcmTokenControllerTest.java │ └── HomeControllerTest.java ├── mureng-batch ├── Dockerfile ├── build.gradle └── src │ ├── main │ ├── java │ │ └── net │ │ │ └── mureng │ │ │ ├── BatchBasePackage.java │ │ │ └── batch │ │ │ ├── BatchStarter.java │ │ │ ├── MurengBatchApplication.java │ │ │ ├── core │ │ │ ├── config │ │ │ │ ├── AutowiringSpringBeanJobFactory.java │ │ │ │ ├── BatchConfig.java │ │ │ │ └── QuartzConfig.java │ │ │ ├── job │ │ │ │ ├── AbstractJobConductor.java │ │ │ │ ├── CronJobRequest.java │ │ │ │ ├── JobRequest.java │ │ │ │ ├── JobRequestService.java │ │ │ │ ├── MurengBatchJobService.java │ │ │ │ ├── MurengJobLauncher.java │ │ │ │ ├── MurengJobLauncherProvider.java │ │ │ │ ├── MurengQuartzJob.java │ │ │ │ ├── ScheduledJob.java │ │ │ │ └── ScheduledJobFactory.java │ │ │ ├── listener │ │ │ │ ├── JobsListener.java │ │ │ │ └── TriggersListener.java │ │ │ ├── scheduler │ │ │ │ ├── ScheduleService.java │ │ │ │ └── ScheduleServiceImpl.java │ │ │ └── trigger │ │ │ │ ├── CronTriggerProvider.java │ │ │ │ ├── SimpleTriggerProvider.java │ │ │ │ ├── TriggerProvider.java │ │ │ │ └── TriggerProviderFactory.java │ │ │ ├── push │ │ │ ├── job │ │ │ │ └── FcmDailyPushJobConductor.java │ │ │ └── service │ │ │ │ └── FcmDailyPushService.java │ │ │ ├── question │ │ │ └── job │ │ │ │ └── TodayQuestionRefreshJobConductor.java │ │ │ ├── todayexpression │ │ │ ├── maintain │ │ │ │ ├── job │ │ │ │ │ └── TodayExpressionMaintainJobConductor.java │ │ │ │ └── service │ │ │ │ │ ├── TodayUsefulExpressionMaintainService.java │ │ │ │ │ └── TodayUsefulExpressionMaintainServiceImpl.java │ │ │ └── refresh │ │ │ │ ├── job │ │ │ │ └── TodayExpressionRefreshJobConductor.java │ │ │ │ └── service │ │ │ │ ├── TodayUsefulExpressionRefreshService.java │ │ │ │ └── TodayUsefulExpressionRefreshServiceImpl.java │ │ │ └── util │ │ │ ├── CronExpression.java │ │ │ └── JobLauncherUtil.java │ └── resources │ │ ├── application-localbatch.yml │ │ ├── quartz.properties │ │ └── quartz_schema.sql │ └── test │ └── java │ └── net │ └── mureng │ └── batch │ ├── AbstractJobTest.java │ ├── core │ └── config │ │ └── TestBatchConfig.java │ ├── push │ └── job │ │ └── FcmDailyPushJobTest.java │ ├── question │ └── job │ │ └── TodayQuestionRefreshJobTest.java │ └── todayexpression │ └── refresh │ └── service │ └── TodayUsefulExpressionRefreshServiceTest.java ├── mureng-core ├── build.gradle └── src │ ├── main │ ├── java │ │ └── net │ │ │ └── mureng │ │ │ ├── CoreBasePackage.java │ │ │ ├── core │ │ │ ├── MurengCoreApplication.java │ │ │ ├── badge │ │ │ │ ├── entity │ │ │ │ │ ├── Badge.java │ │ │ │ │ ├── BadgeAccomplished.java │ │ │ │ │ └── BadgeAccomplishedPK.java │ │ │ │ ├── repository │ │ │ │ │ ├── BadgeAccomplishedRepository.java │ │ │ │ │ └── BadgeRepository.java │ │ │ │ └── service │ │ │ │ │ ├── BadgeAccomplishedService.java │ │ │ │ │ ├── BadgeAccomplishedServiceImpl.java │ │ │ │ │ └── BadgeService.java │ │ │ ├── cookie │ │ │ │ ├── entity │ │ │ │ │ └── CookieAcquirement.java │ │ │ │ ├── repository │ │ │ │ │ └── CookieAcquirementRepository.java │ │ │ │ └── service │ │ │ │ │ └── CookieAcquirementService.java │ │ │ ├── core │ │ │ │ ├── component │ │ │ │ │ ├── DateFactory.java │ │ │ │ │ ├── DirectoryScanner.java │ │ │ │ │ └── NumberRandomizer.java │ │ │ │ ├── exception │ │ │ │ │ ├── AccessNotAllowedException.java │ │ │ │ │ ├── BadRequestException.java │ │ │ │ │ ├── EntityNotFoundException.java │ │ │ │ │ ├── MurengException.java │ │ │ │ │ ├── ResourceNotFoundException.java │ │ │ │ │ └── UnauthorizedException.java │ │ │ │ └── message │ │ │ │ │ ├── ErrorMessage.java │ │ │ │ │ └── ResponseMessage.java │ │ │ ├── member │ │ │ │ ├── entity │ │ │ │ │ ├── FcmToken.java │ │ │ │ │ ├── Member.java │ │ │ │ │ ├── MemberAttendance.java │ │ │ │ │ ├── MemberScrap.java │ │ │ │ │ ├── MemberScrapPK.java │ │ │ │ │ └── MemberSetting.java │ │ │ │ ├── repository │ │ │ │ │ ├── FcmTokenRepository.java │ │ │ │ │ ├── MemberRepository.java │ │ │ │ │ └── MemberScrapRepository.java │ │ │ │ └── service │ │ │ │ │ ├── FcmTokenService.java │ │ │ │ │ └── MemberService.java │ │ │ ├── question │ │ │ │ ├── entity │ │ │ │ │ ├── Question.java │ │ │ │ │ ├── TodayQuestion.java │ │ │ │ │ └── WordHint.java │ │ │ │ ├── repository │ │ │ │ │ ├── QuestionRepository.java │ │ │ │ │ ├── TodayQuestionRepository.java │ │ │ │ │ └── WordHintRepository.java │ │ │ │ └── service │ │ │ │ │ ├── QuestionService.java │ │ │ │ │ ├── TodayQuestionSelectionService.java │ │ │ │ │ ├── TodayQuestionSelectionServiceImpl.java │ │ │ │ │ └── TodayQuestionService.java │ │ │ ├── reply │ │ │ │ ├── entity │ │ │ │ │ ├── Reply.java │ │ │ │ │ ├── ReplyLikes.java │ │ │ │ │ └── ReplyLikesPK.java │ │ │ │ ├── repository │ │ │ │ │ ├── ReplyLikesRepository.java │ │ │ │ │ └── ReplyRepository.java │ │ │ │ └── service │ │ │ │ │ ├── ReplyLikesService.java │ │ │ │ │ ├── ReplyPostProcessService.java │ │ │ │ │ ├── ReplyPostProcessServiceImpl.java │ │ │ │ │ └── ReplyService.java │ │ │ └── todayexpression │ │ │ │ ├── entity │ │ │ │ ├── TodayUsefulExpression.java │ │ │ │ └── UsefulExpression.java │ │ │ │ ├── repository │ │ │ │ ├── TodayUsefulExpressionRepository.java │ │ │ │ └── UsefulExpressionRepository.java │ │ │ │ └── service │ │ │ │ └── UsefulExpressionService.java │ │ │ └── push │ │ │ ├── component │ │ │ └── FcmInitializer.java │ │ │ ├── dto │ │ │ └── NotificationRequest.java │ │ │ └── service │ │ │ ├── FcmLikePushService.java │ │ │ ├── FcmLikePushServiceImpl.java │ │ │ └── FcmService.java │ └── resources │ │ ├── application-dev.yml │ │ ├── application-local.yml │ │ ├── application-prod.yml │ │ ├── application.yml │ │ ├── config │ │ └── liquibase │ │ │ ├── changelog │ │ │ ├── 00000000_initial_schema.xml │ │ │ ├── 20200527_add_unique_constraint.xml │ │ │ ├── 20210618_update.xml │ │ │ ├── 20210619_fcm_token.xml │ │ │ ├── 20210620_fcm_token_member_unique.xml │ │ │ ├── 20210622_add_badge_is_checked_field.xml │ │ │ ├── 20210708_add_cookie_entity.xml │ │ │ ├── 20210708_drop_member_mureng_count.xml │ │ │ └── 20210708_today_expression.xml │ │ │ └── master.xml │ │ ├── local-test-data.sql │ │ └── logback-spring.xml │ └── test │ ├── java │ └── net │ │ └── mureng │ │ └── core │ │ ├── annotation │ │ └── MurengDataTest.java │ │ ├── badge │ │ └── repository │ │ │ └── BadgeAccomplishedRepositoryTest.java │ │ ├── common │ │ └── EntityCreator.java │ │ ├── config │ │ └── DbUnitConfig.java │ │ ├── cookie │ │ └── repository │ │ │ └── CookieAcquirementRepositoryTest.java │ │ ├── member │ │ └── repository │ │ │ ├── MemberRepositoryTest.java │ │ │ └── MemberScrapRepositoryTest.java │ │ ├── question │ │ ├── repository │ │ │ ├── QuestionRepositoryTest.java │ │ │ ├── TodayQuestionRepositoryTest.java │ │ │ └── WordHintRepositoryTest.java │ │ └── service │ │ │ └── TodayQuestionSelectionServiceImplTest.java │ │ └── reply │ │ ├── repository │ │ ├── ReplyLikesRepositoryTest.java │ │ └── ReplyRepositoryTest.java │ │ └── service │ │ └── ReplyServiceTest.java │ └── resources │ ├── dbunit │ ├── entity │ │ ├── badge.xml │ │ ├── badge_accomplished.xml │ │ ├── cookie_acquirement.xml │ │ ├── member.xml │ │ ├── member_scrap.xml │ │ ├── question.xml │ │ ├── reply.xml │ │ ├── reply_likes.xml │ │ ├── today_question.xml │ │ ├── useful_expression.xml │ │ └── word_hint.xml │ └── expected │ │ ├── 멤버_머렝_카운트_증가.xml │ │ └── 멤버_회원가입.xml │ └── sql │ └── integration.sql └── settings.gradle /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## 기능 11 | 12 | ## 할 일 13 | 14 | ## 마감기한 15 | -------------------------------------------------------------------------------- /.github/workflows/gradle-test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Gradle Test 5 | 6 | on: 7 | pull_request: 8 | branches: [ master ] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up JDK 11 16 | uses: actions/setup-java@v2 17 | with: 18 | java-version: '11' 19 | distribution: 'adopt' 20 | - name: Grant execute permission for gradlew 21 | run: chmod +x gradlew 22 | - name: Gradle 빌드 캐싱 23 | id: gradle-cache 24 | uses: actions/cache@v2 25 | with: 26 | path: | 27 | ~/.gradle/caches 28 | ~/.gradle/wrapper 29 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 30 | restore-keys: | 31 | ${{ runner.os }}-gradle- 32 | - name: Gradle 테스트 33 | run: ./gradlew test -Dfile.encoding=UTF-8 -Duser.timezone=Asia/Seoul --info 34 | -------------------------------------------------------------------------------- /.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 | 39 | ### properties ### 40 | application-oauth.properties 41 | /src/main/resources/application-local.yml 42 | /deployment/deployment-local.yml 43 | /.secrets 44 | 45 | /mureng-core/src/main/resources/application-dev-local.yml 46 | -------------------------------------------------------------------------------- /HELP.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ### Reference Documentation 4 | For further reference, please consider the following sections: 5 | 6 | * [Official Gradle documentation](https://docs.gradle.org) 7 | * [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.4.4/gradle-plugin/reference/html/) 8 | * [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.4.4/gradle-plugin/reference/html/#build-image) 9 | * [Spring Boot DevTools](https://docs.spring.io/spring-boot/docs/2.4.4/reference/htmlsingle/#using-boot-devtools) 10 | * [Spring Configuration Processor](https://docs.spring.io/spring-boot/docs/2.4.4/reference/htmlsingle/#configuration-metadata-annotation-processor) 11 | * [Spring Web](https://docs.spring.io/spring-boot/docs/2.4.4/reference/htmlsingle/#boot-features-developing-web-applications) 12 | * [Spring Data JPA](https://docs.spring.io/spring-boot/docs/2.4.4/reference/htmlsingle/#boot-features-jpa-and-spring-data) 13 | 14 | ### Guides 15 | The following guides illustrate how to use some features concretely: 16 | 17 | * [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) 18 | * [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) 19 | * [Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/) 20 | * [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/) 21 | 22 | ### Additional Links 23 | These additional references should also help you: 24 | 25 | * [Gradle Build Scans – insights for your project's build](https://scans.gradle.com#gradle) 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android-Team-1-Backend 2 | 3 | 4 | ### 정기배포 업데이트 내역[🔗](https://github.com/YAPP-18th/Android-Team-1-Backend/wiki/%EC%84%9C%EB%B2%84-%EC%A0%95%EA%B8%B0-%EB%B0%B0%ED%8F%AC-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8-%EB%82%B4%EC%97%AD) 5 | 6 |
7 | 8 | ## 1. 기술 스택 9 | 10 | Spring Boot, JPA & Hibernate, Mysql, Docker, K8S, GCP, AWS RDS, Httpd 11 | 12 | 13 | ## 2. git convention 14 | 15 | 16 | |명령어|내용| 17 | | :-----------------------------------: | :---------------------------------------: | 18 | | CHORE | 빌드 업무 수정, 패키지 매니저 수정 | 19 | | **ADD** | 코드나 테스트, 예제, 문서 등의 추가 | 20 | | **FIX** | 올바르지 않은 동작을 고친 경우 | 21 | | **REMOVE** | 코드의 삭제가 있을 때 | 22 | | **UPDATE** | 문서나 리소스, 라이브러리등의 수정, 추가, 보완 | 23 | | **FEAT** | 새로운 기능 추가 | 24 | | **CORRECT** | 주로 문법의 오류나 타입의 변경, 이름 변경 등 | 25 | | REFACTOR | 코드의 전면적인 수정 | 26 | | DOCS | 문서의 개정 | 27 | | RENAME | 파일의 이름 변경 | 28 | | TEST | TEST 코드 관련 | 29 | 30 | ## 3. Architecture 31 | ![Mureng-arch](https://user-images.githubusercontent.com/36787678/119779854-1bac8e80-bf04-11eb-8816-9d9899c05d47.png) 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /deployment/deployment-batch.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: mureng-batch-deployment 5 | spec: 6 | replicas: 1 7 | minReadySeconds: 30 8 | selector: 9 | matchLabels: 10 | app: mureng-batch 11 | template: 12 | metadata: 13 | labels: 14 | app: mureng-batch 15 | spec: 16 | containers: 17 | - name: mureng-batch 18 | image: 19 | resources: 20 | requests: 21 | memory: "350Mi" 22 | cpu: "350m" 23 | limits: 24 | memory: "350Mi" 25 | cpu: "350m" 26 | ports: 27 | - containerPort: 8080 28 | env: 29 | - name: SPRING_PROFILES_ACTIVE 30 | value: "prod" 31 | - name: SPRING_DATASOURCE_DRIVER_CLASS_NAME 32 | value: "$SPRING_DATASOURCE_DRIVER_CLASS_NAME" 33 | - name: SPRING_DATASOURCE_URL 34 | value: "$SPRING_DATASOURCE_URL" 35 | - name: SPRING_DATASOURCE_USERNAME 36 | value: "$SPRING_DATASOURCE_USERNAME" 37 | - name: SPRING_DATASOURCE_PASSWORD 38 | value: "$SPRING_DATASOURCE_PASSWORD" 39 | - name: FIREBASE_SERVICE_KEY 40 | value: "$FIREBASE_SERVICE_KEY" 41 | - name: SPRING_JWT_SECRET 42 | value: "$SPRING_JWT_SECRET" 43 | - name: AWS_ACCESS_KEY 44 | value: "$CLOUD_AWS_CREDENTIALS_ACCESS_KEY" 45 | - name: AWS_SECRET_KEY 46 | value: "$CLOUD_AWS_CREDENTIALS_SECRET_KEY" 47 | 48 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YAPP-18th/Android-Team-1-Backend/fdde63ef9ad2fb06040c0ed7aee1698e23b06e7d/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-6.8.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | org.gradle.daemon=false -------------------------------------------------------------------------------- /kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | resources: 5 | - deploy.yml 6 | -------------------------------------------------------------------------------- /mureng-api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gradle:6.6.1-jdk11 as compile 2 | 3 | WORKDIR /home/gradle/project 4 | 5 | # Only copy dependency-related files 6 | # COPY build.gradle settings.gradle /home/gradle/project/ 7 | 8 | # Only download dependencies 9 | # Eat the expected build failure since no source code has been copied yet 10 | # RUN gradle clean build --no-daemon > /dev/null 2>&1 || true 11 | COPY build/libs/mureng-api-0.0.1-SNAPSHOT.jar /home/gradle/project 12 | RUN mkdir /home/gradle/mount 13 | 14 | #RUN gradle clean bootJar 15 | 16 | ENTRYPOINT ["java","-jar","-Duser.timezone=Asia/Seoul","/home/gradle/project/mureng-api-0.0.1-SNAPSHOT.jar"] 17 | EXPOSE 8080 -------------------------------------------------------------------------------- /mureng-api/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group 'net.mureng.api' 6 | version '0.0.1-SNAPSHOT' 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | } 14 | 15 | test { 16 | useJUnitPlatform() 17 | } -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/ApiBasePackage.java: -------------------------------------------------------------------------------- 1 | package net.mureng; 2 | 3 | public interface ApiBasePackage { 4 | } 5 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/MurengApiApplication.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api; 2 | 3 | import net.mureng.ApiBasePackage; 4 | import net.mureng.CoreBasePackage; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.boot.web.servlet.ServletComponentScan; 8 | 9 | import javax.annotation.PostConstruct; 10 | import java.util.TimeZone; 11 | 12 | @ServletComponentScan 13 | @SpringBootApplication(scanBasePackageClasses = { CoreBasePackage.class, ApiBasePackage.class }) 14 | public class MurengApiApplication { 15 | 16 | @PostConstruct 17 | public void initialize() { 18 | TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); 19 | } 20 | 21 | public static void main(String[] args) { 22 | SpringApplication.run(MurengApiApplication.class, args); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/badge/component/BadgeMapper.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.badge.component; 2 | 3 | import net.mureng.api.badge.dto.BadgeDto; 4 | import net.mureng.core.badge.entity.Badge; 5 | import org.mapstruct.Mapper; 6 | 7 | @Mapper(componentModel = "spring") 8 | public interface BadgeMapper { 9 | BadgeDto toDto(Badge badge); 10 | } 11 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/badge/dto/BadgeDto.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.badge.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.*; 7 | 8 | 9 | @Builder 10 | @Getter @Setter 11 | @AllArgsConstructor 12 | @NoArgsConstructor 13 | @ApiModel(value="뱃지 모델", description="뱃지 모델") 14 | public class BadgeDto { 15 | @ApiModelProperty(value = "뱃지 이름") 16 | @JsonProperty(index = PropertyDisplayOrder.NAME) 17 | private String name; 18 | 19 | @ApiModelProperty(value = "뱃지 내용") 20 | @JsonProperty(index = PropertyDisplayOrder.CONTENT) 21 | private String content; 22 | 23 | private static class PropertyDisplayOrder { 24 | private static final int NAME = 0; 25 | private static final int CONTENT = 1; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/advice/AbstractControllerAdvice.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.advice; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import net.mureng.api.core.dto.ApiResult; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | 8 | @Slf4j 9 | public abstract class AbstractControllerAdvice { 10 | protected ResponseEntity> handleException(Throwable e, HttpStatus status) { 11 | log.error(e.getMessage(), e); 12 | return handleException(e, status, status.value()); 13 | } 14 | 15 | protected ResponseEntity> handleException(Throwable e, HttpStatus status, int errorCode) { 16 | return ResponseEntity.status(status) 17 | .body(ApiResult.fail(e.getMessage())); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/advice/DefaultControllerAdvice.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.advice; 2 | 3 | import net.mureng.api.core.dto.ApiResult; 4 | import net.mureng.core.core.exception.*; 5 | import net.mureng.core.core.exception.MurengException; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.RestControllerAdvice; 10 | 11 | import javax.annotation.Priority; 12 | 13 | @Priority(20) 14 | @RestControllerAdvice 15 | public class DefaultControllerAdvice extends AbstractControllerAdvice { 16 | @ExceptionHandler(value = { Exception.class, MurengException.class }) 17 | public ResponseEntity> handleUnknownException(Exception e) { 18 | return handleException(e, HttpStatus.INTERNAL_SERVER_ERROR); 19 | } 20 | 21 | @ExceptionHandler(value = { BadRequestException.class }) 22 | public ResponseEntity> handleBadRequestException(Exception e) { 23 | return handleException(e, HttpStatus.BAD_REQUEST); 24 | } 25 | 26 | @ExceptionHandler(value = { ResourceNotFoundException.class}) 27 | public ResponseEntity> handleNotFoundException(Exception e) { 28 | return handleException(e, HttpStatus.NOT_FOUND); 29 | } 30 | 31 | @ExceptionHandler(value = { AccessNotAllowedException.class}) 32 | public ResponseEntity> handleForbiddenException(Exception e) { 33 | return handleException(e, HttpStatus.FORBIDDEN); 34 | } 35 | 36 | @ExceptionHandler(value = { UnauthorizedException.class}) 37 | public ResponseEntity> handleUnauthorizedException(Exception e) { 38 | return handleException(e, HttpStatus.UNAUTHORIZED); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/advice/ValidationControllerAdvice.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.advice; 2 | 3 | import net.mureng.api.core.dto.ApiResult; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.validation.FieldError; 7 | import org.springframework.web.bind.MethodArgumentNotValidException; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.RestControllerAdvice; 10 | 11 | import javax.annotation.Priority; 12 | 13 | @Priority(5) 14 | @RestControllerAdvice 15 | public class ValidationControllerAdvice extends AbstractControllerAdvice { 16 | @ExceptionHandler(value = { MethodArgumentNotValidException.class }) 17 | public ResponseEntity> handleValidationException(MethodArgumentNotValidException e) { 18 | FieldError fieldError = e.getBindingResult().getFieldErrors().get(0); 19 | String message = String.format("[%s : %s]", 20 | fieldError.getField(), fieldError.getDefaultMessage()); 21 | return ResponseEntity.status(HttpStatus.BAD_REQUEST) 22 | .body(ApiResult.fail(message)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/annotation/CurrentUser.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.annotation; 2 | 3 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target({ElementType.PARAMETER, ElementType.TYPE}) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @AuthenticationPrincipal(expression = "#this == 'anonymousUser' ? null : member") 13 | public @interface CurrentUser { 14 | } -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/component/FileUploader.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.component; 2 | 3 | import org.springframework.web.multipart.MultipartFile; 4 | 5 | public interface FileUploader { 6 | String uploadMultiPartFile(MultipartFile mFile, String filePath); 7 | } 8 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/config/AmazonS3Config.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.config; 2 | 3 | import com.amazonaws.auth.AWSCredentials; 4 | import com.amazonaws.auth.AWSStaticCredentialsProvider; 5 | import com.amazonaws.auth.BasicAWSCredentials; 6 | import com.amazonaws.services.s3.AmazonS3; 7 | import com.amazonaws.services.s3.AmazonS3ClientBuilder; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | 12 | @Configuration 13 | public class AmazonS3Config { 14 | @Value("${cloud.aws.credentials.access_key}") 15 | private String accessKey; 16 | @Value("${cloud.aws.credentials.secret_key}") 17 | private String secretKey; 18 | @Value("${cloud.aws.region.static}") 19 | private String region; 20 | 21 | @Bean 22 | AmazonS3 amazonS3() { 23 | AWSCredentials credentials = new BasicAWSCredentials(this.accessKey, this.secretKey); 24 | 25 | //AmazonS3ClientBuilder를 통해 S3 Client를 가져와야 하는데, 자격증명을 해줘야 S3 Client를 가져올 수 있기 때문입니다. 26 | return AmazonS3ClientBuilder.standard() 27 | .withCredentials(new AWSStaticCredentialsProvider(credentials)) 28 | .withRegion(this.region) 29 | .build(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/config/HttpConfig.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.config; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.mureng.api.core.component.AttendanceCheckInterceptor; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 8 | 9 | @Configuration 10 | @RequiredArgsConstructor 11 | public class HttpConfig implements WebMvcConfigurer { 12 | private final AttendanceCheckInterceptor attendanceCheckInterceptor; 13 | 14 | @Override 15 | public void addInterceptors(InterceptorRegistry registry) { 16 | registry.addInterceptor(attendanceCheckInterceptor) 17 | .excludePathPatterns(SecurityConfig.EXCLUDED_URLS); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/config/RestTemplateConfig.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.config; 2 | 3 | import org.springframework.boot.web.client.RestTemplateBuilder; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.http.client.BufferingClientHttpRequestFactory; 7 | import org.springframework.http.client.SimpleClientHttpRequestFactory; 8 | import org.springframework.http.converter.StringHttpMessageConverter; 9 | import org.springframework.web.client.RestTemplate; 10 | 11 | import java.nio.charset.StandardCharsets; 12 | import java.time.Duration; 13 | 14 | @Configuration 15 | public class RestTemplateConfig { 16 | 17 | @Bean 18 | public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) { 19 | return restTemplateBuilder 20 | .requestFactory(() -> new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory())) 21 | .setConnectTimeout(Duration.ofMillis(5000)) // connection-timeout 22 | .setReadTimeout(Duration.ofMillis(5000)) // read-timeout 23 | .additionalMessageConverters(new StringHttpMessageConverter(StandardCharsets.UTF_8)) 24 | .build(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/dto/ApiPageRequest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.*; 7 | import net.mureng.core.core.exception.BadRequestException; 8 | import org.springframework.data.domain.PageRequest; 9 | import org.springframework.data.domain.Pageable; 10 | import org.springframework.data.domain.Sort; 11 | 12 | import java.util.stream.Stream; 13 | 14 | @Getter @Setter 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | @ApiModel(value="페이징 API 요청 모델") 18 | @EqualsAndHashCode 19 | public class ApiPageRequest { 20 | @ApiModelProperty(value = "요청 페이지 번호 (0부터 시작)", example = "0") 21 | private int page = 0; 22 | 23 | @ApiModelProperty(value = "요청 페이지 크기 (기본 : 10)", example = "10") 24 | private int size = 10; 25 | 26 | @ApiModelProperty(value = "페이지 정렬 방식 (기본 : popular)", allowableValues = "popular, newest") 27 | private PageSort sort = PageSort.POPULAR; 28 | 29 | public void setSort(String sortString) { 30 | this.sort = PageSort.create(sortString); 31 | } 32 | 33 | public Pageable convert() { 34 | return PageRequest.of(page, size); 35 | } 36 | 37 | public Pageable convertWithNewestSort() { 38 | return PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "regDate")); 39 | } 40 | 41 | @Getter 42 | public enum PageSort { 43 | POPULAR, 44 | NEWEST; 45 | 46 | @JsonCreator 47 | public static PageSort create(String requestValue) { 48 | return Stream.of(values()) 49 | .filter(v -> v.toString().equalsIgnoreCase(requestValue)) 50 | .findFirst() 51 | .orElseThrow(() -> new BadRequestException("잘못된 정렬 방식입니다.")); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/dto/ApiPageResult.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Getter; 6 | import org.springframework.data.domain.Page; 7 | 8 | import java.util.List; 9 | 10 | @Getter 11 | @ApiModel(value="페이징 API 응답 모델") 12 | public class ApiPageResult extends ApiResult> { 13 | 14 | @ApiModelProperty(value = "페이지 번호", position = 2) 15 | private final int currentPage; 16 | 17 | @ApiModelProperty(value = "총합 페이지", position = 3) 18 | private final int totalPage; 19 | 20 | @ApiModelProperty(value = "페이지 크기", position = 4) 21 | private final int pageSize; 22 | 23 | @ApiModelProperty(value = "전체 답변 개수", position = 5) 24 | private final long totalElements; 25 | 26 | public ApiPageResult(Page data, String message) { 27 | super(message, data.getContent()); 28 | this.currentPage = data.getPageable().getPageNumber(); 29 | this.pageSize = data.getPageable().getPageSize(); 30 | this.totalPage = data.getTotalPages(); 31 | this.totalElements = data.getTotalElements(); 32 | } 33 | 34 | public static ApiPageResult ok(Page data) { 35 | return ok(data, "ok"); 36 | } 37 | 38 | public static ApiPageResult ok(Page data, String message) { 39 | return new ApiPageResult<>(data, message); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/dto/ApiResult.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.Getter; 6 | 7 | @Getter 8 | @ApiModel(value="API 응답 공통 모델") 9 | public class ApiResult { 10 | 11 | @ApiModelProperty(value = "결과 메세지", position = 0) 12 | protected final String message; 13 | 14 | @ApiModelProperty(value = "응답 데이터, 실패시 null", position = 1) 15 | protected final T data; 16 | 17 | @ApiModelProperty(value = "요청 서버 시간", position = 5) 18 | protected final long timestamp; 19 | 20 | public ApiResult(String message, T data) { 21 | this.message = message; 22 | this.data = data; 23 | this.timestamp = System.currentTimeMillis(); 24 | } 25 | 26 | public static ApiResult ok(T data) { 27 | return ok(data, "ok"); 28 | } 29 | 30 | public static ApiResult ok(T data, String message) { 31 | return new ApiResult<>(message, data); 32 | } 33 | 34 | public static ApiResult fail(String message) { 35 | return new ApiResult<>(message, null); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/dto/UserDetailsImpl.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.dto; 2 | 3 | import lombok.Getter; 4 | import net.mureng.core.member.entity.Member; 5 | import org.springframework.security.core.GrantedAuthority; 6 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collection; 11 | import java.util.List; 12 | 13 | public class UserDetailsImpl implements UserDetails { 14 | @Getter 15 | private final Member member; 16 | 17 | public UserDetailsImpl(Member member) { 18 | this.member = member; 19 | } 20 | 21 | @Override 22 | public Collection getAuthorities() { 23 | List authorities = new ArrayList(); 24 | 25 | authorities.add(new SimpleGrantedAuthority("ROLE_USER")); 26 | 27 | return authorities; 28 | } 29 | 30 | @Override 31 | public String getPassword() { 32 | return null; 33 | } 34 | 35 | @Override 36 | public String getUsername() { 37 | return member.getIdentifier(); 38 | } 39 | 40 | @Override 41 | public boolean isAccountNonExpired() { 42 | return true; 43 | } 44 | 45 | @Override 46 | public boolean isAccountNonLocked() { 47 | return true; 48 | } 49 | 50 | @Override 51 | public boolean isCredentialsNonExpired() { 52 | return true; 53 | } 54 | 55 | @Override 56 | public boolean isEnabled() { 57 | return true; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/jwt/component/JwtAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.jwt.component; 2 | 3 | import org.springframework.security.core.AuthenticationException; 4 | import org.springframework.security.web.AuthenticationEntryPoint; 5 | import org.springframework.stereotype.Component; 6 | 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | import java.io.IOException; 10 | 11 | @Component 12 | public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { 13 | 14 | @Override 15 | public void commence(HttpServletRequest request, 16 | HttpServletResponse response, 17 | AuthenticationException authException) throws IOException { 18 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/jwt/component/JwtResolver.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.jwt.component; 2 | 3 | import io.jsonwebtoken.Jwts; 4 | import lombok.RequiredArgsConstructor; 5 | import net.mureng.api.core.util.AuthenticationHelper; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.security.core.Authentication; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.security.core.userdetails.UserDetailsService; 10 | import org.springframework.stereotype.Component; 11 | 12 | import javax.annotation.PostConstruct; 13 | import javax.servlet.http.HttpServletRequest; 14 | import java.util.Base64; 15 | 16 | @Component 17 | @RequiredArgsConstructor 18 | public class JwtResolver { 19 | 20 | @Value("${spring.jwt.secret}") 21 | private String secretKey; 22 | 23 | private final UserDetailsService userDetailsService; 24 | 25 | @PostConstruct 26 | protected void init() { 27 | secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); 28 | } 29 | 30 | public Authentication getAuthentication(String token){ 31 | UserDetails userDetails = userDetailsService.loadUserByUsername(this.getUserIdentifier(token)); 32 | return AuthenticationHelper.getUserPassAuthentication(userDetails); 33 | } 34 | 35 | public String getUserIdentifier(String token){ 36 | return Jwts.parser().setSigningKey(secretKey) 37 | .parseClaimsJws(token).getBody().getSubject(); 38 | } 39 | 40 | public String resolveToken(HttpServletRequest request) { 41 | return request.getHeader("X-AUTH-TOKEN"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/jwt/component/JwtValidator.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.jwt.component; 2 | 3 | import io.jsonwebtoken.Claims; 4 | import io.jsonwebtoken.Jws; 5 | import io.jsonwebtoken.Jwts; 6 | import lombok.RequiredArgsConstructor; 7 | import net.mureng.core.core.component.DateFactory; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.stereotype.Component; 10 | 11 | import javax.annotation.PostConstruct; 12 | import java.util.Base64; 13 | import java.util.Date; 14 | 15 | @Component 16 | @RequiredArgsConstructor 17 | public class JwtValidator { 18 | 19 | @Value("${spring.jwt.secret}") 20 | private String secretKey; 21 | 22 | private final DateFactory dateFactory; 23 | 24 | @PostConstruct 25 | protected void init() { 26 | secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); 27 | } 28 | 29 | public boolean validateToken(String jwtToken){ 30 | return validateTokenBefore(jwtToken, dateFactory.now()); 31 | } 32 | 33 | public boolean validateTokenBefore(String jwtToken, Date date){ 34 | try { 35 | Jws claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwtToken); 36 | return !claims.getBody().getExpiration().before(date); 37 | } catch (Exception e) { 38 | return false; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/jwt/dto/TokenDto.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.jwt.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.*; 6 | 7 | public class TokenDto { 8 | private TokenDto() { } 9 | 10 | @Getter @Setter 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @EqualsAndHashCode 14 | @ApiModel(value="Provider Token Dto", description="Provider 토큰 모델") 15 | public static class Provider { 16 | @ApiModelProperty(value = "Provider 이름 (Kakao / Google)") 17 | private TokenProvider providerName; 18 | 19 | @ApiModelProperty(value = "Provider 제공 Access Token") 20 | private String providerAccessToken; 21 | 22 | public void setProviderName(String providerName) { 23 | this.providerName = TokenProvider.create(providerName); 24 | } 25 | } 26 | 27 | @Getter @Setter 28 | @NoArgsConstructor 29 | @AllArgsConstructor 30 | @EqualsAndHashCode 31 | @ApiModel(value="Mureng Token Dto", description="Mureng 제공 JWT 및 Refresh 토큰 모델") 32 | public static class Mureng { 33 | @ApiModelProperty(value = "Mureng 제공 Access Token (jwt)") 34 | private String murengAccessToken; 35 | 36 | @ApiModelProperty(value = "Mureng 제공 Refresh Token (jwt)") 37 | private String murengRefreshToken; 38 | } 39 | 40 | @Getter @Setter 41 | @NoArgsConstructor 42 | @AllArgsConstructor 43 | @EqualsAndHashCode 44 | @ApiModel(value="Mureng Refresh Token Dto", description="Mureng 제공 JWT 및 Refresh 토큰 모델") 45 | public static class MurengRefresh { 46 | @ApiModelProperty(value = "Mureng 제공 Refresh Token (jwt)") 47 | private String murengRefreshToken; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/jwt/dto/TokenProvider.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.jwt.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import lombok.Getter; 5 | import net.mureng.api.core.dto.ApiPageRequest; 6 | import net.mureng.core.core.exception.BadRequestException; 7 | 8 | import java.util.stream.Stream; 9 | 10 | @Getter 11 | public enum TokenProvider { 12 | KAKAO, 13 | GOOGLE; 14 | 15 | @JsonCreator 16 | public static TokenProvider create(String requestValue) { 17 | return Stream.of(values()) 18 | .filter(v -> v.toString().equalsIgnoreCase(requestValue)) 19 | .findFirst() 20 | .orElseThrow(() -> new BadRequestException("잘못된 프로바이더입니다.")); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/jwt/web/JwtController.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.jwt.web; 2 | 3 | import io.swagger.annotations.Api; 4 | import io.swagger.annotations.ApiOperation; 5 | import io.swagger.annotations.ApiParam; 6 | import lombok.*; 7 | import lombok.extern.slf4j.Slf4j; 8 | import net.mureng.api.core.dto.ApiResult; 9 | import net.mureng.api.core.jwt.dto.TokenDto; 10 | import net.mureng.api.core.jwt.service.JwtService; 11 | import net.mureng.api.core.oauth2.dto.OAuth2Profile; 12 | import org.springframework.http.ResponseEntity; 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 | import javax.validation.Valid; 19 | 20 | @Slf4j 21 | @Api(value = "JWT 관련 엔드포인트") 22 | @RestController 23 | @RequiredArgsConstructor 24 | @RequestMapping("/api/jwt") 25 | public class JwtController { 26 | private final JwtService jwtService; 27 | 28 | @Deprecated 29 | @ApiOperation(value = "JWT 발급(Deprecated)", notes = "JWT를 발급합니다.(Deprecated)") 30 | @PostMapping 31 | public ResponseEntity> issue( 32 | @ApiParam(value = "사용자 id", required = true) @RequestBody @Valid OAuth2Profile profile 33 | ) { 34 | 35 | TokenDto.Mureng token = jwtService.issue(profile.getIdentifier()); 36 | 37 | return ResponseEntity.ok(ApiResult.ok( 38 | new DeprecatedTokenDto(token.getMurengAccessToken(), token.getMurengRefreshToken()) 39 | )); 40 | } 41 | 42 | @AllArgsConstructor 43 | @NoArgsConstructor 44 | @Getter @Setter 45 | public static class DeprecatedTokenDto { 46 | private String accessToken; 47 | private String refreshToken; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/oauth2/dto/OAuth2Profile.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.oauth2.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.*; 6 | 7 | @Builder 8 | @Getter @Setter 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @ApiModel(value="OAuth Profile", description="OAuth Profile 모델") 12 | public class OAuth2Profile { 13 | @ApiModelProperty(value = "사용자 id") 14 | private String identifier; 15 | } 16 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/util/AuthenticationHelper.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.util; 2 | 3 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 4 | import org.springframework.security.core.Authentication; 5 | import org.springframework.security.core.userdetails.UserDetails; 6 | 7 | public class AuthenticationHelper { 8 | private AuthenticationHelper() { } 9 | 10 | public static Authentication getUserPassAuthentication(UserDetails userDetails) { 11 | return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/util/NicknameNormalizer.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.util; 2 | 3 | import org.springframework.lang.Nullable; 4 | import org.springframework.util.StringUtils; 5 | 6 | import java.util.regex.Pattern; 7 | 8 | /** 9 | * 닉네임 정규화 도구
10 | * 최대 12자로 규정하되, 한글의 경우 2자로 계산한다.
11 | * 그 이상은 잘라낸다. 12 | */ 13 | public class NicknameNormalizer { 14 | private static final int MAXIMUM_LENGTH = 12; 15 | 16 | private NicknameNormalizer() { } 17 | 18 | public static String normalize(@Nullable String nickname) { 19 | if (! StringUtils.hasText(nickname)) { 20 | return nickname; 21 | } 22 | 23 | int remainLength = MAXIMUM_LENGTH; 24 | StringBuilder sb = new StringBuilder(); 25 | for (int i = 0; i < nickname.length(); i++) { 26 | char ch = nickname.charAt(i); 27 | if (isKorean(ch)) { 28 | if (remainLength <= 1) { 29 | return sb.toString(); 30 | } 31 | sb.append(ch); 32 | remainLength -= 2; 33 | 34 | } else { 35 | sb.append(ch); 36 | remainLength -= 1; 37 | if (remainLength <= 0) { 38 | break; 39 | } 40 | } 41 | } 42 | return sb.toString(); 43 | } 44 | 45 | private static boolean isKorean(char value) { 46 | return ( 0xAC00 <= value && value <= 0xD7A3 ) || ( 0x3131 <= value && value <= 0x318E ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/util/PathUtil.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.util; 2 | 3 | public class PathUtil { 4 | private PathUtil() { } 5 | 6 | public static String replaceWindowPathToLinuxPath(String path) { 7 | return path.replaceAll("\\\\", "/"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/validation/DateStringValidator.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.validation; 2 | 3 | import net.mureng.api.core.validation.annotation.DateFormat; 4 | 5 | import javax.validation.ConstraintValidator; 6 | import javax.validation.ConstraintValidatorContext; 7 | import java.time.LocalDate; 8 | 9 | public class DateStringValidator implements ConstraintValidator { 10 | @Override 11 | public boolean isValid(String value, ConstraintValidatorContext context) { 12 | try { 13 | LocalDate.parse(value); 14 | } catch (Exception e) { 15 | return false; 16 | } 17 | return true; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/validation/KorEngOnlyValidator.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.validation; 2 | 3 | import net.mureng.api.core.validation.annotation.KorEngOnly; 4 | 5 | import javax.validation.ConstraintValidator; 6 | import javax.validation.ConstraintValidatorContext; 7 | import java.util.regex.Pattern; 8 | 9 | /** 10 | * 한글, 숫자, 영어만 허용한다. 한글의 경우 자음, 모음 만 사용하는건 허용 안함 11 | */ 12 | public class KorEngOnlyValidator implements ConstraintValidator { 13 | @Override 14 | public boolean isValid(String value, ConstraintValidatorContext context) { 15 | return Pattern.matches("^[가-힣0-9a-zA-Z]*$", value); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/validation/TimeStringValidator.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.validation; 2 | 3 | import net.mureng.api.core.validation.annotation.TimeFormat; 4 | 5 | import javax.validation.ConstraintValidator; 6 | import javax.validation.ConstraintValidatorContext; 7 | import java.time.LocalTime; 8 | 9 | public class TimeStringValidator implements ConstraintValidator { 10 | @Override 11 | public boolean isValid(String value, ConstraintValidatorContext context) { 12 | try { 13 | LocalTime.parse(value); 14 | } catch (Exception e) { 15 | return false; 16 | } 17 | return true; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/validation/annotation/DateFormat.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.validation.annotation; 2 | 3 | import net.mureng.api.core.validation.DateStringValidator; 4 | 5 | import javax.validation.Constraint; 6 | import javax.validation.Payload; 7 | import java.lang.annotation.Documented; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.Target; 10 | 11 | import static java.lang.annotation.ElementType.*; 12 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 13 | 14 | @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) 15 | @Retention(RUNTIME) 16 | @Constraint(validatedBy = DateStringValidator.class) 17 | @Documented 18 | public @interface DateFormat { 19 | String message() default "Date format should be like yyyy-MM-dd"; 20 | 21 | Class[] groups() default {}; 22 | 23 | Class[] payload() default {}; 24 | } 25 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/validation/annotation/KorEngOnly.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.validation.annotation; 2 | 3 | import net.mureng.api.core.validation.KorEngOnlyValidator; 4 | 5 | import javax.validation.Constraint; 6 | import javax.validation.Payload; 7 | import java.lang.annotation.Documented; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.Target; 10 | 11 | import static java.lang.annotation.ElementType.*; 12 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 13 | 14 | @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) 15 | @Retention(RUNTIME) 16 | @Constraint(validatedBy = KorEngOnlyValidator.class) 17 | @Documented 18 | public @interface KorEngOnly { 19 | String message() default "한글, 숫자, 영어만 가능합니다."; 20 | 21 | Class[] groups() default {}; 22 | 23 | Class[] payload() default {}; 24 | } 25 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/core/validation/annotation/TimeFormat.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.validation.annotation; 2 | 3 | import net.mureng.api.core.validation.TimeStringValidator; 4 | 5 | import javax.validation.Constraint; 6 | import javax.validation.Payload; 7 | import java.lang.annotation.Documented; 8 | import java.lang.annotation.Retention; 9 | import java.lang.annotation.Target; 10 | 11 | import static java.lang.annotation.ElementType.*; 12 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 13 | 14 | @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) 15 | @Retention(RUNTIME) 16 | @Constraint(validatedBy = TimeStringValidator.class) 17 | @Documented 18 | public @interface TimeFormat { 19 | String message() default "Time format should be like hh:mm:ss"; 20 | 21 | Class[] groups() default {}; 22 | 23 | Class[] payload() default {}; 24 | } 25 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/member/component/MemberAchievementMapper.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.member.component; 2 | 3 | import net.mureng.api.badge.component.BadgeMapper; 4 | import net.mureng.api.member.dto.MemberAchievementDto; 5 | import net.mureng.core.badge.entity.Badge; 6 | import net.mureng.core.member.entity.Member; 7 | import org.mapstruct.Mapper; 8 | import org.mapstruct.Mapping; 9 | 10 | import java.util.List; 11 | 12 | @Mapper(componentModel = "spring", uses = {MemberMapper.class, BadgeMapper.class}) 13 | public interface MemberAchievementMapper { 14 | 15 | @Mapping(target = "requesterProfile", expression = "java(member.isRequesterProfile(loggedInMember.getMemberId()))") 16 | MemberAchievementDto toDto(Member member, List badges, Member loggedInMember); 17 | } 18 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/member/component/MemberMapper.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.member.component; 2 | 3 | import net.mureng.api.member.dto.MemberDto; 4 | import net.mureng.core.member.entity.Member; 5 | import org.mapstruct.Mapper; 6 | import org.mapstruct.Mapping; 7 | import org.mapstruct.NullValuePropertyMappingStrategy; 8 | import org.mapstruct.ReportingPolicy; 9 | 10 | import java.time.format.DateTimeFormatter; 11 | 12 | @Mapper(componentModel = "spring", 13 | unmappedTargetPolicy = ReportingPolicy.IGNORE, 14 | imports = DateTimeFormatter.class) 15 | public interface MemberMapper { 16 | @Mapping(target = "murengCount", expression = "java(member.getMurengCookies().isEmpty() ? 0 : member.getMurengCookies().size())") 17 | @Mapping(target = "attendanceCount", expression = "java(member.getMemberAttendance().getAttendanceCount())") 18 | @Mapping(target = "lastAttendanceDate", expression = "java(member.getMemberAttendance().getLastAttendanceDate()" + 19 | ".format(DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")) )") 20 | MemberDto.ReadOnly toDto(Member member); 21 | 22 | Member toEntity(MemberDto memberDto); 23 | } 24 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/member/component/MemberScrapMapper.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.member.component; 2 | 3 | import net.mureng.api.member.dto.MemberScrapDto; 4 | import net.mureng.api.todayexpression.component.TodayExpressionMapper; 5 | import net.mureng.core.member.entity.Member; 6 | import net.mureng.core.todayexpression.entity.UsefulExpression; 7 | import org.mapstruct.Context; 8 | import org.mapstruct.Mapper; 9 | import org.mapstruct.Mapping; 10 | 11 | import java.util.List; 12 | 13 | @Mapper(componentModel = "spring", uses = { MemberMapper.class, TodayExpressionMapper.class}) 14 | public interface MemberScrapMapper { 15 | 16 | @Mapping(source = "member", target = "member") 17 | @Mapping(source = "scrapList", target = "scrapList") 18 | @Mapping(target = "requesterProfile", expression = "java(member.isRequesterProfile(loggedInMember.getMemberId()))") 19 | MemberScrapDto toDto(Member member, List scrapList, @Context Member loggedInMember); 20 | } 21 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/member/dto/MemberAchievementDto.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.member.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import io.swagger.annotations.ApiModel; 6 | import io.swagger.annotations.ApiModelProperty; 7 | import lombok.*; 8 | import net.mureng.api.badge.dto.BadgeDto; 9 | 10 | import java.util.List; 11 | 12 | @Builder 13 | @Getter @Setter 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @ApiModel(value="사용자 성과 모델", description="사용자 프로필 성과 모델") 17 | public class MemberAchievementDto { 18 | @ApiModelProperty(value = "사용자 정보") 19 | @JsonProperty(index = PropertyDisplayOrder.MEMBER) 20 | private MemberDto member; 21 | 22 | @ApiModelProperty(value = "사용자 뱃지 목록") 23 | @JsonProperty(index = PropertyDisplayOrder.BADGES) 24 | private List badges; 25 | 26 | @ApiModelProperty(value = "요청자의 프로필인지") 27 | @JsonProperty(index = PropertyDisplayOrder.REQUESTER_PROFILE) 28 | private boolean requesterProfile; 29 | 30 | private static class PropertyDisplayOrder { 31 | private static final int MEMBER = 0; 32 | private static final int BADGES = 1; 33 | private static final int REQUESTER_PROFILE = 2; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/member/dto/MemberScrapDto.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.member.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.*; 7 | import net.mureng.api.todayexpression.dto.UsefulExpressionDto; 8 | 9 | import java.util.List; 10 | 11 | @Builder 12 | @Getter @Setter 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | @ApiModel(value="사용자 스크랩 모델", description="사용자 프로필 스크랩 모델") 16 | public class MemberScrapDto { 17 | @ApiModelProperty(value = "사용자 정보") 18 | @JsonProperty(index = PropertyDisplayOrder.MEMBER) 19 | private MemberDto member; 20 | 21 | @ApiModelProperty(value = "스크랩 목록") 22 | @JsonProperty(index = PropertyDisplayOrder.SCRAPLIST) 23 | private List scrapList; 24 | 25 | @ApiModelProperty(value = "요청자의 프로필인지") 26 | @JsonProperty(index = PropertyDisplayOrder.REQUESTER_PROFILE) 27 | private boolean requesterProfile; 28 | 29 | private static class PropertyDisplayOrder { 30 | private static final int MEMBER = 0; 31 | private static final int SCRAPLIST = 1; 32 | private static final int REQUESTER_PROFILE = 2; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/member/service/MemberImageService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.member.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.mureng.api.core.component.FileUploader; 5 | import org.springframework.beans.factory.annotation.Value; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.web.multipart.MultipartFile; 8 | 9 | import javax.annotation.PostConstruct; 10 | 11 | @Service 12 | @RequiredArgsConstructor 13 | public class MemberImageService { 14 | private final FileUploader fileUploader; 15 | 16 | @Value("${media.base.dir.name}") 17 | private String mediaBaseDirName; 18 | private String memberImageDirName = "/member"; 19 | 20 | @PostConstruct 21 | protected void init() { 22 | memberImageDirName = mediaBaseDirName + memberImageDirName; 23 | } 24 | 25 | /** 26 | * Multipart File 을 저장하고, 저장 경로를 리턴한다. 27 | * 28 | * @param imageFile 요청으로 들어온 Multipart File 29 | * @return 웹상에서 저장된 경로 30 | */ 31 | public String uploadMemberImageFile(MultipartFile imageFile) { 32 | return fileUploader.uploadMultiPartFile(imageFile, memberImageDirName) 33 | .replace(mediaBaseDirName, ""); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/member/service/UserDetailsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.member.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.mureng.api.core.dto.UserDetailsImpl; 5 | import net.mureng.core.member.entity.Member; 6 | import net.mureng.core.member.repository.MemberRepository; 7 | import net.mureng.core.member.service.MemberService; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.security.core.userdetails.UserDetailsService; 10 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 11 | import org.springframework.stereotype.Service; 12 | import org.springframework.transaction.annotation.Transactional; 13 | 14 | @RequiredArgsConstructor 15 | @Service("userDetailsService") 16 | public class UserDetailsServiceImpl implements UserDetailsService { 17 | private final MemberService memberService; 18 | 19 | @Override 20 | @Transactional(readOnly = true) 21 | public UserDetails loadUserByUsername(String identifier) throws UsernameNotFoundException { 22 | Member member = memberService.findByIdentifier(identifier); 23 | 24 | return new UserDetailsImpl(member); 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/question/component/QuestionMapper.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.question.component; 2 | 3 | import net.mureng.api.member.component.MemberMapper; 4 | import net.mureng.core.member.entity.Member; 5 | import net.mureng.api.question.dto.QuestionDto; 6 | import net.mureng.core.question.entity.Question; 7 | import org.mapstruct.Mapper; 8 | import org.mapstruct.Mapping; 9 | import org.mapstruct.NullValuePropertyMappingStrategy; 10 | import org.mapstruct.ReportingPolicy; 11 | 12 | @Mapper(componentModel = "spring", 13 | unmappedTargetPolicy = ReportingPolicy.IGNORE, 14 | uses = { WordHintMapper.class, MemberMapper.class }) 15 | public interface QuestionMapper { 16 | @Mapping(target = "repliesCount", expression = "java(question.getReplies().size())") 17 | QuestionDto.ReadOnly toDto(Question question); 18 | 19 | @Mapping(target = "regDate", ignore = true) 20 | Question toEntity(QuestionDto questionDto, Member author); 21 | } 22 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/question/component/WordHintMapper.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.question.component; 2 | 3 | import net.mureng.api.question.dto.WordHintDto; 4 | import net.mureng.core.question.entity.WordHint; 5 | import org.mapstruct.Mapper; 6 | import org.mapstruct.Mapping; 7 | import org.mapstruct.NullValuePropertyMappingStrategy; 8 | import org.mapstruct.ReportingPolicy; 9 | 10 | @Mapper(componentModel = "spring", 11 | unmappedTargetPolicy = ReportingPolicy.IGNORE) 12 | public interface WordHintMapper { 13 | WordHintDto.ReadOnly toDto(WordHint wordHint); 14 | 15 | WordHint toEntity(WordHintDto wordHintDto); 16 | } 17 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/question/dto/WordHintDto.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.question.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import io.swagger.annotations.ApiModel; 5 | import io.swagger.annotations.ApiModelProperty; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | import lombok.Setter; 10 | import lombok.experimental.SuperBuilder; 11 | 12 | @SuperBuilder 13 | @Getter @Setter 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @ApiModel(value="단어 힌트 모델", description="단어 힌트를 나타내는 모델") 17 | public class WordHintDto { 18 | @ApiModelProperty(value = "단어", position = PropertyDisplayOrder.WORD) 19 | @JsonProperty(index = PropertyDisplayOrder.WORD) 20 | private String word; 21 | 22 | @ApiModelProperty(value = "뜻", position = PropertyDisplayOrder.MEANING) 23 | @JsonProperty(index = PropertyDisplayOrder.MEANING) 24 | private String meaning; 25 | 26 | @SuperBuilder 27 | @Getter @Setter 28 | @NoArgsConstructor 29 | @AllArgsConstructor 30 | @ApiModel(value="단어 힌트 읽기 모델", description="단어 힌트를 나타내는 읽기 모델") 31 | public static class ReadOnly extends WordHintDto { 32 | @ApiModelProperty(value = "단어 힌트 기본키", accessMode = ApiModelProperty.AccessMode.READ_ONLY, 33 | position = PropertyDisplayOrder.HINT_ID) 34 | @JsonProperty(index = PropertyDisplayOrder.HINT_ID) 35 | private Long hintId; 36 | } 37 | 38 | private static class PropertyDisplayOrder { 39 | private static final int HINT_ID = 0; 40 | private static final int WORD = 1; 41 | private static final int MEANING = 2; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/question/service/QuestionPaginationService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.question.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.mureng.api.core.dto.ApiPageRequest; 5 | import net.mureng.core.question.entity.Question; 6 | import net.mureng.core.question.repository.QuestionRepository; 7 | import org.springframework.data.domain.Page; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | @Service 12 | @RequiredArgsConstructor 13 | public class QuestionPaginationService { 14 | private final QuestionRepository questionRepository; 15 | 16 | @Transactional(readOnly = true) 17 | public Page getQuestionList(ApiPageRequest pageRequest) { 18 | if(pageRequest.getSort() == ApiPageRequest.PageSort.POPULAR) 19 | return questionRepository.findAllOrderByRepliesSizeDesc(pageRequest.convert()); 20 | 21 | return questionRepository.findAll(pageRequest.convertWithNewestSort()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/reply/component/ReplyLikesMapper.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.reply.component; 2 | 3 | import net.mureng.api.member.component.MemberMapper; 4 | import net.mureng.api.reply.dto.ReplyLikesDto; 5 | import net.mureng.core.reply.entity.ReplyLikes; 6 | import org.mapstruct.*; 7 | 8 | @Mapper(componentModel = "spring", 9 | unmappedTargetPolicy = ReportingPolicy.IGNORE, 10 | uses = { ReplyMapper.class, MemberMapper.class }) 11 | public interface ReplyLikesMapper { 12 | 13 | @Mapping(source = "replyLikes.member.memberId", target = "memberId") 14 | @Mapping(source = "replyLikes.reply.replyId", target = "replyId") 15 | ReplyLikesDto toDto(ReplyLikes replyLikes); 16 | } 17 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/reply/dto/ReplyLikesDto.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.reply.dto; 2 | 3 | import io.swagger.annotations.ApiModel; 4 | import io.swagger.annotations.ApiModelProperty; 5 | import lombok.*; 6 | 7 | @Builder 8 | @Getter @Setter 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @ApiModel(value="답변 좋아요 모델", description="답변 좋아요를 나타내는 모델") 12 | public class ReplyLikesDto { 13 | @ApiModelProperty(value = "사용자 id", position = PropertyDisplayOrder.MEMBER_ID) 14 | private Long memberId; 15 | 16 | @ApiModelProperty(value = "답변 id", position = PropertyDisplayOrder.REPLY_ID) 17 | private Long replyId; 18 | 19 | @Builder.Default 20 | @ApiModelProperty(value = "좋아요", position = PropertyDisplayOrder.LIKES) 21 | private boolean likes = true; 22 | 23 | private static class PropertyDisplayOrder { 24 | private static final int MEMBER_ID = 0; 25 | private static final int REPLY_ID = 1; 26 | private static final int LIKES = 2; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/reply/service/ReplyImageService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.reply.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.mureng.api.core.component.FileUploader; 5 | import net.mureng.core.core.component.DirectoryScanner; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.web.multipart.MultipartFile; 9 | 10 | import javax.annotation.PostConstruct; 11 | import java.util.List; 12 | import java.util.stream.Collectors; 13 | 14 | @Service 15 | @RequiredArgsConstructor 16 | public class ReplyImageService { 17 | private final FileUploader fileUploader; 18 | private final DirectoryScanner directoryScanner; 19 | 20 | @Value("${media.base.dir.name}") 21 | private String mediaBaseDirName; 22 | private String replyImageDirName = "/reply"; 23 | private String replyDefaultImageDirName = "/default"; 24 | 25 | @PostConstruct 26 | protected void init() { 27 | replyImageDirName = mediaBaseDirName + replyImageDirName; 28 | replyDefaultImageDirName = replyImageDirName + replyDefaultImageDirName; 29 | } 30 | 31 | /** 32 | * Multipart File 을 저장하고, 저장 경로를 리턴한다. 33 | * 34 | * @param imageFile 요청으로 들어온 Multipart File 35 | * @return 웹상에서 저장된 경로 36 | */ 37 | public String uploadReplyImageFile(MultipartFile imageFile) { 38 | return fileUploader.uploadMultiPartFile(imageFile, replyImageDirName) 39 | .replace(mediaBaseDirName, ""); 40 | } 41 | 42 | public List getReplyDefaultImageList() { 43 | return directoryScanner.scanFileListInDirectory(replyDefaultImageDirName).stream() 44 | .map(x -> x.replace(mediaBaseDirName, "")) 45 | .collect(Collectors.toList()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/reply/service/ReplyPaginationService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.reply.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.mureng.api.core.dto.ApiPageRequest; 5 | import net.mureng.core.reply.entity.Reply; 6 | import net.mureng.core.reply.repository.ReplyRepository; 7 | import org.springframework.data.domain.Page; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | @Service 12 | @RequiredArgsConstructor 13 | public class ReplyPaginationService { 14 | private final ReplyRepository replyRepository; 15 | 16 | @Transactional(readOnly = true) 17 | public Page findRepliesByQuestionId(Long questionId, ApiPageRequest pageRequest) { 18 | if (pageRequest.getSort() == ApiPageRequest.PageSort.POPULAR) 19 | return replyRepository.findAllByQuestionQuestionIdOrderByReplyLikesSize(questionId, pageRequest.convert()); 20 | 21 | return replyRepository.findAllByQuestionQuestionId(questionId, pageRequest.convertWithNewestSort()); 22 | } 23 | 24 | @Transactional(readOnly = true) 25 | public Page findReplies(ApiPageRequest pageRequest) { 26 | if (pageRequest.getSort() == ApiPageRequest.PageSort.POPULAR) 27 | return replyRepository.findAllByOrderByReplyLikesSize(pageRequest.convert()); 28 | 29 | return replyRepository.findAll(pageRequest.convertWithNewestSort()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/reply/web/ReplyImageController.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.reply.web; 2 | 3 | import io.swagger.annotations.Api; 4 | import io.swagger.annotations.ApiOperation; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.RequiredArgsConstructor; 8 | import net.mureng.api.core.dto.ApiResult; 9 | import net.mureng.api.reply.service.ReplyImageService; 10 | import org.springframework.http.MediaType; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.web.bind.annotation.*; 13 | import org.springframework.web.multipart.MultipartFile; 14 | 15 | import java.util.List; 16 | 17 | @Api(value = "답변 이미지 관련 엔드포인트") 18 | @RestController 19 | @RequiredArgsConstructor 20 | @RequestMapping("/api/reply") 21 | public class ReplyImageController { 22 | private final ReplyImageService replyImageService; 23 | 24 | @ApiOperation(value = "답변 이미지 업로드", notes = "답변에 적용될 이미지를 업로드합니다.") 25 | @PostMapping(value = "/image", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE}) 26 | public ResponseEntity> postImage( 27 | @RequestParam("image") MultipartFile image) { 28 | return ResponseEntity.ok(ApiResult.ok(new ImagePathDto(replyImageService.uploadReplyImageFile(image)))); 29 | } 30 | 31 | @ApiOperation(value = "기본 이미지 목록 조회", notes = "기본 제공되는 이미지 경로 목록을 제공합니다.") 32 | @GetMapping(value = "/default-images") 33 | public ResponseEntity>> getDefaultImages() { 34 | return ResponseEntity.ok(ApiResult.ok( 35 | replyImageService.getReplyDefaultImageList() 36 | )); 37 | } 38 | 39 | @Getter 40 | @AllArgsConstructor 41 | public static class ImagePathDto { 42 | private final String imagePath; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/reply/web/ReplyLikesController.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.reply.web; 2 | 3 | import io.swagger.annotations.Api; 4 | import io.swagger.annotations.ApiOperation; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.RequiredArgsConstructor; 8 | import net.mureng.api.core.annotation.CurrentUser; 9 | import net.mureng.api.core.dto.ApiResult; 10 | import net.mureng.api.reply.component.ReplyLikesMapper; 11 | import net.mureng.core.member.entity.Member; 12 | import net.mureng.core.reply.service.ReplyLikesService; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.web.bind.annotation.*; 15 | import springfox.documentation.annotations.ApiIgnore; 16 | 17 | @Api(value = "답변 좋아요 엔드포인트") 18 | @RestController 19 | @RequiredArgsConstructor 20 | @RequestMapping("/api/reply") 21 | public class ReplyLikesController { 22 | 23 | private final ReplyLikesService replyLikesService; 24 | private final ReplyLikesMapper replyLikesMapper; 25 | 26 | @ApiOperation(value = "답변 좋아요", notes = "해당 답변에 좋아요를 추가합니다.") 27 | @PostMapping("/{replyId}/reply-likes") 28 | public ResponseEntity postReplyLikes(@CurrentUser Member member, @PathVariable Long replyId){ 29 | return ResponseEntity.ok(ApiResult.ok( 30 | replyLikesMapper.toDto( 31 | replyLikesService.postReplyLikes(member, replyId) 32 | ) 33 | )); 34 | } 35 | 36 | @ApiOperation(value = "답변 좋아요 취소", notes = "해당 답변에 좋아요를 취소합니다.") 37 | @DeleteMapping("/{replyId}/reply-likes") 38 | public ResponseEntity> deleteReplyLikes(@CurrentUser Member member, @PathVariable Long replyId){ 39 | replyLikesService.deleteReplyLikes(member, replyId); 40 | return ResponseEntity.ok(ApiResult.ok(new DeletedDto(true))); 41 | } 42 | 43 | @ApiIgnore 44 | @AllArgsConstructor 45 | @Getter 46 | public static class DeletedDto{ 47 | private final boolean deleted; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/todayexpression/component/TodayExpressionMapper.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.todayexpression.component; 2 | 3 | import net.mureng.api.todayexpression.dto.UsefulExpressionDto; 4 | import net.mureng.core.member.entity.Member; 5 | import net.mureng.core.todayexpression.entity.UsefulExpression; 6 | import org.mapstruct.Context; 7 | import org.mapstruct.Mapper; 8 | import org.mapstruct.Mapping; 9 | 10 | @Mapper(componentModel = "spring") 11 | public interface TodayExpressionMapper { 12 | 13 | @Mapping(target = "scrappedByRequester", expression = "java(usefulExpression.scrappedByRequester(loggedInMember))") 14 | UsefulExpressionDto toDto(UsefulExpression usefulExpression, @Context Member loggedInMember); 15 | } 16 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/todayexpression/web/TodayExpressionController.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.todayexpression.web; 2 | 3 | import io.swagger.annotations.Api; 4 | import io.swagger.annotations.ApiOperation; 5 | import lombok.RequiredArgsConstructor; 6 | import net.mureng.api.core.annotation.CurrentUser; 7 | import net.mureng.api.core.dto.ApiPageResult; 8 | import net.mureng.api.core.dto.ApiResult; 9 | import net.mureng.api.todayexpression.component.TodayExpressionMapper; 10 | import net.mureng.api.todayexpression.dto.UsefulExpressionDto; 11 | import net.mureng.core.member.entity.Member; 12 | import net.mureng.core.todayexpression.service.UsefulExpressionService; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.web.bind.annotation.GetMapping; 15 | import org.springframework.web.bind.annotation.RequestMapping; 16 | import org.springframework.web.bind.annotation.RestController; 17 | 18 | import java.util.List; 19 | import java.util.stream.Collectors; 20 | 21 | @Api(value = "오늘의 표현 엔드포인트") 22 | @RestController 23 | @RequiredArgsConstructor 24 | @RequestMapping("/api/today-expression") 25 | public class TodayExpressionController { 26 | private final UsefulExpressionService usefulExpressionService; 27 | private final TodayExpressionMapper todayExpressionMapper; 28 | 29 | @ApiOperation(value = "오늘의 표현 가져오기", notes = "오늘의 표현을 가져옵니다.") 30 | @GetMapping 31 | public ResponseEntity>> getTodayExpressions(@CurrentUser Member member){ 32 | return ResponseEntity.ok(ApiResult.ok( 33 | usefulExpressionService.getTodayExpressions().stream() 34 | .map(x -> todayExpressionMapper.toDto(x, member)) 35 | .collect(Collectors.toList()) 36 | )); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/web/ErrorControllerImpl.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.web; 2 | 3 | import net.mureng.api.core.dto.ApiResult; 4 | import org.springframework.boot.web.servlet.error.ErrorController; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | import springfox.documentation.annotations.ApiIgnore; 10 | 11 | import javax.servlet.RequestDispatcher; 12 | import javax.servlet.http.HttpServletRequest; 13 | 14 | @ApiIgnore 15 | @RestController 16 | public class ErrorControllerImpl implements ErrorController { 17 | 18 | @RequestMapping("/error") 19 | public ResponseEntity error(HttpServletRequest request) { 20 | Object statusObj = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); 21 | if (statusObj != null) { 22 | int statusCode = Integer.parseInt(statusObj.toString()); 23 | HttpStatus status = HttpStatus.valueOf(statusCode); 24 | return ResponseEntity.status(status) 25 | .body(ApiResult.fail(status.name())); 26 | } 27 | 28 | return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) 29 | .body(ApiResult.fail("Internal Server Error")); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/web/HomeController.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.web; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import net.mureng.api.core.dto.ApiResult; 7 | import net.mureng.core.core.exception.MurengException; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RestController; 12 | import springfox.documentation.annotations.ApiIgnore; 13 | 14 | @ApiIgnore 15 | @RestController 16 | @RequestMapping("/api") 17 | public class HomeController { 18 | 19 | @GetMapping("/test") 20 | public ResponseEntity> test() { 21 | return ResponseEntity.ok(ApiResult.ok(new TestObject("test is ok!!!"))); 22 | } 23 | 24 | @GetMapping("/authenticated-test") 25 | public ResponseEntity> authenticatedTest() { 26 | return ResponseEntity.ok(ApiResult.ok(new TestObject("test is ok!!!"))); 27 | } 28 | 29 | @GetMapping("/test-failure") 30 | public ResponseEntity> testFailure() { 31 | throw new MurengException("net.mureng.mureng failure"); 32 | } 33 | 34 | @AllArgsConstructor 35 | @Getter @Setter 36 | public static class TestObject { 37 | private String result; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /mureng-api/src/main/java/net/mureng/api/web/ProbeController.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.web; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import net.mureng.api.core.dto.ApiResult; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | import springfox.documentation.annotations.ApiIgnore; 12 | 13 | @ApiIgnore 14 | @RestController 15 | @RequestMapping("/") // HealthCheck를 위한 것으로, 큰 의미 없음!! 16 | public class ProbeController { 17 | 18 | @GetMapping("") 19 | public ResponseEntity> test() { 20 | return ResponseEntity.ok(ApiResult.ok(new TestObject("test is ok!!!"))); 21 | } 22 | 23 | @AllArgsConstructor 24 | @Getter @Setter 25 | public static class TestObject { 26 | private String result; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/MurengApiApplicationTests.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class MurengApiApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/annotation/WithMockMurengUser.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.annotation; 2 | 3 | import net.mureng.api.config.WithMockMurengUserSecurityContextFactory; 4 | import org.springframework.security.test.context.support.WithSecurityContext; 5 | 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | 9 | @Retention(RetentionPolicy.RUNTIME) 10 | @WithSecurityContext(factory = WithMockMurengUserSecurityContextFactory.class) 11 | public @interface WithMockMurengUser { 12 | } 13 | -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/badge/BadgeMapperTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.badge; 2 | 3 | import net.mureng.api.badge.component.BadgeMapper; 4 | import net.mureng.api.badge.dto.BadgeDto; 5 | import net.mureng.api.common.DtoCreator; 6 | import net.mureng.core.badge.entity.Badge; 7 | import net.mureng.core.common.EntityCreator; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | 14 | @SpringBootTest 15 | public class BadgeMapperTest { 16 | 17 | @Autowired 18 | private BadgeMapper badgeMapper; 19 | 20 | private static final Badge badge = EntityCreator.createBadgeEntity(); 21 | private static final BadgeDto badgeDto = DtoCreator.createBadgeDto(); 22 | 23 | @Test 24 | public void 엔티티에서_DTO변환_테스트() { 25 | BadgeDto mappedDto = badgeMapper.toDto(badge); 26 | 27 | assertEquals(mappedDto.getName(), badgeDto.getName()); 28 | assertEquals(mappedDto.getContent(), badgeDto.getContent()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/core/component/FileSystemFileUploaderTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.component; 2 | 3 | import org.junit.jupiter.api.AfterEach; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.mock.web.MockMultipartFile; 7 | 8 | import java.io.File; 9 | 10 | import static org.junit.jupiter.api.Assertions.*; 11 | 12 | class FileSystemFileUploaderTest { 13 | private final FileSystemFileUploader fileUploader = new FileSystemFileUploader(); 14 | private String filePath = ""; 15 | 16 | @Test 17 | public void 파일_저장_테스트() { 18 | // given 19 | MockMultipartFile mockMultipartFile = new MockMultipartFile( 20 | "file", 21 | "hello.txt", 22 | MediaType.TEXT_PLAIN_VALUE, 23 | "Hello, World!".getBytes() 24 | ); 25 | 26 | // when 27 | filePath = fileUploader.uploadMultiPartFile(mockMultipartFile, "./"); 28 | 29 | // then 30 | File testFile = new File(filePath); 31 | assertTrue(testFile.exists()); 32 | } 33 | 34 | @AfterEach 35 | public void tearDown() { 36 | File testFile = new File(filePath); 37 | if (testFile.exists()) 38 | testFile.delete(); 39 | } 40 | } -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/core/jwt/component/JwtCreatorTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.jwt.component; 2 | 3 | import net.mureng.core.common.EntityCreator; 4 | import net.mureng.core.core.component.DateFactory; 5 | import org.junit.jupiter.api.BeforeEach; 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.test.util.ReflectionTestUtils; 12 | 13 | import java.util.Calendar; 14 | import java.util.Locale; 15 | import java.util.TimeZone; 16 | 17 | import static org.junit.jupiter.api.Assertions.*; 18 | import static org.mockito.Mockito.when; 19 | 20 | class JwtCreatorTest extends JwtTest { 21 | @InjectMocks 22 | private JwtCreator jwtCreator; 23 | 24 | @Mock 25 | private DateFactory dateFactory; 26 | 27 | @BeforeEach 28 | void setUp() { 29 | ReflectionTestUtils.setField(jwtCreator, "secretKey", TEST_SECRET_KEY); 30 | Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("Asia/Seoul"), Locale.KOREA); 31 | calendar.set(2080, Calendar.OCTOBER, 21, 11, 58, 30); 32 | when(dateFactory.now()).thenReturn(calendar.getTime()); 33 | } 34 | 35 | @Test 36 | public void 액세스_토큰_생성_테스트() { 37 | assertEquals(TEST_ACCESS_TOKEN, jwtCreator.createAccessToken(EntityCreator.createMemberEntity()));; 38 | } 39 | 40 | @Test 41 | public void 리프레시_토큰_생성_테스트() { 42 | assertEquals(TEST_REFRESH_TOKEN, jwtCreator.createRefreshToken(EntityCreator.createMemberEntity()));; 43 | } 44 | } -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/core/jwt/component/JwtTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.jwt.component; 2 | 3 | import net.mureng.core.core.component.DateFactory; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.extension.ExtendWith; 6 | import org.mockito.InjectMocks; 7 | import org.mockito.Mock; 8 | import org.mockito.junit.jupiter.MockitoExtension; 9 | import org.springframework.test.util.ReflectionTestUtils; 10 | 11 | import java.util.Calendar; 12 | 13 | import static org.mockito.Mockito.when; 14 | 15 | /** 16 | * 2080 년과 같이 먼 미래로 설정한 이유는 JWT Claim 파싱 시 유효기간이 지나면 ExpiredJwtException을 발생시키기 때문 17 | */ 18 | @ExtendWith(MockitoExtension.class) 19 | abstract class JwtTest { 20 | protected static final String TEST_SECRET_KEY = "testKey"; 21 | 22 | /** 23 | * 2080-10-21 11:58:30 기준 + 24시간 생성 24 | */ 25 | protected static final String TEST_ACCESS_TOKEN = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxMjMiLCJuaWNrbmFtZSI6IlRlc3QiL" + 26 | "CJpYXQiOjM0OTY3MDUxMTAsImV4cCI6MzQ5Njc5MTUxMH0.zzKLUCSfCPgseM9wbuTOFnKWTI6rSRzkL0BBruBe6P93iKjxh4y6vxAMV" + 27 | "zF_LOZqdCKCUF9WWUnNX--xru1EZQ"; 28 | 29 | /** 30 | * 2080-10-21 11:58:30 기준 + 2달 생성 31 | */ 32 | protected static final String TEST_REFRESH_TOKEN = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxMjMiLCJuaWNrbmFtZSI6IlRlc3Qi" + 33 | "LCJpYXQiOjM0OTY3MDUxMTAsImV4cCI6MzUwMTg4OTExMH0.rRluGJ0MlzXCMAZcgEcHhW9h-QgqEtAegjk_CXDn2HK_hJihly1Xkr4u" + 34 | "m9CgpmTpQOFS5RHqBrF12FdaXFyz0A"; 35 | } -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/core/util/NicknameNormalizerTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.util; 2 | 3 | import net.mureng.api.member.dto.MemberDto; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.*; 7 | 8 | class NicknameNormalizerTest { 9 | @Test 10 | void 닉네임_정규화_테스트() { 11 | assertEquals("최대여섯글자", NicknameNormalizer.normalize("최대여섯글자")); 12 | assertEquals("최대여섯글자", NicknameNormalizer.normalize("최대여섯글자초과")); 13 | assertEquals("MaximumNickn", NicknameNormalizer.normalize("MaximumNickn")); 14 | assertEquals("MaximumNickn", NicknameNormalizer.normalize("MaximumNickname")); 15 | assertEquals("최대여섯Nick", NicknameNormalizer.normalize("최대여섯Nickname")); 16 | assertEquals("최대여섯Na12", NicknameNormalizer.normalize("최대여섯Na123456")); 17 | assertEquals("최N대i여c섯k", NicknameNormalizer.normalize("최N대i여c섯k글n자ame")); 18 | } 19 | } -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/core/validation/AbstractValidatorTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.validation; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | 5 | import javax.validation.Validation; 6 | import javax.validation.ValidatorFactory; 7 | import javax.validation.Validator; 8 | 9 | public abstract class AbstractValidatorTest { 10 | protected Validator validator; 11 | 12 | @BeforeEach 13 | public void setUp() { 14 | ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); 15 | validator = factory.getValidator(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/core/validation/DateStringValidatorTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.validation; 2 | 3 | 4 | import net.mureng.api.core.validation.annotation.DateFormat; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import javax.validation.ConstraintViolation; 8 | 9 | import java.util.Set; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | 13 | class DateStringValidatorTest extends AbstractValidatorTest { 14 | @Test 15 | public void 날짜문자열_검증_단순_테스트() { 16 | DateStringValidator validator = new DateStringValidator(); 17 | assertTrue(validator.isValid("2020-10-14", null)); 18 | assertFalse(validator.isValid("20202-10-14", null)); 19 | assertFalse(validator.isValid("2020-13-14", null)); 20 | } 21 | 22 | @Test 23 | public void 날짜문자열_검증_심화_테스트() { 24 | SimpleDto simpleDto = new SimpleDto("2020-10-14"); 25 | Set> violations = validator.validate(simpleDto); 26 | assertTrue(violations.isEmpty()); 27 | 28 | simpleDto = new SimpleDto("20202-10-14"); 29 | violations = validator.validate(simpleDto); 30 | assertFalse(violations.isEmpty()); 31 | 32 | simpleDto = new SimpleDto("2020-13-14"); 33 | violations = validator.validate(simpleDto); 34 | assertFalse(violations.isEmpty()); 35 | } 36 | 37 | private static class SimpleDto { 38 | SimpleDto(String dateString) { 39 | this.dateString = dateString; 40 | } 41 | 42 | @DateFormat 43 | private final String dateString; 44 | 45 | public String getDateString() { 46 | return dateString; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/core/validation/KorEngOnlyValidatorTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.validation; 2 | 3 | import net.mureng.api.core.validation.annotation.KorEngOnly; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import javax.validation.ConstraintViolation; 7 | import java.util.Set; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | class KorEngOnlyValidatorTest extends AbstractValidatorTest { 12 | @Test 13 | public void 한글영어문자열_검증_단순_테스트() { 14 | KorEngOnlyValidator validator = new KorEngOnlyValidator(); 15 | assertTrue(validator.isValid("한글만된다", null)); 16 | assertTrue(validator.isValid("canbeenglish", null)); 17 | assertTrue(validator.isValid("Canbeboth둘다된다", null)); 18 | assertTrue(validator.isValid("한글english1234", null)); 19 | assertFalse(validator.isValid("얘는안된다;;", null)); 20 | assertFalse(validator.isValid("notthistoo~.~", null)); 21 | assertFalse(validator.isValid("해보니까 공백넣는 것도 안됨", null)); 22 | } 23 | 24 | @Test 25 | public void 한글영어문자열_검증_심화_테스트() { 26 | SimpleDto simpleDto = new SimpleDto("한글english1234"); 27 | Set> violations = validator.validate(simpleDto); 28 | assertTrue(violations.isEmpty()); 29 | 30 | simpleDto = new SimpleDto("얘는 안된다;;"); 31 | violations = validator.validate(simpleDto); 32 | assertFalse(violations.isEmpty()); 33 | 34 | simpleDto = new SimpleDto("not this too ~.~"); 35 | violations = validator.validate(simpleDto); 36 | assertFalse(violations.isEmpty()); 37 | } 38 | 39 | private static class SimpleDto { 40 | SimpleDto(String value) { 41 | this.value = value; 42 | } 43 | 44 | @KorEngOnly 45 | private final String value; 46 | 47 | public String getValue() { 48 | return value; 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/core/validation/TimeStringValidatorTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.core.validation; 2 | 3 | 4 | import net.mureng.api.core.validation.annotation.TimeFormat; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import javax.validation.ConstraintViolation; 8 | import java.util.Set; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertFalse; 11 | import static org.junit.jupiter.api.Assertions.assertTrue; 12 | 13 | class TimeStringValidatorTest extends AbstractValidatorTest { 14 | @Test 15 | public void 시간문자열_검증_단순_테스트() { 16 | TimeStringValidator validator = new TimeStringValidator(); 17 | assertTrue(validator.isValid("11:23:34", null)); 18 | assertFalse(validator.isValid("111:23:34", null)); 19 | assertFalse(validator.isValid("23:61:44", null)); 20 | } 21 | 22 | @Test 23 | public void 시간문자열_검증_심화_테스트() { 24 | SimpleDto simpleDto = new SimpleDto("11:23:34"); 25 | Set> violations = validator.validate(simpleDto); 26 | assertTrue(violations.isEmpty()); 27 | 28 | simpleDto = new SimpleDto("111:23:34"); 29 | violations = validator.validate(simpleDto); 30 | assertFalse(violations.isEmpty()); 31 | 32 | simpleDto = new SimpleDto("23:61:44"); 33 | violations = validator.validate(simpleDto); 34 | assertFalse(violations.isEmpty()); 35 | } 36 | 37 | private static class SimpleDto { 38 | SimpleDto(String timeString) { 39 | this.timeString = timeString; 40 | } 41 | 42 | @TimeFormat 43 | private final String timeString; 44 | 45 | public String getTimeString() { 46 | return timeString; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/member/component/MemberAchievementMapperTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.member.component; 2 | 3 | import net.mureng.api.badge.dto.BadgeDto; 4 | import net.mureng.api.common.DtoCreator; 5 | import net.mureng.api.member.dto.MemberAchievementDto; 6 | import net.mureng.api.member.dto.MemberDto; 7 | import net.mureng.core.badge.entity.Badge; 8 | import net.mureng.core.badge.service.BadgeAccomplishedServiceImpl; 9 | import net.mureng.core.common.EntityCreator; 10 | import net.mureng.core.member.entity.Member; 11 | import org.junit.jupiter.api.Test; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | 15 | import java.util.Arrays; 16 | import java.util.List; 17 | 18 | import static org.junit.jupiter.api.Assertions.assertEquals; 19 | import static org.junit.jupiter.api.Assertions.assertTrue; 20 | 21 | 22 | @SpringBootTest 23 | public class MemberAchievementMapperTest { 24 | 25 | @Autowired 26 | private MemberAchievementMapper memberAchievementMapper; 27 | 28 | private static final List badges = Arrays.asList(EntityCreator.createBadgeEntity(), EntityCreator.createBadgeEntity()); 29 | private static final List badgeDtos = Arrays.asList(DtoCreator.createBadgeDto(), DtoCreator.createBadgeDto()); 30 | 31 | private static final Member member = EntityCreator.createMemberEntity(); 32 | private static final MemberDto memberDto = DtoCreator.createMemberDto(); 33 | 34 | @Test 35 | public void 엔티티에서_DTO변환_테스트() { 36 | MemberAchievementDto mappedDto = memberAchievementMapper.toDto(member, badges, member); 37 | 38 | assertEquals(mappedDto.getMember().getEmail(), memberDto.getEmail()); 39 | assertEquals(mappedDto.getBadges().size(), badgeDtos.size()); 40 | assertEquals(mappedDto.getBadges().get(0).getName(), badgeDtos.get(0).getName()); 41 | assertTrue(mappedDto.isRequesterProfile()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/member/component/MemberScrapMapperTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.member.component; 2 | 3 | import net.mureng.api.common.DtoCreator; 4 | import net.mureng.api.member.dto.MemberDto; 5 | import net.mureng.api.member.dto.MemberScrapDto; 6 | import net.mureng.api.todayexpression.dto.UsefulExpressionDto; 7 | import net.mureng.core.common.EntityCreator; 8 | import net.mureng.core.member.entity.Member; 9 | import net.mureng.core.todayexpression.entity.UsefulExpression; 10 | import org.junit.jupiter.api.Test; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | import static org.junit.jupiter.api.Assertions.assertEquals; 18 | import static org.junit.jupiter.api.Assertions.assertTrue; 19 | 20 | @SpringBootTest 21 | public class MemberScrapMapperTest { 22 | @Autowired 23 | private MemberScrapMapper memberScrapMapper; 24 | 25 | private static final MemberDto memberDto = DtoCreator.createMemberDto(); 26 | private static final List todayExpressionDtoList = Arrays.asList(DtoCreator.createTodayExpressionDto(), DtoCreator.createTodayExpressionDto()); 27 | private static final Member member = EntityCreator.createMemberEntity(); 28 | private static final List todayExpressionList = Arrays.asList(EntityCreator.createUsefulExpressionEntity(), EntityCreator.createUsefulExpressionEntity()); 29 | 30 | @Test 31 | public void 엔티티에서_DTO변환_테스트() { 32 | MemberScrapDto mappedDto = memberScrapMapper.toDto(member, todayExpressionList, member); 33 | assertEquals(mappedDto.getMember().getEmail(), memberDto.getEmail()); 34 | assertEquals(mappedDto.getScrapList().get(0).getExpression(), todayExpressionDtoList.get(0).getExpression()); 35 | assertTrue(mappedDto.getScrapList().get(0).isScrappedByRequester()); 36 | assertTrue(mappedDto.isRequesterProfile()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/member/web/MemberBadgeControllerTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.member.web; 2 | 3 | import net.mureng.api.annotation.WithMockMurengUser; 4 | import net.mureng.api.member.service.MemberBadgeService; 5 | import net.mureng.api.web.AbstractControllerTest; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.boot.test.mock.mockito.MockBean; 8 | import org.springframework.test.web.servlet.result.MockMvcResultMatchers; 9 | 10 | import static org.mockito.ArgumentMatchers.any; 11 | import static org.mockito.BDDMockito.given; 12 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 13 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; 14 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 16 | 17 | public class MemberBadgeControllerTest extends AbstractControllerTest { 18 | @Test 19 | @WithMockMurengUser 20 | public void 사용자_뱃지_획득_확인_수정_테스트() throws Exception { 21 | mockMvc.perform( 22 | put("/api/member/me/check/badge/{badgeId}", 1) 23 | ).andExpect(status().isOk()) 24 | .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("뱃지 획득을 확인하였습니다.")) 25 | .andDo(print()); 26 | } 27 | 28 | @Test 29 | @WithMockMurengUser 30 | public void 사용자_뱃지_획득_확인_테스트() throws Exception { 31 | mockMvc.perform( 32 | get("/api/member/me/check/badge/{badgeId}", 1) 33 | ).andExpect(status().isOk()) 34 | .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("ok")) 35 | .andExpect(MockMvcResultMatchers.jsonPath("$.data.checked").value(true)) 36 | .andDo(print()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/oauth/OAuthServiceTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.oauth; 2 | 3 | import net.mureng.api.core.jwt.dto.TokenDto; 4 | import net.mureng.api.core.jwt.dto.TokenProvider; 5 | import net.mureng.api.core.oauth2.dto.OAuth2Profile; 6 | import net.mureng.api.core.oauth2.service.OAuth2Service; 7 | import org.junit.jupiter.api.Disabled; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | 12 | @SpringBootTest 13 | @Disabled("개발용 임시 테스트") 14 | public class OAuthServiceTest { 15 | 16 | @Autowired 17 | private OAuth2Service oAuth2Service; 18 | 19 | @Test 20 | public void 액세스토큰으로_사용자정보_얻어오기_테스트() { 21 | String accessToken = "Y2ijVCkF3GycaYp2ok2q3UQd0eaLsF270rCgLwo9c-sAAAF6HhFdVQ"; 22 | TokenDto.Provider token = new TokenDto.Provider(TokenProvider.KAKAO, accessToken); 23 | 24 | OAuth2Profile profile = oAuth2Service.getProfile(token); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/question/component/WordHintMapperTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.question.component; 2 | 3 | import net.mureng.api.question.dto.WordHintDto; 4 | import net.mureng.core.question.entity.Question; 5 | import net.mureng.core.question.entity.WordHint; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | 10 | import java.time.LocalDateTime; 11 | 12 | import static org.junit.jupiter.api.Assertions.*; 13 | 14 | @SpringBootTest 15 | class WordHintMapperTest { 16 | @Autowired 17 | WordHintMapper wordHintMapper; 18 | 19 | private final WordHint wordHint = WordHint.builder() 20 | .hintId(1L) 21 | .question(Question.builder().build()) 22 | .word("apple") 23 | .meaning("사과") 24 | .regDate(LocalDateTime.parse("2020-10-14T11:00:00")) 25 | .build(); 26 | 27 | private final WordHintDto.ReadOnly wordHintDto = WordHintDto.ReadOnly.builder() 28 | .hintId(1L) 29 | .word("apple") 30 | .meaning("사과") 31 | .build(); 32 | 33 | @Test 34 | public void 엔티티에서_DTO변환_테스트() { 35 | WordHintDto.ReadOnly mappedDto = wordHintMapper.toDto(wordHint); 36 | assertEquals(wordHintDto.getHintId(), mappedDto.getHintId()); 37 | assertEquals(wordHintDto.getWord(), mappedDto.getWord()); 38 | assertEquals(wordHintDto.getMeaning(), mappedDto.getMeaning()); 39 | } 40 | 41 | @Test 42 | public void DTO에서_엔티티변환_테스트() { 43 | WordHint mappedEntity = wordHintMapper.toEntity(wordHintDto); 44 | assertEquals(wordHint.getWord(), mappedEntity.getWord()); 45 | assertEquals(wordHint.getMeaning(), mappedEntity.getMeaning()); 46 | } 47 | } -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/reply/component/ReplyLikesMapperTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.reply.component; 2 | 3 | import net.mureng.api.common.DtoCreator; 4 | import net.mureng.api.reply.dto.ReplyLikesDto; 5 | import net.mureng.core.common.EntityCreator; 6 | import net.mureng.core.reply.entity.ReplyLikes; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertEquals; 12 | import static org.junit.jupiter.api.Assertions.assertTrue; 13 | 14 | @SpringBootTest 15 | public class ReplyLikesMapperTest { 16 | 17 | @Autowired 18 | private ReplyLikesMapper replyLikesMapper; 19 | 20 | private final ReplyLikes replyLikes = EntityCreator.createReplyLikesEntity(); 21 | private final ReplyLikesDto replyLikesDto = DtoCreator.createReplyLikesDto(); 22 | 23 | @Test 24 | public void 엔티티에서_DTO변환_테스트() { 25 | ReplyLikesDto mappedDto = replyLikesMapper.toDto(replyLikes); 26 | assertEquals(replyLikesDto.getReplyId(), mappedDto.getReplyId()); 27 | assertEquals(replyLikesDto.getMemberId(), mappedDto.getMemberId()); 28 | assertTrue(mappedDto.isLikes()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/todayexpression/component/UsefulExpressionMapperTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.todayexpression.component; 2 | 3 | import net.mureng.api.common.DtoCreator; 4 | import net.mureng.api.todayexpression.dto.UsefulExpressionDto; 5 | import net.mureng.core.badge.service.BadgeAccomplishedServiceImpl; 6 | import net.mureng.core.common.EntityCreator; 7 | import net.mureng.core.member.entity.Member; 8 | import net.mureng.core.todayexpression.entity.UsefulExpression; 9 | import org.junit.jupiter.api.Test; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | 13 | import static org.junit.jupiter.api.Assertions.assertEquals; 14 | import static org.junit.jupiter.api.Assertions.assertTrue; 15 | 16 | @SpringBootTest 17 | public class UsefulExpressionMapperTest { 18 | 19 | @Autowired 20 | private TodayExpressionMapper todayExpressionMapper; 21 | 22 | private final UsefulExpression usefulExpression = EntityCreator.createUsefulExpressionEntity(); 23 | private final UsefulExpressionDto usefulExpressionDto = DtoCreator.createTodayExpressionDto(); 24 | 25 | private final Member member = EntityCreator.createMemberEntity(); 26 | 27 | @Test 28 | public void 엔티티에서_DTO변환_테스트() { 29 | UsefulExpressionDto mappedDto = todayExpressionMapper.toDto(usefulExpression, member); 30 | assertEquals(usefulExpressionDto.getExpId(), mappedDto.getExpId()); 31 | assertEquals(usefulExpressionDto.getExpression(), mappedDto.getExpression()); 32 | assertEquals(usefulExpressionDto.getMeaning(), mappedDto.getMeaning()); 33 | assertEquals(usefulExpressionDto.getExpressionExample(), mappedDto.getExpressionExample()); 34 | assertEquals(usefulExpressionDto.getExpressionExampleMeaning(), mappedDto.getExpressionExampleMeaning()); 35 | assertTrue(mappedDto.isScrappedByRequester()); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/web/AbstractControllerTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.web; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.jdbc.Sql; 7 | import org.springframework.test.web.servlet.MockMvc; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import static org.mockito.ArgumentMatchers.eq; 11 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 12 | 13 | @Transactional 14 | @Sql("classpath:/sql/integration.sql") 15 | @SpringBootTest 16 | @AutoConfigureMockMvc 17 | public abstract class AbstractControllerTest { 18 | @Autowired 19 | protected MockMvc mockMvc; 20 | } -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/web/ErrorControllerTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.web; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.test.web.servlet.result.MockMvcResultMatchers; 5 | 6 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 7 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 8 | 9 | class ErrorControllerTest extends AbstractControllerTest { 10 | @Test 11 | public void API호출_성공_테스트() throws Exception { 12 | mockMvc.perform( 13 | get("/api/test") 14 | ).andExpect(status().isOk()) 15 | .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("ok")); 16 | } 17 | 18 | @Test 19 | public void API호출_실패_테스트() throws Exception { 20 | mockMvc.perform( 21 | get("/api/test-failure") 22 | ).andExpect(status().isInternalServerError()) 23 | .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("net.mureng.mureng failure")); 24 | } 25 | } -------------------------------------------------------------------------------- /mureng-api/src/test/java/net/mureng/api/web/FcmTokenControllerTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.api.web; 2 | 3 | import net.mureng.api.annotation.WithMockMurengUser; 4 | import net.mureng.core.member.service.FcmTokenService; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.boot.test.mock.mockito.MockBean; 7 | import org.springframework.http.MediaType; 8 | import org.springframework.security.core.userdetails.UserDetailsService; 9 | import org.springframework.test.web.servlet.result.MockMvcResultMatchers; 10 | 11 | import static org.junit.jupiter.api.Assertions.*; 12 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 13 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 14 | 15 | class FcmTokenControllerTest extends AbstractControllerTest { 16 | @MockBean 17 | private FcmTokenService tokenService; 18 | 19 | @Test 20 | public void 비로그인_사용자_fcm토큰호출_테스트() throws Exception { 21 | mockMvc.perform( 22 | post("/api/fcm-token") 23 | .contentType(MediaType.APPLICATION_JSON) 24 | .content("{\"fcmToken\": \"testToken\"}") 25 | ).andExpect(status().isOk()) 26 | .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("ok")); 27 | } 28 | 29 | @Test 30 | @WithMockMurengUser 31 | public void 로그인_사용자_fcm토큰호출_테스트() throws Exception { 32 | mockMvc.perform( 33 | put("/api/member/me/fcm-token") 34 | .contentType(MediaType.APPLICATION_JSON) 35 | .content("{\"fcmToken\": \"testToken\"}") 36 | ).andExpect(status().isOk()) 37 | .andExpect(MockMvcResultMatchers.jsonPath("$.message").value("ok")); 38 | } 39 | } -------------------------------------------------------------------------------- /mureng-batch/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gradle:6.6.1-jdk11 as compile 2 | 3 | WORKDIR /home/gradle/project 4 | 5 | # Only copy dependency-related files 6 | # COPY build.gradle settings.gradle /home/gradle/project/ 7 | 8 | # Only download dependencies 9 | # Eat the expected build failure since no source code has been copied yet 10 | # RUN gradle clean build --no-daemon > /dev/null 2>&1 || true 11 | COPY build/libs/mureng-batch-0.0.1-SNAPSHOT.jar /home/gradle/project 12 | RUN mkdir /home/gradle/mount 13 | 14 | #RUN gradle clean bootJar 15 | 16 | ENTRYPOINT ["java","-jar","-Duser.timezone=Asia/Seoul","/home/gradle/project/mureng-batch-0.0.1-SNAPSHOT.jar"] 17 | EXPOSE 8080 -------------------------------------------------------------------------------- /mureng-batch/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | group 'net.mureng.batch' 6 | version '0.0.1-SNAPSHOT' 7 | 8 | repositories { 9 | mavenCentral() 10 | } 11 | 12 | dependencies { 13 | 14 | } 15 | 16 | test { 17 | useJUnitPlatform() 18 | } -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/BatchBasePackage.java: -------------------------------------------------------------------------------- 1 | package net.mureng; 2 | 3 | public interface BatchBasePackage { 4 | } 5 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/BatchStarter.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.mureng.batch.core.job.JobRequestService; 5 | import net.mureng.batch.core.job.ScheduledJobFactory; 6 | import net.mureng.batch.core.scheduler.ScheduleService; 7 | import org.springframework.context.ApplicationListener; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.event.ContextRefreshedEvent; 10 | 11 | import java.util.List; 12 | 13 | @Configuration 14 | @RequiredArgsConstructor 15 | public class BatchStarter implements ApplicationListener { 16 | private final ScheduleService scheduleService; 17 | private final ScheduledJobFactory jobFactory; 18 | private final List jobRequestServices; 19 | 20 | @Override 21 | public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { 22 | for (JobRequestService service : jobRequestServices) { 23 | scheduleService.addJob( 24 | jobFactory.buildScheduledJob(service.create()) 25 | ); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/MurengBatchApplication.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch; 2 | 3 | import net.mureng.BatchBasePackage; 4 | import net.mureng.CoreBasePackage; 5 | import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | 9 | import javax.annotation.PostConstruct; 10 | import java.util.TimeZone; 11 | 12 | @EnableBatchProcessing 13 | @SpringBootApplication(scanBasePackageClasses = { CoreBasePackage.class, BatchBasePackage.class }) 14 | public class MurengBatchApplication { 15 | 16 | @PostConstruct 17 | public void initialize() { 18 | TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); 19 | } 20 | 21 | public static void main(String[] args) { 22 | SpringApplication.run(MurengBatchApplication.class, args); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/core/config/AutowiringSpringBeanJobFactory.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.core.config; 2 | 3 | import lombok.NonNull; 4 | import org.quartz.spi.TriggerFiredBundle; 5 | import org.springframework.beans.BeansException; 6 | import org.springframework.beans.factory.config.AutowireCapableBeanFactory; 7 | import org.springframework.context.ApplicationContext; 8 | import org.springframework.context.ApplicationContextAware; 9 | import org.springframework.scheduling.quartz.SpringBeanJobFactory; 10 | 11 | public class AutowiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware { 12 | private transient AutowireCapableBeanFactory beanFactory; 13 | 14 | @Override 15 | public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { 16 | beanFactory = applicationContext.getAutowireCapableBeanFactory(); 17 | } 18 | 19 | @Override 20 | protected @NonNull Object createJobInstance(@NonNull TriggerFiredBundle bundle) throws Exception { 21 | final Object job = super.createJobInstance(bundle); 22 | beanFactory.autowireBean(job); 23 | return job; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/core/config/BatchConfig.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.core.config; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import net.mureng.batch.core.job.JobRequestService; 6 | import net.mureng.batch.core.job.MurengJobLauncher; 7 | import net.mureng.batch.core.job.MurengJobLauncherProvider; 8 | import org.springframework.batch.core.Job; 9 | import org.springframework.batch.core.configuration.JobRegistry; 10 | import org.springframework.batch.core.configuration.support.JobRegistryBeanPostProcessor; 11 | import org.springframework.beans.factory.config.AutowireCapableBeanFactory; 12 | import org.springframework.context.ApplicationContext; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.context.support.GenericApplicationContext; 16 | 17 | import javax.annotation.PostConstruct; 18 | import java.util.List; 19 | 20 | @Slf4j 21 | @RequiredArgsConstructor 22 | @Configuration 23 | public class BatchConfig { 24 | private final GenericApplicationContext applicationContext; 25 | private final List murengJobLauncherProviders; 26 | 27 | @Bean 28 | public JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor(JobRegistry jobRegistry) { 29 | JobRegistryBeanPostProcessor jobRegistryBeanPostProcessor = new JobRegistryBeanPostProcessor(); 30 | jobRegistryBeanPostProcessor.setJobRegistry(jobRegistry); 31 | return jobRegistryBeanPostProcessor; 32 | } 33 | 34 | @PostConstruct 35 | public void init() { 36 | for (MurengJobLauncherProvider provider : murengJobLauncherProviders) { 37 | applicationContext.registerBean(provider.getJobLauncherName(), MurengJobLauncher.class, provider::getLauncher); 38 | applicationContext.registerBean(provider.getLauncher().getJob().getName(), Job.class, () -> provider.getLauncher().getJob()); 39 | } 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/core/job/CronJobRequest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.core.job; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import lombok.experimental.SuperBuilder; 7 | import org.quartz.Job; 8 | import org.quartz.JobDataMap; 9 | import org.springframework.format.annotation.DateTimeFormat; 10 | 11 | import java.time.LocalDateTime; 12 | 13 | @SuperBuilder 14 | @Getter @Setter 15 | public class CronJobRequest extends JobRequest { 16 | private final String cronExpression; 17 | } 18 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/core/job/JobRequest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.core.job; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | import lombok.experimental.SuperBuilder; 8 | import org.quartz.Job; 9 | import org.quartz.JobDataMap; 10 | import org.springframework.format.annotation.DateTimeFormat; 11 | 12 | import java.time.LocalDateTime; 13 | 14 | @SuperBuilder 15 | @Getter @Setter 16 | public class JobRequest { 17 | @Builder.Default 18 | protected final String jobGroup = "DEFAULT"; 19 | protected final String jobName; 20 | protected final Class jobClass; 21 | protected final JobDataMap jobDataMap; 22 | 23 | @Builder.Default 24 | @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 25 | private final LocalDateTime startDateAt = LocalDateTime.now(); 26 | private final long repeatIntervalInSeconds; 27 | private final int repeatCount = -1; 28 | } 29 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/core/job/JobRequestService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.core.job; 2 | 3 | import net.mureng.batch.core.job.JobRequest; 4 | 5 | /** 6 | * 이 인터페이스를 구현하여 빈으로 등록해두면, 7 | * {@link net.mureng.batch.BatchStarter}에 의해 자동으로 등록됨 8 | * 9 | * @see net.mureng.batch.BatchStarter 10 | */ 11 | public interface JobRequestService { 12 | JobRequest create(); 13 | } 14 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/core/job/MurengBatchJobService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.core.job; 2 | 3 | public interface MurengBatchJobService { 4 | MurengJobLauncherProvider getProvider(); 5 | JobRequestService getJobRequestService(); 6 | } 7 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/core/job/MurengJobLauncher.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.core.job; 2 | 3 | import lombok.Getter; 4 | import lombok.RequiredArgsConstructor; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.batch.core.Job; 7 | import org.springframework.batch.core.JobParametersBuilder; 8 | import org.springframework.batch.core.launch.JobLauncher; 9 | 10 | import java.time.LocalDateTime; 11 | 12 | @Slf4j 13 | @RequiredArgsConstructor 14 | public class MurengJobLauncher { 15 | @Getter 16 | private final Job job; 17 | private final JobLauncher jobLauncher; 18 | 19 | public void execute() { 20 | try { 21 | jobLauncher.run( 22 | job, 23 | new JobParametersBuilder() 24 | .addString("datetime", LocalDateTime.now().toString()) 25 | .toJobParameters() // job parameter 설정 26 | ); 27 | } catch (Exception e) { 28 | log.warn("batch failed : ", e); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/core/job/MurengJobLauncherProvider.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.core.job; 2 | 3 | import org.springframework.batch.core.Job; 4 | 5 | /** 6 | * 이 인터페이스를 구현해서 bean으로 등록하면 자동으로 등록된다. 7 | * @see net.mureng.batch.core.config.BatchConfig 8 | */ 9 | public interface MurengJobLauncherProvider { 10 | /** 11 | * 이 이름으로 bean이 등록될 것임 12 | * @return 등록될 jobLauncher bean 이름 13 | */ 14 | String getJobLauncherName(); 15 | 16 | /** 17 | * 이 MurengJobJauncher가 bean으로 등록될 것임 18 | * @return 등록될 jobLauncher 19 | */ 20 | MurengJobLauncher getLauncher(); 21 | } 22 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/core/job/MurengQuartzJob.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.core.job; 2 | 3 | import org.quartz.InterruptableJob; 4 | import org.springframework.batch.core.configuration.JobLocator; 5 | import org.springframework.batch.core.launch.JobLauncher; 6 | import org.springframework.scheduling.quartz.QuartzJobBean; 7 | 8 | public abstract class MurengQuartzJob extends QuartzJobBean implements InterruptableJob { 9 | } 10 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/core/job/ScheduledJob.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.core.job; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import org.quartz.Job; 7 | import org.quartz.JobDetail; 8 | import org.quartz.Trigger; 9 | 10 | @Builder @Getter 11 | @AllArgsConstructor 12 | public class ScheduledJob { 13 | @Builder.Default 14 | private final String jobGroup = "DEFAULT"; 15 | private final String jobName; 16 | private final JobDetail jobDetail; 17 | private final Trigger trigger; 18 | private final Class jobClass; 19 | } 20 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/core/listener/JobsListener.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.core.listener; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.quartz.JobExecutionContext; 5 | import org.quartz.JobExecutionException; 6 | import org.quartz.JobKey; 7 | import org.quartz.JobListener; 8 | import org.springframework.stereotype.Component; 9 | 10 | @Slf4j 11 | @Component 12 | public class JobsListener implements JobListener { 13 | 14 | @Override 15 | public String getName() { 16 | return "globalJob"; 17 | } 18 | 19 | @Override 20 | public void jobToBeExecuted(JobExecutionContext context) { 21 | JobKey jobKey = context.getJobDetail().getKey(); 22 | log.info("jobToBeExecuted :: jobKey : {}", jobKey); 23 | } 24 | 25 | @Override 26 | public void jobExecutionVetoed(JobExecutionContext context) { 27 | JobKey jobKey = context.getJobDetail().getKey(); 28 | log.info("jobExecutionVetoed :: jobKey : {}", jobKey); 29 | } 30 | 31 | @Override 32 | public void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) { 33 | JobKey jobKey = context.getJobDetail().getKey(); 34 | log.info("jobWasExecuted :: jobKey : {}", jobKey); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/core/listener/TriggersListener.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.core.listener; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.quartz.JobExecutionContext; 5 | import org.quartz.JobKey; 6 | import org.quartz.Trigger; 7 | import org.quartz.TriggerListener; 8 | import org.springframework.stereotype.Component; 9 | 10 | @Slf4j 11 | @Component 12 | public class TriggersListener implements TriggerListener { 13 | 14 | @Override 15 | public String getName() { 16 | return "globalTrigger"; 17 | } 18 | 19 | @Override 20 | public void triggerFired(Trigger trigger, JobExecutionContext context) { 21 | JobKey jobKey = trigger.getJobKey(); 22 | log.info("triggerFired at {} :: jobKey : {}", trigger.getStartTime(), jobKey); 23 | } 24 | 25 | @Override 26 | public boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) { 27 | return false; 28 | } 29 | 30 | @Override 31 | public void triggerMisfired(Trigger trigger) { 32 | JobKey jobKey = trigger.getJobKey(); 33 | log.info("triggerMisfired at {} :: jobKey : {}", trigger.getStartTime(), jobKey); 34 | } 35 | 36 | @Override 37 | public void triggerComplete(Trigger trigger, JobExecutionContext context, 38 | Trigger.CompletedExecutionInstruction triggerInstructionCode) { 39 | JobKey jobKey = trigger.getJobKey(); 40 | log.info("triggerComplete at {} :: jobKey : {}", trigger.getStartTime(), jobKey); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/core/scheduler/ScheduleService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.core.scheduler; 2 | 3 | import net.mureng.batch.core.job.JobRequest; 4 | import net.mureng.batch.core.job.ScheduledJob; 5 | import org.quartz.Job; 6 | import org.quartz.JobKey; 7 | 8 | public interface ScheduleService { 9 | boolean isJobRunning(JobKey jobKey); 10 | boolean isJobExists(JobKey jobKey); 11 | boolean addJob(ScheduledJob scheduledJob); 12 | boolean deleteJob(JobKey jobKey); 13 | boolean pauseJob(JobKey jobKey); 14 | boolean resumeJob(JobKey jobKey); 15 | String getJobState(JobKey jobKey); 16 | } 17 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/core/trigger/CronTriggerProvider.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.core.trigger; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import net.mureng.batch.core.job.CronJobRequest; 5 | import net.mureng.batch.core.job.JobRequest; 6 | import org.quartz.SimpleTrigger; 7 | import org.quartz.Trigger; 8 | import org.springframework.scheduling.quartz.CronTriggerFactoryBean; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.text.ParseException; 12 | import java.util.TimeZone; 13 | 14 | import static org.quartz.CronExpression.isValidExpression; 15 | 16 | @Slf4j 17 | @Component 18 | public class CronTriggerProvider implements TriggerProvider { 19 | @Override 20 | public Trigger getTrigger(JobRequest jobRequest) { 21 | CronJobRequest cronJobRequest = (CronJobRequest)jobRequest; 22 | checkValidExpression(cronJobRequest.getCronExpression()); 23 | 24 | CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean(); 25 | factoryBean.setName(cronJobRequest.getJobName()); 26 | factoryBean.setGroup(cronJobRequest.getJobGroup()); 27 | factoryBean.setCronExpression(cronJobRequest.getCronExpression()); 28 | factoryBean.setTimeZone(TimeZone.getTimeZone("Asia/Seoul")); 29 | factoryBean.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW); 30 | try { 31 | factoryBean.afterPropertiesSet(); 32 | } catch (ParseException e) { 33 | log.warn("ParseException: ", e); 34 | } 35 | return factoryBean.getObject(); 36 | } 37 | 38 | private void checkValidExpression(String cronExpression) { 39 | if (! isValidExpression(cronExpression)) { 40 | throw new IllegalArgumentException("Provided expression " + cronExpression + 41 | " is not a valid cron expression"); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/core/trigger/SimpleTriggerProvider.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.core.trigger; 2 | 3 | import net.mureng.batch.core.job.JobRequest; 4 | import org.quartz.SimpleTrigger; 5 | import org.quartz.Trigger; 6 | import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.time.ZoneId; 10 | import java.util.Date; 11 | 12 | @Component 13 | public class SimpleTriggerProvider implements TriggerProvider { 14 | @Override 15 | public Trigger getTrigger(JobRequest jobRequest) { 16 | SimpleTriggerFactoryBean factoryBean = new SimpleTriggerFactoryBean(); 17 | factoryBean.setName(jobRequest.getJobName()); 18 | factoryBean.setGroup(jobRequest.getJobGroup()); 19 | factoryBean.setStartTime(Date.from(jobRequest.getStartDateAt().atZone(ZoneId.systemDefault()).toInstant())); 20 | factoryBean.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW); 21 | factoryBean.setRepeatInterval(jobRequest.getRepeatIntervalInSeconds() * 1000); //ms 단위임 22 | factoryBean.setRepeatCount(jobRequest.getRepeatCount()); 23 | 24 | factoryBean.afterPropertiesSet(); 25 | return factoryBean.getObject(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/core/trigger/TriggerProvider.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.core.trigger; 2 | 3 | import net.mureng.batch.core.job.JobRequest; 4 | import org.quartz.Trigger; 5 | 6 | public interface TriggerProvider { 7 | Trigger getTrigger(JobRequest jobRequest); 8 | } 9 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/core/trigger/TriggerProviderFactory.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.core.trigger; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import net.mureng.batch.core.job.CronJobRequest; 6 | import net.mureng.batch.core.job.JobRequest; 7 | import org.quartz.SimpleTrigger; 8 | import org.quartz.Trigger; 9 | import org.springframework.scheduling.quartz.CronTriggerFactoryBean; 10 | import org.springframework.scheduling.quartz.SimpleTriggerFactoryBean; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.text.ParseException; 14 | import java.time.ZoneId; 15 | import java.util.Date; 16 | import java.util.TimeZone; 17 | 18 | import static org.quartz.CronExpression.isValidExpression; 19 | 20 | @Slf4j 21 | @Component 22 | @RequiredArgsConstructor 23 | public class TriggerProviderFactory { 24 | private final SimpleTriggerProvider simpleTriggerProvider; 25 | private final CronTriggerProvider cronTriggerProvider; 26 | 27 | public TriggerProvider getInstance(JobRequest jobRequest) { 28 | if (jobRequest instanceof CronJobRequest) { 29 | return this.cronTriggerProvider; 30 | } 31 | 32 | return this.simpleTriggerProvider; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/push/service/FcmDailyPushService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.push.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.mureng.core.member.entity.FcmToken; 5 | import net.mureng.core.member.entity.Member; 6 | import net.mureng.core.question.service.TodayQuestionService; 7 | import net.mureng.core.reply.service.ReplyService; 8 | import net.mureng.push.dto.NotificationRequest; 9 | import net.mureng.push.service.FcmService; 10 | import org.springframework.stereotype.Service; 11 | 12 | @Service 13 | @RequiredArgsConstructor 14 | public class FcmDailyPushService { 15 | private static final String DAILY_PUSH_TITLE = "오늘의 영어 질문"; 16 | private static final String DAILY_PUSH_CLICK_ACTION = "MAIN"; 17 | private static final String DAILY_PUSH_CHANNEL_ID = "DAILY_CHANNEL"; 18 | 19 | private final FcmService fcmService; 20 | private final TodayQuestionService todayQuestionService; 21 | private final ReplyService replyService; 22 | 23 | public void push(FcmToken fcmToken) { 24 | Member member = fcmToken.getMember(); 25 | if (member == null || ! member.getMemberSetting().isDailyPushActive()) { 26 | return; 27 | } 28 | 29 | if (replyService.isAlreadyRepliedToday(member.getMemberId())) { 30 | return; 31 | } 32 | 33 | fcmService.send(NotificationRequest.builder() 34 | .token(fcmToken.getToken()) 35 | .title(DAILY_PUSH_TITLE) 36 | .message(todayQuestionService.getTodayQuestionByMemberId(member.getMemberId()).getContent()) 37 | .clickAction(DAILY_PUSH_CLICK_ACTION) 38 | .channelId(DAILY_PUSH_CHANNEL_ID) 39 | .build()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/todayexpression/maintain/service/TodayUsefulExpressionMaintainService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.todayexpression.maintain.service; 2 | 3 | public interface TodayUsefulExpressionMaintainService { 4 | void maintain(); 5 | } 6 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/todayexpression/refresh/service/TodayUsefulExpressionRefreshService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.todayexpression.refresh.service; 2 | 3 | import net.mureng.core.member.entity.Member; 4 | import net.mureng.core.question.entity.Question; 5 | 6 | public interface TodayUsefulExpressionRefreshService { 7 | void reselectTodayUsefulExpression(); 8 | } 9 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/util/CronExpression.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.util; 2 | 3 | public class CronExpression { 4 | public static final String EVERY_DAY_00_AM = "0 0 0 ? * * *"; 5 | public static final String EVERY_DAY_09_PM = "0 0 21 * * ?"; 6 | public static final String EVERY_5_MINUTES = "0 */5 * ? * *"; 7 | public static final String EVERY_MINUTE = "0 * * ? * *"; 8 | public static final String EVERY_5_SECONDS = "*/5 * * ? * *"; 9 | public static final String EVERY_SECOND = "* * * ? * *"; 10 | } 11 | -------------------------------------------------------------------------------- /mureng-batch/src/main/java/net/mureng/batch/util/JobLauncherUtil.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.util; 2 | 3 | import net.mureng.batch.core.job.MurengJobLauncher; 4 | import org.springframework.context.ApplicationContext; 5 | import org.springframework.context.ApplicationContextAware; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Component 9 | public class JobLauncherUtil implements ApplicationContextAware { 10 | private static ApplicationContext context; 11 | 12 | @Override 13 | public void setApplicationContext(ApplicationContext applicationContext) { 14 | // TODO Auto-generated method stub 15 | context = applicationContext; 16 | } 17 | 18 | public static MurengJobLauncher getBeanName(String beanName) { 19 | return context.getBean(beanName, MurengJobLauncher.class); 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /mureng-batch/src/main/resources/application-localbatch.yml: -------------------------------------------------------------------------------- 1 | # TODO : local 프로파일과 병합 - liquibase 초기화 이슈 해결 우선 2 | spring: 3 | sql: 4 | init: 5 | schema-locations: quartz_schema.sql -------------------------------------------------------------------------------- /mureng-batch/src/test/java/net/mureng/batch/AbstractJobTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch; 2 | 3 | import org.springframework.batch.test.JobLauncherTestUtils; 4 | import org.springframework.batch.test.JobRepositoryTestUtils; 5 | import org.springframework.batch.test.context.SpringBatchTest; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.test.context.jdbc.Sql; 8 | 9 | @SpringBatchTest 10 | @Sql("classpath:/sql/integration.sql") 11 | public abstract class AbstractJobTest { 12 | @Autowired 13 | protected JobLauncherTestUtils jobLauncherTestUtils; 14 | 15 | @Autowired 16 | protected JobRepositoryTestUtils jobRepositoryTestUtils; 17 | } 18 | -------------------------------------------------------------------------------- /mureng-batch/src/test/java/net/mureng/batch/core/config/TestBatchConfig.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.core.config; 2 | 3 | import net.mureng.CoreBasePackage; 4 | import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; 5 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 6 | import org.springframework.context.annotation.ComponentScan; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | @Configuration 12 | //@EnableAutoConfiguration 13 | @EnableBatchProcessing 14 | @ComponentScan(basePackages = {"net.mureng.core", "net.mureng.push", "net.mureng.batch.core"}) 15 | public class TestBatchConfig { 16 | 17 | } -------------------------------------------------------------------------------- /mureng-batch/src/test/java/net/mureng/batch/push/job/FcmDailyPushJobTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.push.job; 2 | 3 | import net.mureng.batch.AbstractJobTest; 4 | import net.mureng.batch.core.config.TestBatchConfig; 5 | import net.mureng.batch.push.service.FcmDailyPushService; 6 | import org.junit.jupiter.api.BeforeEach; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.batch.core.ExitStatus; 9 | import org.springframework.batch.core.JobExecution; 10 | import org.springframework.batch.core.JobParameters; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.boot.test.mock.mockito.MockBean; 13 | 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | 16 | @SpringBootTest(classes = {FcmDailyPushJobConductor.class, TestBatchConfig.class}) 17 | class FcmDailyPushJobTest extends AbstractJobTest { 18 | @MockBean 19 | private FcmDailyPushService fcmDailyPushService; 20 | 21 | @BeforeEach 22 | public void clearMetadata() { 23 | jobRepositoryTestUtils.removeJobExecutions(); 24 | } 25 | 26 | @Test 27 | public void 오늘의_표현_새로고침_테스트() throws Exception { 28 | // given 29 | JobParameters jobParameters = 30 | jobLauncherTestUtils.getUniqueJobParameters(); 31 | 32 | // when 33 | JobExecution jobExecution = 34 | jobLauncherTestUtils.launchJob(jobParameters); 35 | 36 | // then 37 | assertEquals(ExitStatus.COMPLETED, 38 | jobExecution.getExitStatus()); 39 | } 40 | } -------------------------------------------------------------------------------- /mureng-batch/src/test/java/net/mureng/batch/todayexpression/refresh/service/TodayUsefulExpressionRefreshServiceTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.batch.todayexpression.refresh.service; 2 | 3 | import net.mureng.core.core.component.NumberRandomizer; 4 | import net.mureng.core.todayexpression.entity.TodayUsefulExpression; 5 | import net.mureng.core.todayexpression.repository.TodayUsefulExpressionRepository; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.boot.test.mock.mockito.MockBean; 10 | import org.springframework.test.context.jdbc.Sql; 11 | import org.springframework.transaction.annotation.Transactional; 12 | 13 | import java.util.List; 14 | 15 | import static org.junit.jupiter.api.Assertions.assertEquals; 16 | import static org.mockito.ArgumentMatchers.anyInt; 17 | import static org.mockito.BDDMockito.given; 18 | import static org.mockito.Mockito.mock; 19 | 20 | @Sql("classpath:/sql/integration.sql") 21 | @SpringBootTest 22 | class TodayUsefulExpressionRefreshServiceTest { 23 | @Autowired 24 | private TodayUsefulExpressionRefreshServiceImpl todayUsefulExpressionRefreshService; 25 | 26 | @Autowired 27 | private TodayUsefulExpressionRepository todayUsefulExpressionRepository; 28 | 29 | @MockBean 30 | private NumberRandomizer numberRandomizer; 31 | 32 | @Test 33 | @Transactional 34 | void 오늘의_표현_갱신_테스트() { 35 | given(numberRandomizer.getRandomInt(anyInt())).willReturn(4, 3); 36 | 37 | todayUsefulExpressionRefreshService.reselectTodayUsefulExpression(); 38 | 39 | List todayUsefulExpressions = todayUsefulExpressionRepository.findAll(); 40 | assertEquals(2, todayUsefulExpressions.size()); 41 | assertEquals(4, todayUsefulExpressions.get(0).getUsefulExpression().getExpId()); 42 | assertEquals(3, todayUsefulExpressions.get(1).getUsefulExpression().getExpId()); 43 | } 44 | } -------------------------------------------------------------------------------- /mureng-core/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | } 4 | 5 | bootJar { 6 | enabled = false 7 | } 8 | jar { 9 | enabled = true 10 | } 11 | 12 | group 'net.mureng.core' 13 | version '0.0.1-SNAPSHOT' 14 | 15 | repositories { 16 | mavenCentral() 17 | } 18 | 19 | dependencies { 20 | 21 | } 22 | 23 | test { 24 | useJUnitPlatform() 25 | } -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/CoreBasePackage.java: -------------------------------------------------------------------------------- 1 | package net.mureng; 2 | 3 | public interface CoreBasePackage { 4 | } 5 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/MurengCoreApplication.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class MurengCoreApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(MurengCoreApplication.class, args); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/badge/entity/Badge.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.badge.entity; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | 7 | @Builder 8 | @Getter @Setter 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Entity 12 | @Table(name = "badge") 13 | public class Badge { 14 | 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.IDENTITY) 17 | @Column(name="badge_id", nullable = false) 18 | private Long badgeId; 19 | 20 | @Column(nullable = false, length = 20) 21 | private String name; 22 | 23 | @Column(nullable = false) 24 | private String content; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/badge/entity/BadgeAccomplished.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.badge.entity; 2 | 3 | import lombok.*; 4 | import net.mureng.core.member.entity.Member; 5 | 6 | import javax.persistence.*; 7 | import java.time.LocalDateTime; 8 | 9 | @Builder 10 | @Getter @Setter 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Entity 14 | @Table(name = "badge_accomplished") 15 | public class BadgeAccomplished { 16 | 17 | @EmbeddedId 18 | private BadgeAccomplishedPK id; 19 | 20 | @MapsId("memberId") 21 | @ManyToOne 22 | @JoinColumn(name = "member_id") 23 | private Member member; 24 | 25 | @MapsId("badgeId") 26 | @ManyToOne 27 | @JoinColumn(name = "badge_id") 28 | private Badge badge; 29 | 30 | @Builder.Default 31 | @Column(name = "is_checked", nullable = false) 32 | private Boolean isChecked = false; 33 | 34 | @Builder.Default 35 | @Column(name = "reg_date", nullable = false) 36 | private LocalDateTime regDate = LocalDateTime.now(); 37 | 38 | } 39 | 40 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/badge/entity/BadgeAccomplishedPK.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.badge.entity; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.Embeddable; 8 | import java.io.Serializable; 9 | import java.util.Objects; 10 | 11 | @Getter 12 | @Embeddable 13 | @NoArgsConstructor 14 | public class BadgeAccomplishedPK implements Serializable { 15 | private Long badgeId; 16 | private Long memberId; 17 | 18 | @Override 19 | public boolean equals(Object o) { 20 | if (this == o) return true; 21 | if (o == null || getClass() != o.getClass()) return false; 22 | BadgeAccomplishedPK that = (BadgeAccomplishedPK) o; 23 | return Objects.equals(badgeId, that.badgeId) && Objects.equals(memberId, that.memberId); 24 | } 25 | 26 | @Override 27 | public int hashCode() { 28 | return Objects.hash(badgeId, memberId); 29 | } 30 | 31 | @Builder 32 | public BadgeAccomplishedPK(Long badgeId, Long memberId) { 33 | this.badgeId = badgeId; 34 | this.memberId = memberId; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/badge/repository/BadgeAccomplishedRepository.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.badge.repository; 2 | 3 | import net.mureng.core.badge.entity.BadgeAccomplished; 4 | import net.mureng.core.badge.entity.BadgeAccomplishedPK; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | import java.util.List; 8 | 9 | public interface BadgeAccomplishedRepository extends JpaRepository { 10 | List findBadgeAccomplishedsByIdMemberId(Long memberId); 11 | boolean existsBadgeAccomplishedByMemberMemberIdAndBadgeBadgeId(Long memberId, Long badgeId); 12 | } 13 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/badge/repository/BadgeRepository.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.badge.repository; 2 | 3 | import net.mureng.core.badge.entity.Badge; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface BadgeRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/badge/service/BadgeAccomplishedService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.badge.service; 2 | 3 | public interface BadgeAccomplishedService { 4 | boolean createMureng3Days(Long memberId); 5 | boolean createCelebrityMureng(Long replyId); 6 | boolean createAcademicMureng(Long memberId); 7 | boolean createMurengSet(Long memberId); 8 | boolean isAlreadyCheckedCelebrityMureng(Long memberId); 9 | } 10 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/badge/service/BadgeService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.badge.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.mureng.core.badge.entity.Badge; 5 | import net.mureng.core.badge.repository.BadgeRepository; 6 | import net.mureng.core.core.exception.EntityNotFoundException; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import static net.mureng.core.core.message.ErrorMessage.NOT_EXIST_BADGE; 11 | 12 | @RequiredArgsConstructor 13 | @Service 14 | public class BadgeService { 15 | private final BadgeRepository badgeRepository; 16 | 17 | @Transactional(readOnly = true) 18 | public Badge findById(Long badgeId){ 19 | return badgeRepository.findById(badgeId) 20 | .orElseThrow(() -> new EntityNotFoundException(NOT_EXIST_BADGE)); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/cookie/entity/CookieAcquirement.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.cookie.entity; 2 | 3 | import lombok.*; 4 | import net.mureng.core.member.entity.Member; 5 | 6 | import javax.persistence.*; 7 | import java.time.LocalDate; 8 | import java.time.LocalDateTime; 9 | 10 | @Builder 11 | @Getter 12 | @Setter 13 | @NoArgsConstructor 14 | @AllArgsConstructor 15 | @Entity 16 | @Table(name = "cookie_acquirement") 17 | public class CookieAcquirement { 18 | 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | @Column(name = "id") 22 | private Long id; 23 | 24 | @ManyToOne 25 | @JoinColumn(name = "member_id") 26 | private Member member; 27 | 28 | @Builder.Default 29 | @Column(name = "reg_date", nullable = false) 30 | private LocalDate regDate = LocalDate.now(); 31 | } 32 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/cookie/repository/CookieAcquirementRepository.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.cookie.repository; 2 | 3 | import net.mureng.core.cookie.entity.CookieAcquirement; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.time.LocalDate; 7 | import java.time.LocalDateTime; 8 | 9 | public interface CookieAcquirementRepository extends JpaRepository { 10 | boolean existsByRegDateAndMemberMemberId(LocalDate date, Long memberId); 11 | } 12 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/cookie/service/CookieAcquirementService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.cookie.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.mureng.core.cookie.entity.CookieAcquirement; 5 | import net.mureng.core.cookie.repository.CookieAcquirementRepository; 6 | import net.mureng.core.member.entity.Member; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import java.time.LocalDate; 11 | import java.time.LocalDateTime; 12 | import java.time.LocalTime; 13 | 14 | @RequiredArgsConstructor 15 | @Service 16 | public class CookieAcquirementService { 17 | private final CookieAcquirementRepository cookieRepository; 18 | 19 | @Transactional 20 | public void acquireMurengCookie(Member member){ 21 | CookieAcquirement cookie = CookieAcquirement.builder().member(member).build(); 22 | 23 | cookieRepository.saveAndFlush(cookie); 24 | } 25 | 26 | @Transactional(readOnly = true) 27 | public boolean isAlreadyCookieAcquiredToday(Long memberId) { 28 | LocalDate date = LocalDate.now(); 29 | 30 | return cookieRepository.existsByRegDateAndMemberMemberId(date, memberId); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/core/component/DateFactory.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.core.component; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.util.Calendar; 6 | import java.util.Date; 7 | import java.util.Locale; 8 | import java.util.TimeZone; 9 | 10 | @Component 11 | public class DateFactory { 12 | public Date now() { 13 | return new Date(); 14 | } 15 | 16 | /** 17 | * 현재 날짜로부터 더한(혹은 뺀) 결과를 리턴 18 | * @param field {@link Calendar} 의 상수 필드 (ex: Calendar.MONTH) 19 | * @param amount 변동할 양 20 | * @return 결과 Date 21 | * @see Calendar 22 | */ 23 | public Date addFromNow(int field, int amount) { 24 | Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("Asia/Seoul"), Locale.KOREA); 25 | calendar.setTime(new Date()); 26 | calendar.add(field, amount); 27 | return calendar.getTime(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/core/component/DirectoryScanner.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.core.component; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.io.File; 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | import static net.mureng.core.core.message.ErrorMessage.NOT_DIRECTORY; 11 | 12 | @Component 13 | public class DirectoryScanner { 14 | 15 | /** 16 | * 특정 경로를 스캔하여 파일명의 목록을 리턴한다. 17 | * @param directoryPath 디렉터리 경로 18 | * @return 파일 경로의 목록 19 | * @throws IllegalArgumentException 파일 경로가 들어왔을 경우 20 | */ 21 | public List scanFileListInDirectory(String directoryPath) { 22 | File[] listFiles = new File(directoryPath).listFiles(); 23 | if (listFiles == null) { // null 인 경우 파일이다. 24 | throw new IllegalArgumentException(NOT_DIRECTORY); 25 | } 26 | 27 | List fileList = new ArrayList<>(); 28 | for (File f : listFiles) { 29 | if (f.isDirectory()) { 30 | continue; 31 | } 32 | 33 | fileList.add(f.getAbsolutePath()); 34 | } 35 | Collections.sort(fileList); 36 | return fileList; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/core/component/NumberRandomizer.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.core.component; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.util.Random; 6 | 7 | @Component 8 | public class NumberRandomizer { 9 | private final Random rand = new Random(); 10 | 11 | /** 12 | * 무작위 양의 정수를 구한다. 13 | * @param maximum 나올 수 있는 최대 숫자 14 | * @return 랜덤 숫자 15 | */ 16 | public int getRandomInt(int maximum) { 17 | rand.setSeed(System.currentTimeMillis()); 18 | return rand.nextInt(maximum) + 1; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/core/exception/AccessNotAllowedException.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.core.exception; 2 | 3 | /** 4 | * 리소스 접근 권한 없음 예외 (403 Forbidden) 5 | */ 6 | public class AccessNotAllowedException extends MurengException { 7 | public AccessNotAllowedException() { 8 | } 9 | 10 | public AccessNotAllowedException(String message) { 11 | super(message); 12 | } 13 | 14 | public AccessNotAllowedException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | 18 | public AccessNotAllowedException(Throwable cause) { 19 | super(cause); 20 | } 21 | 22 | public AccessNotAllowedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 23 | super(message, cause, enableSuppression, writableStackTrace); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/core/exception/BadRequestException.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.core.exception; 2 | 3 | /** 4 | * 리소스 잘못된 요청 예외 (400 Bad Request) 5 | */ 6 | public class BadRequestException extends MurengException { 7 | public BadRequestException() { 8 | } 9 | 10 | public BadRequestException(String message) { 11 | super(message); 12 | } 13 | 14 | public BadRequestException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | 18 | public BadRequestException(Throwable cause) { 19 | super(cause); 20 | } 21 | 22 | public BadRequestException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 23 | super(message, cause, enableSuppression, writableStackTrace); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/core/exception/EntityNotFoundException.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.core.exception; 2 | 3 | import net.mureng.core.core.exception.MurengException; 4 | import net.mureng.core.core.exception.ResourceNotFoundException; 5 | 6 | public class EntityNotFoundException extends ResourceNotFoundException { 7 | public EntityNotFoundException() { 8 | } 9 | 10 | public EntityNotFoundException(String message) { 11 | super(message); 12 | } 13 | 14 | public EntityNotFoundException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | 18 | public EntityNotFoundException(Throwable cause) { 19 | super(cause); 20 | } 21 | 22 | public EntityNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 23 | super(message, cause, enableSuppression, writableStackTrace); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/core/exception/MurengException.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.core.exception; 2 | 3 | /** 4 | * 머렝 도메인 비즈니스 예외 (500 Server Error) 5 | */ 6 | public class MurengException extends RuntimeException { 7 | 8 | public MurengException() { 9 | } 10 | 11 | public MurengException(String message) { 12 | super(message); 13 | } 14 | 15 | public MurengException(String message, Throwable cause) { 16 | super(message, cause); 17 | } 18 | 19 | public MurengException(Throwable cause) { 20 | super(cause); 21 | } 22 | 23 | public MurengException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 24 | super(message, cause, enableSuppression, writableStackTrace); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/core/exception/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.core.exception; 2 | 3 | /** 4 | * 리소스 찾을 수 없음 예외 (404 Not Found) 5 | */ 6 | public class ResourceNotFoundException extends MurengException { 7 | public ResourceNotFoundException() { 8 | } 9 | 10 | public ResourceNotFoundException(String message) { 11 | super(message); 12 | } 13 | 14 | public ResourceNotFoundException(String message, Throwable cause) { 15 | super(message, cause); 16 | } 17 | 18 | public ResourceNotFoundException(Throwable cause) { 19 | super(cause); 20 | } 21 | 22 | public ResourceNotFoundException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 23 | super(message, cause, enableSuppression, writableStackTrace); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/core/exception/UnauthorizedException.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.core.exception; 2 | /** 3 | * 인증되지 않은 접근 예외 (401 NotAuthorized) 4 | */ 5 | public class UnauthorizedException extends MurengException{ 6 | public UnauthorizedException() { 7 | } 8 | 9 | public UnauthorizedException(String message) { 10 | super(message); 11 | } 12 | 13 | public UnauthorizedException(String message, Throwable cause) { 14 | super(message, cause); 15 | } 16 | 17 | public UnauthorizedException(Throwable cause) { 18 | super(cause); 19 | } 20 | 21 | public UnauthorizedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 22 | super(message, cause, enableSuppression, writableStackTrace); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/core/message/ErrorMessage.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.core.message; 2 | 3 | import lombok.NoArgsConstructor; 4 | 5 | @NoArgsConstructor 6 | public class ErrorMessage { 7 | // Authorization 8 | public static String UNAUTHORIZED = "접근 권한이 없습니다."; 9 | 10 | // Member 11 | public static String NOT_EXIST_MEMBER = "존재하지 않는 사용자입니다."; 12 | public static String DUPLICATED_NICKNAME = "이미 사용 중인 닉네임입니다."; 13 | public static String ALREADY_ANSWERED_MEMBER = "이미 오늘 답변한 사용자입니다."; 14 | 15 | // Question 16 | public static String NOT_EXIST_QUESTION = "존재하지 않는 질문입니다."; 17 | 18 | // Badge 19 | public static String NOT_EXIST_BADGE = "존재하지 않는 뱃지입니다."; 20 | public static String NOT_ACCOMPLISHED_BADGE = "획득하지 않은 뱃지 번호입니다."; 21 | public static String MEMBER_CHECK_BADGE_ACCOMPLISHED = "사용자가 뱃지 획득을 확인하였습니다."; 22 | 23 | // Reply 24 | public static String NOT_EXIST_REPLY = "존재하지 않는 답변입니다."; 25 | public static String ALREADY_ANSWERED_REPLY = "이미 답변한 질문입니다."; 26 | 27 | // ReplyLikes 28 | public static String ALREADY_PUSHED_REPLY_LIKE = "이미 좋아요를 눌렀습니다."; 29 | public static String ALREADY_CANCELED_REPLY_LIKE = "이미 좋아요를 취소했습니다."; 30 | 31 | // File Directory 32 | public static String NOT_DIRECTORY = "Not a directory"; 33 | 34 | // Today Question 35 | public static String QUESTION_REFRESH_FAIL = "질문 새로 고침에 실패하였습니다."; 36 | 37 | // Today Expression 38 | public static String EXPRESSION_REFRESH_FAIL = "오늘의 표현 새로 고침에 실패하였습니다."; 39 | public static String NOT_EXIST_EXPRESSION = "존재하지 않는 오늘의 표현에 대한 요청입니다."; 40 | 41 | // Cookie 42 | public static String ALREADY_COOKIE_ACQUIRED = "이미 오늘 쿠키를 획득하였습니다."; 43 | 44 | // Scrap 45 | public static String ALREADY_SCRAPPED_EXPRESSION = "이미 스크랩한 표현입니다."; 46 | public static String ALREADY_CANCELED_EXPRESSION = "이미 스크랩을 취소한 표현입니다."; 47 | 48 | } 49 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/core/message/ResponseMessage.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.core.message; 2 | 3 | import lombok.NoArgsConstructor; 4 | 5 | @NoArgsConstructor 6 | public class ResponseMessage { 7 | public static String CHECK_BADGE_ACCOMPLISHED = "뱃지 획득을 확인하였습니다."; 8 | } 9 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/member/entity/FcmToken.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.member.entity; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | import javax.validation.constraints.NotNull; 7 | 8 | @Getter @Setter 9 | @Builder 10 | @AllArgsConstructor 11 | @NoArgsConstructor 12 | @Entity 13 | @Table(name = "fcm_token") 14 | public class FcmToken { 15 | @Id 16 | @GeneratedValue(strategy = GenerationType.IDENTITY) 17 | @Column(name = "idx", nullable = false) 18 | private Long idx; 19 | 20 | @OneToOne 21 | @JoinColumn(name = "member_id", unique = true) 22 | private Member member; 23 | 24 | @NotNull 25 | @Column(nullable = false, unique = true) 26 | private String token; 27 | } 28 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/member/entity/MemberAttendance.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.member.entity; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.*; 9 | import java.time.LocalDate; 10 | 11 | @Embeddable 12 | @Builder 13 | @Getter 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class MemberAttendance { 17 | @Column(name = "attendance_count", nullable = false) 18 | private int attendanceCount; 19 | 20 | @Column(name = "last_attendance_date", nullable = false) 21 | @Builder.Default 22 | private LocalDate lastAttendanceDate = LocalDate.now(); 23 | } 24 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/member/entity/MemberScrap.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.member.entity; 2 | 3 | import lombok.*; 4 | import net.mureng.core.todayexpression.entity.UsefulExpression; 5 | 6 | import javax.persistence.*; 7 | import java.time.LocalDateTime; 8 | 9 | @Builder 10 | @Getter @Setter 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Entity 14 | @Table(name = "member_scrap") 15 | public class MemberScrap { 16 | @EmbeddedId 17 | private MemberScrapPK id; 18 | 19 | @MapsId("memberId") 20 | @ManyToOne 21 | @JoinColumn(name = "member_id") 22 | private Member member; 23 | 24 | @MapsId("expId") 25 | @ManyToOne 26 | @JoinColumn(name = "exp_id") 27 | private UsefulExpression usefulExpression; 28 | 29 | @Builder.Default 30 | @Column(name = "reg_date", nullable = false) 31 | private LocalDateTime regDate = LocalDateTime.now(); 32 | } 33 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/member/entity/MemberScrapPK.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.member.entity; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.Embeddable; 8 | import java.io.Serializable; 9 | import java.util.Objects; 10 | 11 | @Getter 12 | @NoArgsConstructor 13 | @Embeddable 14 | public class MemberScrapPK implements Serializable { 15 | private Long memberId; 16 | 17 | private Long expId; 18 | 19 | @Override 20 | public boolean equals(Object o) { 21 | if (this == o) return true; 22 | if (o == null || getClass() != o.getClass()) return false; 23 | MemberScrapPK that = (MemberScrapPK) o; 24 | return Objects.equals(memberId, that.memberId) && Objects.equals(expId, that.expId); 25 | } 26 | 27 | @Override 28 | public int hashCode() { 29 | return Objects.hash(memberId, expId); 30 | } 31 | 32 | @Builder 33 | public MemberScrapPK(Long memberId, Long expId) { 34 | this.memberId = memberId; 35 | this.expId = expId; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/member/entity/MemberSetting.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.member.entity; 2 | 3 | 4 | import lombok.*; 5 | 6 | import javax.persistence.*; 7 | 8 | @Embeddable 9 | @Builder 10 | @Getter @Setter 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | public class MemberSetting { 14 | @Builder.Default 15 | @Column(name = "is_daily_push_active", nullable = false) 16 | private boolean isDailyPushActive = true; 17 | 18 | @Builder.Default 19 | @Column(name = "is_like_push_active", nullable = false) 20 | private boolean isLikePushActive = true; 21 | } 22 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/member/repository/FcmTokenRepository.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.member.repository; 2 | 3 | import net.mureng.core.member.entity.FcmToken; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | public interface FcmTokenRepository extends JpaRepository { 9 | boolean existsByToken(String token); 10 | Optional findByMemberMemberId(Long memberId); 11 | Optional findByToken(String token); 12 | } 13 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/member/repository/MemberRepository.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.member.repository; 2 | 3 | import net.mureng.core.member.entity.Member; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | public interface MemberRepository extends JpaRepository { 9 | boolean existsByNicknameAndIsActive(String nickname, Boolean active); 10 | // boolean existsByEmail(String email); 11 | Optional findByIdentifier(String identifier); 12 | Optional findByIdentifierAndIsActive(String identifier, Boolean active); 13 | boolean existsByIdentifier(String identifier); 14 | boolean existsByIdentifierAndIsActive(String identifier, Boolean active); 15 | } 16 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/member/repository/MemberScrapRepository.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.member.repository; 2 | 3 | import net.mureng.core.member.entity.MemberScrap; 4 | import net.mureng.core.member.entity.MemberScrapPK; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | import java.util.List; 8 | 9 | public interface MemberScrapRepository extends JpaRepository { 10 | List findAllByIdMemberId(Long memberId); 11 | Long countByMemberMemberId(Long memberId); 12 | } 13 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/question/entity/Question.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.question.entity; 2 | 3 | import lombok.*; 4 | import net.mureng.core.member.entity.Member; 5 | import net.mureng.core.reply.entity.Reply; 6 | 7 | import javax.persistence.*; 8 | import java.time.LocalDateTime; 9 | import java.util.ArrayList; 10 | import java.util.HashSet; 11 | import java.util.List; 12 | import java.util.Set; 13 | 14 | @Builder 15 | @Getter @Setter 16 | @NoArgsConstructor 17 | @AllArgsConstructor 18 | @Entity 19 | @Table(name = "question") 20 | public class Question { 21 | 22 | @Id 23 | @GeneratedValue(strategy = GenerationType.IDENTITY) 24 | @Column(name = "question_id") 25 | private Long questionId; 26 | 27 | @ManyToOne 28 | @JoinColumn(name = "member_id") 29 | private Member author; 30 | 31 | @Column(length = 20) 32 | private String category; 33 | 34 | @Column(nullable = false, length = 150) 35 | private String content; 36 | 37 | @Column(name = "ko_content") 38 | private String koContent; 39 | 40 | @Builder.Default 41 | @OneToMany(mappedBy = "question", fetch = FetchType.EAGER) 42 | private Set wordHints = new HashSet<>(); 43 | 44 | @Builder.Default 45 | @OneToMany(mappedBy = "question") 46 | private List replies = new ArrayList<>(); 47 | 48 | @Builder.Default 49 | @Column(name = "reg_date", nullable = false) 50 | private LocalDateTime regDate = LocalDateTime.now(); 51 | } 52 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/question/entity/TodayQuestion.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.question.entity; 2 | 3 | import lombok.*; 4 | import net.mureng.core.member.entity.Member; 5 | 6 | import javax.persistence.*; 7 | import java.time.LocalDateTime; 8 | 9 | @Builder 10 | @Getter 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Entity 14 | @Table(name = "today_question") 15 | public class TodayQuestion { 16 | 17 | @Id 18 | private Long memberId; 19 | 20 | @MapsId 21 | @OneToOne 22 | @JoinColumn(name = "member_id", nullable = false) 23 | private Member member; 24 | 25 | @ManyToOne 26 | @JoinColumn(name = "question_id", nullable = false) 27 | private Question question; 28 | 29 | @Builder.Default 30 | @Column(name = "reg_date", nullable = false) 31 | private LocalDateTime regDate = LocalDateTime.now(); 32 | 33 | @Builder.Default 34 | @Column(name = "mod_date", nullable = false) 35 | private LocalDateTime modDate = LocalDateTime.now(); 36 | 37 | public void setQuestion(Question question) { 38 | this.question = question; 39 | this.modDate = LocalDateTime.now(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/question/entity/WordHint.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.question.entity; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | import java.time.LocalDateTime; 7 | 8 | @Builder 9 | @Getter @Setter 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Entity 13 | @Table(name = "word_hint") 14 | public class WordHint { 15 | 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | @Column(name = "hint_id") 19 | private Long hintId; 20 | 21 | @ManyToOne 22 | @JoinColumn(name = "question_id") 23 | private Question question; 24 | 25 | @Column(nullable = false) 26 | private String word; 27 | 28 | @Column(nullable = false) 29 | private String meaning; 30 | 31 | @Builder.Default 32 | @Column(name = "reg_date", nullable = false) 33 | private LocalDateTime regDate = LocalDateTime.now(); 34 | } 35 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/question/repository/QuestionRepository.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.question.repository; 2 | 3 | import net.mureng.core.question.entity.Question; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.data.jpa.repository.Query; 8 | 9 | import java.util.List; 10 | 11 | public interface QuestionRepository extends JpaRepository { 12 | List findAllByAuthorMemberIdOrderByRegDateDesc(Long memberId); 13 | boolean existsByQuestionIdAndAuthorMemberId(Long questionId, Long memberId); 14 | 15 | @Query("Select q from Question q order by q.replies.size desc") 16 | Page findAllOrderByRepliesSizeDesc(Pageable page); 17 | } 18 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/question/repository/TodayQuestionRepository.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.question.repository; 2 | 3 | import net.mureng.core.question.entity.TodayQuestion; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface TodayQuestionRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/question/repository/WordHintRepository.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.question.repository; 2 | 3 | import net.mureng.core.question.entity.Question; 4 | import net.mureng.core.question.entity.WordHint; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | import java.util.List; 8 | 9 | public interface WordHintRepository extends JpaRepository { 10 | List findAllByQuestion(Question question); 11 | } 12 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/question/service/QuestionService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.question.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.mureng.core.core.exception.ResourceNotFoundException; 5 | import net.mureng.core.question.entity.Question; 6 | import net.mureng.core.question.repository.QuestionRepository; 7 | import org.springframework.stereotype.Service; 8 | import org.springframework.transaction.annotation.Transactional; 9 | 10 | import java.util.List; 11 | 12 | import static net.mureng.core.core.message.ErrorMessage.NOT_EXIST_QUESTION; 13 | 14 | @Service 15 | @RequiredArgsConstructor 16 | public class QuestionService { 17 | private final QuestionRepository questionRepository; 18 | 19 | @Transactional(readOnly = true) 20 | public Question getQuestionById(Long questionId){ 21 | return questionRepository.findById(questionId).orElseThrow(() -> new ResourceNotFoundException(NOT_EXIST_QUESTION)); 22 | } 23 | 24 | @Transactional(readOnly = true) 25 | public boolean existsById(Long questionId){ 26 | return questionRepository.existsById(questionId); 27 | } 28 | 29 | @Transactional(readOnly = true) 30 | public List getQuestionWrittenByMember(Long memberId){ 31 | return questionRepository.findAllByAuthorMemberIdOrderByRegDateDesc(memberId); 32 | } 33 | 34 | @Transactional 35 | public Question create(Question question){ 36 | return questionRepository.saveAndFlush(question); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/question/service/TodayQuestionSelectionService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.question.service; 2 | 3 | import net.mureng.core.member.entity.Member; 4 | import net.mureng.core.question.entity.Question; 5 | 6 | public interface TodayQuestionSelectionService { 7 | Question reselectTodayQuestion(Member member); 8 | } 9 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/question/service/TodayQuestionService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.question.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.mureng.core.core.exception.ResourceNotFoundException; 5 | import net.mureng.core.question.entity.Question; 6 | import net.mureng.core.question.entity.TodayQuestion; 7 | import net.mureng.core.question.repository.TodayQuestionRepository; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import static net.mureng.core.core.message.ErrorMessage.NOT_EXIST_QUESTION; 12 | 13 | @Service 14 | @RequiredArgsConstructor 15 | public class TodayQuestionService { 16 | private final TodayQuestionRepository todayQuestionRepository; 17 | 18 | @Transactional(readOnly = true) 19 | public Question getTodayQuestionByMemberId(Long memberId) { 20 | return todayQuestionRepository.findById(memberId).orElseThrow(() -> new ResourceNotFoundException(NOT_EXIST_QUESTION)).getQuestion(); 21 | } 22 | 23 | @Transactional 24 | public void saveTodayQuestion(Long memberId, Question question) { 25 | TodayQuestion todayQuestion = todayQuestionRepository.findById(memberId).orElseThrow(() -> new ResourceNotFoundException(NOT_EXIST_QUESTION)); 26 | todayQuestion.setQuestion(question); 27 | todayQuestionRepository.saveAndFlush(todayQuestion); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/reply/entity/ReplyLikes.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.reply.entity; 2 | 3 | import lombok.*; 4 | import net.mureng.core.member.entity.Member; 5 | 6 | import javax.persistence.*; 7 | import java.time.LocalDateTime; 8 | 9 | @Builder 10 | @Getter @Setter 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Entity 14 | @Table(name = "reply_likes") 15 | public class ReplyLikes { 16 | @EmbeddedId 17 | private ReplyLikesPK id; 18 | 19 | @MapsId("memberId") 20 | @ManyToOne 21 | @JoinColumn(name = "member_id") 22 | private Member member; 23 | 24 | @MapsId("replyId") 25 | @ManyToOne 26 | @JoinColumn(name = "reply_id") 27 | private Reply reply; 28 | 29 | @Builder.Default 30 | @Column(name = "reg_date", nullable = false) 31 | private LocalDateTime regDate = LocalDateTime.now(); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/reply/entity/ReplyLikesPK.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.reply.entity; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.persistence.Embeddable; 8 | import java.io.Serializable; 9 | import java.util.Objects; 10 | 11 | @Getter 12 | @NoArgsConstructor 13 | @Embeddable 14 | public class ReplyLikesPK implements Serializable { 15 | private Long replyId; 16 | private Long memberId; 17 | 18 | @Override 19 | public boolean equals(Object o) { 20 | if (this == o) return true; 21 | if (o == null || getClass() != o.getClass()) return false; 22 | ReplyLikesPK that = (ReplyLikesPK) o; 23 | return Objects.equals(replyId, that.replyId) && Objects.equals(memberId, that.memberId); 24 | } 25 | 26 | @Override 27 | public int hashCode() { 28 | return Objects.hash(replyId, memberId); 29 | } 30 | 31 | @Builder 32 | public ReplyLikesPK(Long memberId, Long replyId) { 33 | this.memberId = memberId; 34 | this.replyId = replyId; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/reply/repository/ReplyLikesRepository.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.reply.repository; 2 | 3 | import net.mureng.core.reply.entity.ReplyLikes; 4 | import net.mureng.core.reply.entity.ReplyLikesPK; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | public interface ReplyLikesRepository extends JpaRepository { 8 | int countByIdReplyId(Long replyId); 9 | Boolean existsByMemberMemberIdAndReplyReplyId(Long memberId, Long replyId); 10 | } 11 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/reply/repository/ReplyRepository.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.reply.repository; 2 | 3 | import net.mureng.core.reply.entity.Reply; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.data.jpa.repository.Query; 8 | 9 | import java.time.LocalDateTime; 10 | import java.util.List; 11 | import java.util.Optional; 12 | 13 | public interface ReplyRepository extends JpaRepository { 14 | List findAllByAuthorMemberIdOrderByReplyIdDesc(Long memberId); 15 | List findAllByQuestionQuestionId(Long questionId); 16 | Page findAllByQuestionQuestionId(Long questionId, Pageable pageable); 17 | 18 | Long countAllByAuthorMemberId(Long memberId); 19 | 20 | @Query("Select r from Reply r where r.question.questionId = :questionId order by r.replyLikes.size desc") 21 | Page findAllByQuestionQuestionIdOrderByReplyLikesSize(Long questionId, Pageable pageable); 22 | 23 | @Query("Select r from Reply r order by r.replyLikes.size desc") 24 | Page findAllByOrderByReplyLikesSize(Pageable pageable); 25 | 26 | boolean existsByRegDateBetweenAndAuthorMemberId(LocalDateTime startDateTime, LocalDateTime endDateTime, Long memberId); 27 | boolean existsByQuestionQuestionIdAndAuthorMemberId(Long questionId, Long memberId); 28 | Optional findByAuthorMemberIdAndQuestionQuestionId(Long memberId, Long questionId); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/reply/service/ReplyPostProcessService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.reply.service; 2 | 3 | import net.mureng.core.reply.entity.Reply; 4 | 5 | public interface ReplyPostProcessService { 6 | void postProcess(Reply reply); 7 | } 8 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/reply/service/ReplyPostProcessServiceImpl.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.reply.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import lombok.extern.slf4j.Slf4j; 5 | import net.mureng.core.cookie.repository.CookieAcquirementRepository; 6 | import net.mureng.core.cookie.service.CookieAcquirementService; 7 | import net.mureng.core.core.exception.BadRequestException; 8 | import net.mureng.core.member.entity.Member; 9 | import net.mureng.core.member.repository.MemberRepository; 10 | import net.mureng.core.reply.entity.Reply; 11 | import org.springframework.stereotype.Service; 12 | import org.springframework.transaction.annotation.Transactional; 13 | 14 | import java.time.LocalDate; 15 | import java.time.LocalDateTime; 16 | import java.time.LocalTime; 17 | 18 | import static net.mureng.core.core.message.ErrorMessage.ALREADY_ANSWERED_MEMBER; 19 | import static net.mureng.core.core.message.ErrorMessage.ALREADY_COOKIE_ACQUIRED; 20 | 21 | @Slf4j 22 | @Service 23 | @RequiredArgsConstructor 24 | public class ReplyPostProcessServiceImpl implements ReplyPostProcessService { 25 | private final MemberRepository memberRepository; 26 | private final CookieAcquirementService cookieAcquirementService; 27 | private final CookieAcquirementRepository cookieAcquirementRepository; 28 | 29 | @Transactional 30 | public void postProcess(Reply reply) { 31 | Member member = reply.getAuthor(); 32 | if( !cookieAcquirementService.isAlreadyCookieAcquiredToday(member.getMemberId()) ) { 33 | log.info(ALREADY_COOKIE_ACQUIRED); 34 | cookieAcquirementService.acquireMurengCookie(member); 35 | } 36 | 37 | memberRepository.saveAndFlush(member); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/todayexpression/entity/TodayUsefulExpression.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.todayexpression.entity; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.*; 6 | import java.time.LocalDateTime; 7 | 8 | @Builder 9 | @Getter @Setter 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Entity 13 | @Table(name = "today_useful_expression") 14 | public class TodayUsefulExpression { 15 | 16 | @Id 17 | @GeneratedValue(strategy = GenerationType.IDENTITY) 18 | @JoinColumn(name = "id", nullable = false) 19 | private Long id; 20 | 21 | @OneToOne 22 | @JoinColumn(name = "exp_id", nullable = false) 23 | private UsefulExpression usefulExpression; 24 | 25 | @Builder.Default 26 | @Column(name = "reg_date", nullable = false) 27 | private LocalDateTime regDate = LocalDateTime.now(); 28 | 29 | @Builder.Default 30 | @Column(name = "mod_date", nullable = false) 31 | private LocalDateTime modDate = LocalDateTime.now(); 32 | } 33 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/todayexpression/entity/UsefulExpression.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.todayexpression.entity; 2 | 3 | import lombok.*; 4 | import net.mureng.core.member.entity.Member; 5 | import net.mureng.core.member.entity.MemberScrap; 6 | 7 | import javax.persistence.*; 8 | import java.time.LocalDateTime; 9 | import java.util.HashSet; 10 | import java.util.Set; 11 | 12 | @Builder 13 | @Getter @Setter 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | @Entity 17 | @Table(name = "useful_expression") 18 | public class UsefulExpression { 19 | 20 | @Id 21 | @GeneratedValue(strategy = GenerationType.IDENTITY) 22 | @Column(name = "exp_id", nullable = false) 23 | private Long expId; 24 | 25 | @Column(nullable = false) 26 | private String expression; 27 | 28 | @Column(nullable = false) 29 | private String meaning; 30 | 31 | @Column(name = "expression_example", nullable = false) 32 | private String expressionExample; 33 | 34 | @Column(name = "expression_example_meaning", nullable = false) 35 | private String expressionExampleMeaning; 36 | 37 | @Builder.Default 38 | @Column(name = "reg_date", nullable = false) 39 | private LocalDateTime regDate = LocalDateTime.now(); 40 | 41 | @Builder.Default 42 | @Column(name = "mod_date", nullable = false) 43 | private LocalDateTime modDate = LocalDateTime.now(); 44 | 45 | @Builder.Default 46 | @OneToMany(mappedBy = "usefulExpression", cascade = CascadeType.ALL) 47 | private Set memberScraps = new HashSet<>(); 48 | 49 | public boolean scrappedByRequester(Member member){ 50 | for(MemberScrap memberScrap : this.memberScraps){ 51 | if(memberScrap.getId().getMemberId() == member.getMemberId()) 52 | return true; 53 | } 54 | return false; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/todayexpression/repository/TodayUsefulExpressionRepository.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.todayexpression.repository; 2 | 3 | import net.mureng.core.todayexpression.entity.TodayUsefulExpression; 4 | import net.mureng.core.todayexpression.entity.UsefulExpression; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | public interface TodayUsefulExpressionRepository extends JpaRepository { 8 | } 9 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/todayexpression/repository/UsefulExpressionRepository.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.todayexpression.repository; 2 | 3 | import net.mureng.core.todayexpression.entity.UsefulExpression; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface UsefulExpressionRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/core/todayexpression/service/UsefulExpressionService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.todayexpression.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.mureng.core.todayexpression.entity.TodayUsefulExpression; 5 | import net.mureng.core.todayexpression.entity.UsefulExpression; 6 | import net.mureng.core.todayexpression.repository.TodayUsefulExpressionRepository; 7 | import net.mureng.core.todayexpression.repository.UsefulExpressionRepository; 8 | import org.springframework.data.domain.Page; 9 | import org.springframework.data.domain.PageImpl; 10 | import org.springframework.data.domain.PageRequest; 11 | import org.springframework.data.domain.Pageable; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.transaction.annotation.Transactional; 14 | 15 | import java.time.LocalDate; 16 | import java.time.temporal.ChronoUnit; 17 | import java.util.List; 18 | import java.util.stream.Collectors; 19 | 20 | @Service 21 | @RequiredArgsConstructor 22 | public class UsefulExpressionService { 23 | private final TodayUsefulExpressionRepository todayUsefulExpressionRepository; 24 | 25 | /** 26 | * 매일 변경하는 로직 27 | */ 28 | @Transactional(readOnly = true) 29 | public List getTodayExpressions(){ 30 | return todayUsefulExpressionRepository.findAll().stream() 31 | .map(TodayUsefulExpression::getUsefulExpression) 32 | .collect(Collectors.toList()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/push/dto/NotificationRequest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.push.dto; 2 | 3 | import lombok.*; 4 | 5 | @Builder 6 | @NoArgsConstructor 7 | @AllArgsConstructor 8 | @Getter @Setter 9 | public class NotificationRequest { 10 | private String token; 11 | private String title; 12 | private String message; 13 | private String image; 14 | private String clickAction; 15 | private String channelId; 16 | } 17 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/push/service/FcmLikePushService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.push.service; 2 | 3 | import net.mureng.core.member.entity.Member; 4 | import net.mureng.core.reply.entity.Reply; 5 | 6 | public interface FcmLikePushService { 7 | void pushToAuthor(Reply reply, Member likedMember); 8 | } 9 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/push/service/FcmLikePushServiceImpl.java: -------------------------------------------------------------------------------- 1 | package net.mureng.push.service; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import net.mureng.core.member.entity.FcmToken; 5 | import net.mureng.core.member.entity.Member; 6 | import net.mureng.core.member.repository.FcmTokenRepository; 7 | import net.mureng.core.reply.entity.Reply; 8 | import net.mureng.push.dto.NotificationRequest; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.util.Optional; 12 | 13 | @Service 14 | @RequiredArgsConstructor 15 | public class FcmLikePushServiceImpl implements FcmLikePushService { 16 | private static final String LIKE_PUSH_TITLE = "머렝"; 17 | private static final String LIKE_PUSH_MESSAGE_TEMPLATE = "%s님이 회원님의 글을 좋아해요❤"; 18 | private static final String LIKE_PUSH_CLICK_ACTION = "MAIN"; 19 | private static final String LIKE_PUSH_CHANNEL_ID = "LIKE_CHANNEL"; 20 | 21 | private final FcmTokenRepository fcmTokenRepository; 22 | private final FcmService fcmService; 23 | 24 | @Override 25 | public void pushToAuthor(Reply reply, Member likedMember) { 26 | Optional fcmToken = fcmTokenRepository.findByMemberMemberId(reply.getAuthor().getMemberId()); 27 | if (! reply.getAuthor().getMemberSetting().isLikePushActive() || fcmToken.isEmpty()) { 28 | return; 29 | } 30 | 31 | fcmService.send(NotificationRequest.builder() 32 | .token(fcmToken.get().getToken()) 33 | .title(LIKE_PUSH_TITLE) 34 | .message(String.format(LIKE_PUSH_MESSAGE_TEMPLATE, likedMember.getNickname())) 35 | .clickAction(LIKE_PUSH_CLICK_ACTION) 36 | .channelId(LIKE_PUSH_CHANNEL_ID) 37 | .build()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /mureng-core/src/main/java/net/mureng/push/service/FcmService.java: -------------------------------------------------------------------------------- 1 | package net.mureng.push.service; 2 | 3 | import com.google.firebase.messaging.*; 4 | import lombok.extern.slf4j.Slf4j; 5 | import net.mureng.push.dto.NotificationRequest; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.concurrent.ExecutionException; 9 | 10 | @Slf4j 11 | @Service 12 | public class FcmService { 13 | public void send(final NotificationRequest notificationRequest) { 14 | Message message = Message.builder() 15 | .setToken(notificationRequest.getToken()) 16 | .setNotification(new Notification(notificationRequest.getTitle(), 17 | notificationRequest.getMessage())) 18 | .setWebpushConfig(WebpushConfig.builder().putHeader("ttl", "300") 19 | .setNotification(new WebpushNotification(notificationRequest.getTitle(), 20 | notificationRequest.getMessage(), 21 | notificationRequest.getImage())) 22 | .build()) 23 | .setAndroidConfig(AndroidConfig.builder() 24 | .setNotification(AndroidNotification.builder() 25 | .setClickAction(notificationRequest.getClickAction()) 26 | .setChannelId(notificationRequest.getChannelId()) 27 | .build()) 28 | .build()) 29 | .build(); 30 | 31 | try { 32 | String response = FirebaseMessaging.getInstance().send(message); 33 | log.info("Sent message: " + response); 34 | } catch (FirebaseMessagingException e) { 35 | log.error("Sent message was failed with token : " + notificationRequest.getToken(), e); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mureng-core/src/main/resources/application-dev.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | # JPA 3 | jpa: 4 | show_sql: false 5 | properties: 6 | hibernate: 7 | format_sql: false 8 | 9 | # JWT 10 | jwt: 11 | secret: 12345678901234567890123456789012 12 | 13 | liquibase: 14 | enabled: false 15 | 16 | # logging 17 | logging: 18 | level: 19 | org: 20 | hibernate: 21 | type: 22 | descriptor: 23 | sql: error 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /mureng-core/src/main/resources/application-local.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | liquibase: 3 | enabled: false 4 | jpa: 5 | hibernate: 6 | ddl-auto: create-drop 7 | sql: 8 | init: 9 | data-locations: local-test-data.sql -------------------------------------------------------------------------------- /mureng-core/src/main/resources/application-prod.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | # JPA 3 | jpa: 4 | show_sql: false 5 | properties: 6 | hibernate: 7 | format_sql: false 8 | 9 | liquibase: 10 | enabled: false 11 | 12 | # logging 13 | logging: 14 | level: 15 | org: 16 | hibernate: 17 | type: 18 | descriptor: 19 | sql: error 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /mureng-core/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: default 4 | 5 | # JPA 6 | jpa: 7 | hibernate: 8 | ddl-auto: validate 9 | show_sql: true 10 | properties: 11 | hibernate: 12 | format_sql: true 13 | # Batch 14 | batch: 15 | job: 16 | enabled: false 17 | 18 | # JWT 19 | jwt: 20 | secret: 12345678901234567890123456789012 21 | 22 | # liquibase 23 | liquibase: 24 | change-log: classpath:config/liquibase/master.xml 25 | # enabled: false 26 | social: 27 | KAKAO: 28 | url: 29 | profile: https://kapi.kakao.com/v2/user/me 30 | GOOGLE: 31 | url: 32 | profile: https://www.googleapis.com/oauth2/v3/userinfo 33 | 34 | 35 | # logging 36 | logging: 37 | level: 38 | org: 39 | hibernate: 40 | type: 41 | descriptor: 42 | sql: trace 43 | net: 44 | mureng: 45 | info 46 | 47 | # server 48 | server: 49 | error: 50 | whitelabel: 51 | enabled: false 52 | 53 | servlet: 54 | encoding: 55 | charset: UTF-8 56 | enabled: true 57 | force: true 58 | 59 | 60 | # media 61 | media: 62 | base: 63 | dir: 64 | name: /home/gradle/mount 65 | 66 | 67 | # firebase 68 | firebase: 69 | service: 70 | key: 71 | 72 | cloud: 73 | aws: 74 | credentials: 75 | access_key: CLOUD_AWS_CREDENTIALS_ACCESS_KEY 76 | secret_key: CLOUD_AWS_CREDENTIALS_SECRET_KEY 77 | s3: 78 | bucket: CLOUD_AWS_S3_BUCKET 79 | region: 80 | static: ap-northeast-2 -------------------------------------------------------------------------------- /mureng-core/src/main/resources/config/liquibase/changelog/20200527_add_unique_constraint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /mureng-core/src/main/resources/config/liquibase/changelog/20210618_update.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /mureng-core/src/main/resources/config/liquibase/changelog/20210619_fcm_token.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /mureng-core/src/main/resources/config/liquibase/changelog/20210620_fcm_token_member_unique.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /mureng-core/src/main/resources/config/liquibase/changelog/20210622_add_badge_is_checked_field.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /mureng-core/src/main/resources/config/liquibase/changelog/20210708_add_cookie_entity.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /mureng-core/src/main/resources/config/liquibase/changelog/20210708_drop_member_mureng_count.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /mureng-core/src/main/resources/config/liquibase/changelog/20210708_today_expression.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /mureng-core/src/main/resources/config/liquibase/master.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /mureng-core/src/test/java/net/mureng/core/annotation/MurengDataTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.annotation; 2 | 3 | import com.github.springtestdbunit.TransactionDbUnitTestExecutionListener; 4 | import com.github.springtestdbunit.annotation.DbUnitConfiguration; 5 | import net.mureng.core.config.DbUnitConfig; 6 | import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; 7 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 8 | import org.springframework.context.annotation.Import; 9 | import org.springframework.test.context.ContextConfiguration; 10 | import org.springframework.test.context.TestExecutionListeners; 11 | import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; 12 | import org.springframework.transaction.annotation.Transactional; 13 | 14 | import java.lang.annotation.ElementType; 15 | import java.lang.annotation.Retention; 16 | import java.lang.annotation.RetentionPolicy; 17 | import java.lang.annotation.Target; 18 | 19 | @Target(ElementType.TYPE) 20 | @Retention(RetentionPolicy.RUNTIME) 21 | @Import(DbUnitConfig.class) 22 | @ContextConfiguration 23 | @DataJpaTest 24 | @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) 25 | @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, 26 | TransactionDbUnitTestExecutionListener.class }) 27 | @DbUnitConfiguration(databaseConnection = "dbUnitDatabaseConnection") 28 | @Transactional 29 | public @interface MurengDataTest { 30 | } 31 | -------------------------------------------------------------------------------- /mureng-core/src/test/java/net/mureng/core/config/DbUnitConfig.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.config; 2 | 3 | import com.github.springtestdbunit.bean.DatabaseConfigBean; 4 | import com.github.springtestdbunit.bean.DatabaseDataSourceConnectionFactoryBean; 5 | import org.dbunit.database.DefaultMetadataHandler; 6 | import org.dbunit.ext.h2.H2DataTypeFactory; 7 | import org.springframework.beans.factory.annotation.Qualifier; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | import javax.sql.DataSource; 12 | 13 | 14 | @Configuration 15 | public class DbUnitConfig { 16 | 17 | @Bean 18 | public DatabaseConfigBean dbUnitDatabaseConfig() { 19 | DatabaseConfigBean config = new DatabaseConfigBean(); 20 | config.setAllowEmptyFields(true); 21 | config.setDatatypeFactory(new H2DataTypeFactory()); 22 | config.setMetadataHandler(new DefaultMetadataHandler()); 23 | return config; 24 | } 25 | 26 | @Bean 27 | public DatabaseDataSourceConnectionFactoryBean dbUnitDatabaseConnection(@Qualifier("dataSource") DataSource dataSource) { 28 | DatabaseDataSourceConnectionFactoryBean dbUnitDatabaseConnection = new DatabaseDataSourceConnectionFactoryBean(); 29 | dbUnitDatabaseConnection.setDataSource(dataSource); 30 | dbUnitDatabaseConnection.setDatabaseConfig(dbUnitDatabaseConfig()); 31 | return dbUnitDatabaseConnection; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /mureng-core/src/test/java/net/mureng/core/cookie/repository/CookieAcquirementRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.cookie.repository; 2 | 3 | import com.github.springtestdbunit.annotation.DatabaseSetup; 4 | import net.mureng.core.annotation.MurengDataTest; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | 8 | import java.time.LocalDate; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertTrue; 11 | 12 | @MurengDataTest 13 | @DatabaseSetup({ 14 | "classpath:dbunit/entity/member.xml", 15 | "classpath:dbunit/entity/cookie_acquirement.xml" 16 | }) 17 | public class CookieAcquirementRepositoryTest { 18 | @Autowired 19 | private CookieAcquirementRepository cookieRepository; 20 | 21 | public static final Long MEMBER_ID = 1L; 22 | 23 | @Test 24 | public void 오늘_쿠키_획득했는지_확인(){ 25 | LocalDate date = LocalDate.of(2020, 10,15); 26 | 27 | assertTrue(cookieRepository.existsByRegDateAndMemberMemberId(date, MEMBER_ID)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mureng-core/src/test/java/net/mureng/core/question/repository/TodayQuestionRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.question.repository; 2 | 3 | import com.github.springtestdbunit.annotation.DatabaseSetup; 4 | import net.mureng.core.annotation.MurengDataTest; 5 | import net.mureng.core.question.entity.TodayQuestion; 6 | import net.mureng.core.question.entity.WordHint; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | 10 | import java.util.Set; 11 | 12 | import static org.junit.jupiter.api.Assertions.*; 13 | 14 | @MurengDataTest 15 | class TodayQuestionRepositoryTest { 16 | public static final Long MEMBER_ID = 1L; 17 | 18 | @Autowired 19 | private TodayQuestionRepository todayQuestionRepository; 20 | 21 | @Test 22 | @DatabaseSetup({ 23 | "classpath:dbunit/entity/member.xml", 24 | "classpath:dbunit/entity/question.xml", 25 | "classpath:dbunit/entity/today_question.xml" 26 | }) 27 | public void 오늘의_질문_테스트() { 28 | TodayQuestion todayQuestion = todayQuestionRepository.findById(MEMBER_ID).orElseThrow(); 29 | 30 | assertEquals(MEMBER_ID, todayQuestion.getMemberId()); 31 | assertEquals("Test", todayQuestion.getMember().getNickname()); 32 | assertEquals("what is your favorite color?", todayQuestion.getQuestion().getContent()); 33 | } 34 | 35 | @Test 36 | @DatabaseSetup({ 37 | "classpath:dbunit/entity/member.xml", 38 | "classpath:dbunit/entity/question.xml", 39 | "classpath:dbunit/entity/today_question.xml", 40 | "classpath:dbunit/entity/word_hint.xml" 41 | }) 42 | public void 오늘의_질문_연관_단어힌트_테스트() { 43 | TodayQuestion todayQuestion = todayQuestionRepository.findById(MEMBER_ID).orElseThrow(); 44 | Set wordHints = todayQuestion.getQuestion().getWordHints(); 45 | assertEquals(2, wordHints.size()); 46 | } 47 | } -------------------------------------------------------------------------------- /mureng-core/src/test/java/net/mureng/core/question/repository/WordHintRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.question.repository; 2 | 3 | import com.github.springtestdbunit.annotation.DatabaseSetup; 4 | import net.mureng.core.annotation.MurengDataTest; 5 | import net.mureng.core.question.entity.Question; 6 | import net.mureng.core.question.entity.WordHint; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | 10 | import java.util.List; 11 | 12 | import static org.junit.jupiter.api.Assertions.assertEquals; 13 | 14 | @MurengDataTest 15 | @DatabaseSetup({ 16 | "classpath:dbunit/entity/member.xml", 17 | "classpath:dbunit/entity/question.xml", 18 | "classpath:dbunit/entity/word_hint.xml" 19 | }) 20 | public class WordHintRepositoryTest { 21 | 22 | private static final Long QUESTION_ID = 1L; 23 | 24 | @Autowired 25 | WordHintRepository wordHintRepository; 26 | 27 | @Autowired 28 | QuestionRepository questionRepository; 29 | 30 | @Test 31 | public void 질문의_단어_힌트_조회(){ 32 | Question question = questionRepository.findById(QUESTION_ID).orElseThrow(); 33 | 34 | List wordHintList = wordHintRepository.findAllByQuestion(question); 35 | 36 | assertEquals(2, wordHintList.size()); 37 | 38 | assertEquals("blue", wordHintList.get(0).getWord()); 39 | assertEquals("파란색", wordHintList.get(0).getMeaning()); 40 | 41 | assertEquals("coral", wordHintList.get(1).getWord()); 42 | assertEquals("코랄색", wordHintList.get(1).getMeaning()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mureng-core/src/test/java/net/mureng/core/reply/repository/ReplyLikesRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package net.mureng.core.reply.repository; 2 | 3 | import com.github.springtestdbunit.annotation.DatabaseSetup; 4 | import net.mureng.core.annotation.MurengDataTest; 5 | import net.mureng.core.reply.entity.ReplyLikesPK; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | @MurengDataTest 12 | @DatabaseSetup({ 13 | "classpath:dbunit/entity/member.xml", 14 | "classpath:dbunit/entity/question.xml", 15 | "classpath:dbunit/entity/reply.xml", 16 | "classpath:dbunit/entity/reply_likes.xml" 17 | }) 18 | public class ReplyLikesRepositoryTest { 19 | @Autowired 20 | ReplyLikesRepository replyLikesRepository; 21 | 22 | private static final Long REPLY_ID = 1L; 23 | 24 | @Test 25 | public void 답변_좋아요_수_조회(){ 26 | int replyCount = replyLikesRepository.countByIdReplyId(REPLY_ID); 27 | 28 | assertEquals(2, replyCount); 29 | } 30 | 31 | @Test 32 | public void 좋아요_존재_확인(){ 33 | ReplyLikesPK existReplyLikesPK = new ReplyLikesPK(1L, 1L); 34 | ReplyLikesPK notExistReplyLikesPK = new ReplyLikesPK(3L, 3L); 35 | 36 | assertTrue(replyLikesRepository.existsById(existReplyLikesPK)); 37 | assertFalse(replyLikesRepository.existsById(notExistReplyLikesPK)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /mureng-core/src/test/resources/dbunit/entity/badge.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mureng-core/src/test/resources/dbunit/entity/badge_accomplished.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mureng-core/src/test/resources/dbunit/entity/cookie_acquirement.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mureng-core/src/test/resources/dbunit/entity/member.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /mureng-core/src/test/resources/dbunit/entity/member_scrap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mureng-core/src/test/resources/dbunit/entity/question.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /mureng-core/src/test/resources/dbunit/entity/reply.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /mureng-core/src/test/resources/dbunit/entity/reply_likes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mureng-core/src/test/resources/dbunit/entity/today_question.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mureng-core/src/test/resources/dbunit/entity/useful_expression.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /mureng-core/src/test/resources/dbunit/entity/word_hint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /mureng-core/src/test/resources/dbunit/expected/멤버_머렝_카운트_증가.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 12 | 13 | -------------------------------------------------------------------------------- /mureng-core/src/test/resources/dbunit/expected/멤버_회원가입.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | 7 | rootProject.name = 'mureng' 8 | include 'mureng-core' 9 | include 'mureng-api' 10 | include 'mureng-batch' 11 | --------------------------------------------------------------------------------