├── .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 | 
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 extends GrantedAuthority> 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 extends Payload>[] 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 extends Payload>[] 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 extends Payload>[] 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 extends Job> 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 extends Job> 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 |
--------------------------------------------------------------------------------