├── infra ├── certbot │ └── .gitkeep ├── redis │ ├── start.sh │ └── docker-compose.yaml ├── mysql │ ├── start.sh │ └── Dockerfile └── nginx │ ├── Dockerfile │ └── conf.d │ ├── app-server.conf │ └── nginx.conf ├── lombok.config ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitmodules ├── cakk-common ├── src │ └── main │ │ └── kotlin │ │ └── com │ │ └── cakk │ │ └── common │ │ ├── utils │ │ ├── ConstUtils.kt │ │ ├── DateUtils.kt │ │ ├── DecodeUtils.kt │ │ ├── RandomUtils.kt │ │ └── SetUtils.kt │ │ ├── enums │ │ ├── Gender.kt │ │ ├── Provider.kt │ │ ├── Role.kt │ │ ├── LinkKind.kt │ │ ├── Days.kt │ │ ├── CakeDesignCategory.kt │ │ ├── VerificationStatus.kt │ │ └── RedisKey.kt │ │ ├── exception │ │ └── CakkException.kt │ │ └── response │ │ └── ApiResponse.kt └── build.gradle.kts ├── cakk-domain ├── build.gradle.kts ├── mysql │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── cakk │ │ │ └── domain │ │ │ └── mysql │ │ │ ├── dto │ │ │ └── param │ │ │ │ ├── tag │ │ │ │ └── TagParam.java │ │ │ │ ├── shop │ │ │ │ ├── CakeShopLinkParam.java │ │ │ │ ├── CakeShopSimpleParam.java │ │ │ │ ├── CakeShopSearchParam.java │ │ │ │ ├── CakeShopInfoParam.java │ │ │ │ ├── CakeShopOperationParam.java │ │ │ │ ├── ShopOperationParam.java │ │ │ │ ├── CakeShopLocationResponseParam.java │ │ │ │ ├── CakeShopSearchResponseParam.java │ │ │ │ ├── CakeShopUpdateParam.java │ │ │ │ ├── UpdateShopAddressParam.java │ │ │ │ └── CakeShopDetailParam.java │ │ │ │ ├── like │ │ │ │ ├── HeartCakeImageResponseParam.java │ │ │ │ └── HeartCakeShopResponseParam.java │ │ │ │ ├── cake │ │ │ │ ├── CakeImageWithShopInfoResponseParam.java │ │ │ │ └── CakeDetailParam.java │ │ │ │ ├── link │ │ │ │ └── UpdateLinkParam.java │ │ │ │ ├── user │ │ │ │ ├── CertificationParam.java │ │ │ │ └── ProfileUpdateParam.java │ │ │ │ └── operation │ │ │ │ └── UpdateShopOperationParam.java │ │ │ ├── repository │ │ │ ├── jpa │ │ │ │ ├── CakeJpaRepository.kt │ │ │ │ ├── CakeShopJpaRepository.kt │ │ │ │ ├── UserWithdrawalJpaRepository.kt │ │ │ │ ├── UserJpaRepository.kt │ │ │ │ ├── CakeShopLinkJpaRepository.kt │ │ │ │ ├── CakeCategoryJpaRepository.kt │ │ │ │ ├── CakeShopOperationJpaRepository.kt │ │ │ │ ├── TagJpaRepository.kt │ │ │ │ └── CakeHeartJpaRepository.kt │ │ │ └── query │ │ │ │ └── UserQueryRepository.java │ │ │ ├── mapper │ │ │ ├── TagMapper.java │ │ │ ├── CakeHeartMapper.java │ │ │ ├── CakeTagMapper.java │ │ │ ├── CakeShopLikeMapper.java │ │ │ ├── CakeShopHeartMapper.java │ │ │ └── EventMapper.java │ │ │ ├── event │ │ │ └── shop │ │ │ │ └── CertificationEvent.java │ │ │ ├── config │ │ │ ├── P6spyConfig.kt │ │ │ ├── JpaConfig.kt │ │ │ ├── QuerydslConfig.kt │ │ │ └── DataSourceConfig.kt │ │ │ ├── bo │ │ │ ├── shop │ │ │ │ ├── CakeShopBySearchParam.java │ │ │ │ ├── CakeShopByLocationParam.java │ │ │ │ ├── CakeShopParam.java │ │ │ │ └── CakeShops.java │ │ │ └── user │ │ │ │ └── VerificationPolicy.java │ │ │ ├── entity │ │ │ ├── audit │ │ │ │ ├── AuditCreatedEntity.java │ │ │ │ └── AuditEntity.java │ │ │ ├── cake │ │ │ │ ├── Tag.java │ │ │ │ ├── CakeHeart.java │ │ │ │ └── CakeTag.java │ │ │ └── shop │ │ │ │ ├── CakeShopLike.java │ │ │ │ └── CakeShopHeart.java │ │ │ └── converter │ │ │ ├── DayOfWeekConverter.kt │ │ │ ├── LinkKindConverter.kt │ │ │ └── VerificationStatusConverter.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── cakk │ │ └── domain │ │ └── annotation │ │ └── TestWithDisplayName.java └── redis │ ├── build.gradle.kts │ └── src │ └── main │ └── java │ └── com │ └── cakk │ └── domain │ └── redis │ ├── dto │ └── param │ │ └── ExecuteWithLockParam.java │ ├── template │ ├── RedisZSetTemplate.java │ └── RedisValueTemplate.java │ ├── annotation │ ├── RedisRepository.java │ └── RedisCustomTemplate.java │ └── repository │ ├── TokenRedisRepository.java │ ├── LockRedisRepository.java │ ├── KeywordRedisRepository.java │ ├── EmailVerificationRedisRepository.java │ ├── CakeViewsRedisRepository.java │ └── CakeShopViewsRedisRepository.java ├── cakk-core ├── src │ ├── main │ │ └── kotlin │ │ │ └── com │ │ │ └── cakk │ │ │ └── core │ │ │ ├── dto │ │ │ ├── event │ │ │ │ ├── CakeIncreaseViewsEvent.kt │ │ │ │ ├── IncreaseSearchCountEvent.kt │ │ │ │ ├── CakeShopIncreaseViewsEvent.kt │ │ │ │ ├── EmailWithVerificationCodeSendEvent.kt │ │ │ │ └── ErrorAlertEvent.kt │ │ │ ├── param │ │ │ │ ├── user │ │ │ │ │ ├── GenerateCodeParam.kt │ │ │ │ │ ├── VerifyEmailParam.kt │ │ │ │ │ ├── UserSignInParam.kt │ │ │ │ │ ├── OwnerCandidateParam.kt │ │ │ │ │ └── UserSignUpParam.kt │ │ │ │ ├── search │ │ │ │ │ ├── TopSearchedListParam.kt │ │ │ │ │ ├── CakeShopSearchByViewsParam.kt │ │ │ │ │ ├── SearchShopByLocationParam.kt │ │ │ │ │ ├── HeartCakeSearchParam.kt │ │ │ │ │ └── HeartCakeShopSearchParam.kt │ │ │ │ ├── shop │ │ │ │ │ ├── PromotionParam.kt │ │ │ │ │ ├── ShopLinkParam.kt │ │ │ │ │ ├── ShopOperationParam.kt │ │ │ │ │ └── CreateShopParam.kt │ │ │ │ └── cake │ │ │ │ │ ├── CakeSearchByViewsParam.kt │ │ │ │ │ ├── CakeSearchByShopParam.kt │ │ │ │ │ ├── CakeSearchByCategoryParam.kt │ │ │ │ │ ├── CakeSearchParam.kt │ │ │ │ │ ├── CakeUpdateParam.kt │ │ │ │ │ └── CakeCreateParam.kt │ │ │ └── response │ │ │ │ ├── like │ │ │ │ ├── HeartResponse.kt │ │ │ │ ├── HeartCakeImageListResponse.kt │ │ │ │ └── HeartCakeShopListResponse.kt │ │ │ │ ├── shop │ │ │ │ ├── CakeShopCreateResponse.kt │ │ │ │ ├── CakeShopOwnerResponse.kt │ │ │ │ ├── CakeShopByMineResponse.kt │ │ │ │ ├── CakeShopSimpleResponse.kt │ │ │ │ ├── CakeShopOwnerCandidatesResponse.kt │ │ │ │ ├── CakeShopByMapResponse.kt │ │ │ │ ├── CakeShopSearchResponse.kt │ │ │ │ ├── CakeShopOwnerCandidateResponse.kt │ │ │ │ ├── CakeShopInfoResponse.kt │ │ │ │ └── CakeShopDetailResponse.kt │ │ │ │ ├── user │ │ │ │ ├── JwtResponse.kt │ │ │ │ └── ProfileInformationResponse.kt │ │ │ │ ├── search │ │ │ │ └── TopSearchedListResponse.kt │ │ │ │ └── cake │ │ │ │ ├── CakeImageWithShopInfoListResponse.kt │ │ │ │ └── CakeDetailResponse.kt │ │ │ ├── vo │ │ │ └── JsonWebToken.kt │ │ │ ├── mapper │ │ │ ├── HeartMapper.kt │ │ │ ├── SearchMapper.kt │ │ │ ├── CakeDesignCategoryMapper.kt │ │ │ ├── PointMapper.kt │ │ │ └── ShopOperationMapper.kt │ │ │ ├── dispatcher │ │ │ └── OidcProviderDispatcher.kt │ │ │ ├── facade │ │ │ ├── user │ │ │ │ ├── UserLikeFacade.kt │ │ │ │ ├── UserHeartFacade.kt │ │ │ │ ├── UserReadFacade.kt │ │ │ │ └── UserManageFacade.kt │ │ │ ├── tag │ │ │ │ ├── TagReadFacade.kt │ │ │ │ └── TagManageFacade.kt │ │ │ ├── shop │ │ │ │ └── CakeShopManageFacade.kt │ │ │ └── cake │ │ │ │ ├── CakeManageFacade.kt │ │ │ │ └── CakeShopUserReadFacade.kt │ │ │ ├── annotation │ │ │ ├── DistributedLock.kt │ │ │ ├── DomainFacade.kt │ │ │ └── ApplicationEventListener.kt │ │ │ ├── provider │ │ │ ├── jwt │ │ │ │ └── JwtProvider.kt │ │ │ └── oauth │ │ │ │ └── OidcProvider.kt │ │ │ ├── service │ │ │ ├── like │ │ │ │ └── LikeService.kt │ │ │ ├── search │ │ │ │ └── KeywordService.kt │ │ │ ├── views │ │ │ │ └── ViewsService.kt │ │ │ └── user │ │ │ │ └── UserService.kt │ │ │ ├── aspect │ │ │ └── AopForTransaction.kt │ │ │ ├── utils │ │ │ └── CustomSpringExpressionLanguageParser.kt │ │ │ └── listener │ │ │ ├── SearchEventListener.kt │ │ │ ├── EmailSendEventListener.kt │ │ │ ├── ViewsIncreaseEventListener.kt │ │ │ ├── ErrorAlertEventListener.kt │ │ │ └── CertificationEventListener.kt │ └── test │ │ └── kotlin │ │ └── com │ │ └── cakk │ │ └── core │ │ └── common │ │ └── annotation │ │ └── TestWithDisplayName.kt └── build.gradle.kts ├── cakk-external ├── src │ └── main │ │ └── kotlin │ │ └── com │ │ └── cakk │ │ └── external │ │ ├── sender │ │ ├── MessageSender.kt │ │ ├── SlackMessageSender.kt │ │ └── EmailMessageSender.kt │ │ ├── extractor │ │ ├── MessageExtractor.kt │ │ ├── MimeMessageExtractor.kt │ │ └── SlackMessageExtractor.kt │ │ ├── vo │ │ ├── message │ │ │ ├── VerificationMessage.kt │ │ │ ├── ErrorAlertMessage.kt │ │ │ └── CertificationMessage.kt │ │ ├── s3 │ │ │ └── PresignedUrl.kt │ │ └── key │ │ │ ├── OidcPublicKey.kt │ │ │ └── OidcPublicKeyList.kt │ │ ├── template │ │ └── MessageTemplate.kt │ │ ├── config │ │ ├── SlackWebhookConfig.kt │ │ ├── RestClientConfig.kt │ │ └── S3Config.kt │ │ └── client │ │ ├── AppleAuthClient.kt │ │ └── KakaoAuthClient.kt └── build.gradle.kts ├── .github ├── pull_request_template.md ├── ISSUE_TEMPLATE │ ├── feature-template.md │ ├── docs-edit-template.md │ ├── fix-template.md │ └── refactor-template.md └── workflows │ └── weekly-batch-job.yml ├── cakk-api ├── src │ ├── main │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── cakk │ │ │ │ └── api │ │ │ │ ├── annotation │ │ │ │ ├── AccessToken.kt │ │ │ │ ├── SignInUser.kt │ │ │ │ ├── RefreshToken.kt │ │ │ │ └── OperationDay.kt │ │ │ │ ├── dto │ │ │ │ └── request │ │ │ │ │ ├── user │ │ │ │ │ ├── GenerateCodeRequest.kt │ │ │ │ │ ├── VerifyEmailRequest.kt │ │ │ │ │ ├── UserSignInRequest.kt │ │ │ │ │ ├── CertificationRequest.kt │ │ │ │ │ ├── ProfileUpdateRequest.kt │ │ │ │ │ └── UserSignUpRequest.kt │ │ │ │ │ ├── search │ │ │ │ │ └── TopSearchedListRequest.kt │ │ │ │ │ ├── cake │ │ │ │ │ ├── CakeSearchByViewsRequest.kt │ │ │ │ │ ├── CakeSearchByShopRequest.kt │ │ │ │ │ ├── CakeSearchByCategoryRequest.kt │ │ │ │ │ ├── CakeSearchByLocationRequest.kt │ │ │ │ │ ├── CakeCreateRequest.kt │ │ │ │ │ └── CakeUpdateRequest.kt │ │ │ │ │ ├── like │ │ │ │ │ ├── HeartCakeSearchRequest.kt │ │ │ │ │ └── HeartCakeShopSearchRequest.kt │ │ │ │ │ ├── shop │ │ │ │ │ ├── CakeShopSearchByViewsRequest.kt │ │ │ │ │ ├── PromotionRequest.kt │ │ │ │ │ ├── UpdateShopAddressRequest.kt │ │ │ │ │ ├── UpdateShopRequest.kt │ │ │ │ │ ├── CakeShopSearchRequest.kt │ │ │ │ │ ├── SearchShopByLocationRequest.kt │ │ │ │ │ └── CreateShopRequest.kt │ │ │ │ │ ├── operation │ │ │ │ │ └── UpdateShopOperationRequest.kt │ │ │ │ │ └── link │ │ │ │ │ └── UpdateLinkRequest.kt │ │ │ │ ├── config │ │ │ │ ├── AsyncConfig.kt │ │ │ │ ├── CacheConfig.kt │ │ │ │ ├── AspectConfig.kt │ │ │ │ ├── ComponentScanConfig.kt │ │ │ │ ├── JwtConfig.kt │ │ │ │ ├── WebMvcConfig.kt │ │ │ │ ├── GoogleConfig.kt │ │ │ │ └── JacksonConfig.kt │ │ │ │ ├── Application.kt │ │ │ │ ├── controller │ │ │ │ ├── s3 │ │ │ │ │ └── AwsS3Controller.kt │ │ │ │ └── search │ │ │ │ │ └── KeywordController.kt │ │ │ │ ├── provider │ │ │ │ └── oauth │ │ │ │ │ ├── AppleAuthProvider.kt │ │ │ │ │ ├── KakaoAuthProvider.kt │ │ │ │ │ └── GoogleAuthProvider.kt │ │ │ │ ├── validator │ │ │ │ └── OperationValidator.kt │ │ │ │ └── resolver │ │ │ │ ├── RefreshTokenResolver.kt │ │ │ │ ├── AuthorizedUserResolver.kt │ │ │ │ └── AccessTokenResolver.kt │ │ └── resources │ │ │ ├── app-banner.dat │ │ │ └── application.yml │ └── test │ │ ├── resources │ │ └── sql │ │ │ ├── delete-all.sql │ │ │ ├── insert-heart.sql │ │ │ ├── insert-like-for-concurrency-test.sql │ │ │ └── insert-business-information.sql │ │ └── java │ │ └── com │ │ └── cakk │ │ └── api │ │ ├── common │ │ ├── annotation │ │ │ ├── MockCustomUser.kt │ │ │ └── TestWithDisplayName.kt │ │ ├── utils │ │ │ └── TestUtils.kt │ │ ├── base │ │ │ └── MockitoTest.kt │ │ ├── fixture │ │ │ └── MockGoogleIdToken.kt │ │ ├── container │ │ │ ├── MysqlTestContainer.java │ │ │ └── RedisTestContainer.java │ │ └── config │ │ │ └── WithMockCustomUserSecurityContextFactory.kt │ │ └── ApplicationTest.kt └── spy.log ├── cakk-admin ├── src │ └── main │ │ ├── kotlin │ │ └── com │ │ │ └── cakk │ │ │ └── admin │ │ │ ├── annotation │ │ │ ├── AdminUser.kt │ │ │ └── OperationDay.kt │ │ │ ├── dto │ │ │ └── request │ │ │ │ ├── PromotionRequest.kt │ │ │ │ ├── ShopOperationUpdateByAdminRequest.kt │ │ │ │ ├── LinkUpdateByAdminRequest.kt │ │ │ │ ├── CakeShopUpdateByAdminRequest.kt │ │ │ │ ├── CakeUpdateByAdminRequest.kt │ │ │ │ ├── CakeCreateByAdminRequest.kt │ │ │ │ ├── AddressUpdateByAdminRequest.kt │ │ │ │ └── CakeShopCreateByAdminRequest.kt │ │ │ ├── config │ │ │ └── ComponentScanConfig.kt │ │ │ ├── AdminApplication.kt │ │ │ ├── mapper │ │ │ ├── CakeDesignCategoryMapper.kt │ │ │ ├── ShopOperationMapper.kt │ │ │ ├── LinkMapper.kt │ │ │ └── CakeMapper.kt │ │ │ ├── validator │ │ │ └── OperationValidator.kt │ │ │ └── resolver │ │ │ └── AdminUserResolver.kt │ │ └── resources │ │ ├── application.yml │ │ └── application-local.yml └── build.gradle.kts ├── codecov.yml ├── gradle.properties ├── config └── checkstyle │ └── checkstyle-suppressions.xml ├── .editorconfig ├── cakk-batch ├── src │ └── main │ │ ├── java │ │ └── com │ │ │ └── cakk │ │ │ └── batch │ │ │ ├── utils │ │ │ ├── BatchConstants.java │ │ │ └── BatchUtils.java │ │ │ ├── config │ │ │ ├── ComponentScanConfig.java │ │ │ └── SlackWebhookConfig.java │ │ │ ├── BatchApplication.java │ │ │ ├── controller │ │ │ └── WeeklyJobController.java │ │ │ ├── handler │ │ │ └── LambdaHandler.java │ │ │ ├── tasklet │ │ │ ├── FindKeywordListTaskLet.java │ │ │ └── FindViewsCakeIdListTaskLet.java │ │ │ └── job │ │ │ └── launcher │ │ │ └── WeeklyJobLauncher.java │ │ └── resources │ │ ├── batch-banner.dat │ │ ├── application-local.yml │ │ └── application.yml └── build.gradle ├── appspec.yml ├── .gitignore ├── scripts ├── stop.sh └── start.sh └── settings.gradle.kts /infra/certbot/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /infra/redis/start.sh: -------------------------------------------------------------------------------- 1 | sudo docker-compose -d up 2 | -------------------------------------------------------------------------------- /lombok.config: -------------------------------------------------------------------------------- 1 | lombok.addLombokGeneratedAnnotation = true 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CAKK-DEV/cakk-server/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "server-secret"] 2 | path = server-secret 3 | url = https://github.com/CAKK-DEV/cakk-server-secret 4 | -------------------------------------------------------------------------------- /cakk-common/src/main/kotlin/com/cakk/common/utils/ConstUtils.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.common.utils 2 | 3 | const val DEFAULT_PAGE_SIZE = 10 4 | -------------------------------------------------------------------------------- /infra/mysql/start.sh: -------------------------------------------------------------------------------- 1 | docker build --platform linux/amd64 -t mysql . 2 | docker run --restart always -e TZ=Asia/Seoul --name mysql -d -p 3306:3306 mysql 3 | -------------------------------------------------------------------------------- /infra/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.19.6-alpine 2 | COPY ./config/nginx.conf /etc/nginx/conf.d/nginx.conf 3 | ENTRYPOINT ["nginx", "-g", "daemon off;"] 4 | -------------------------------------------------------------------------------- /cakk-common/build.gradle.kts: -------------------------------------------------------------------------------- 1 | description = "common module" 2 | 3 | tasks.bootJar { 4 | enabled = false 5 | } 6 | 7 | tasks.jar { 8 | enabled = true 9 | } 10 | -------------------------------------------------------------------------------- /cakk-common/src/main/kotlin/com/cakk/common/enums/Gender.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.common.enums 2 | 3 | enum class Gender { 4 | 5 | MALE, 6 | FEMALE, 7 | UNKNOWN 8 | } 9 | -------------------------------------------------------------------------------- /cakk-common/src/main/kotlin/com/cakk/common/enums/Provider.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.common.enums 2 | 3 | enum class Provider { 4 | 5 | APPLE, 6 | GOOGLE, 7 | KAKAO 8 | } 9 | -------------------------------------------------------------------------------- /cakk-common/src/main/kotlin/com/cakk/common/utils/DateUtils.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.common.utils 2 | 3 | fun currentTimeMillis(): Long = System.currentTimeMillis() 4 | 5 | -------------------------------------------------------------------------------- /cakk-domain/build.gradle.kts: -------------------------------------------------------------------------------- 1 | description = "domain module" 2 | 3 | tasks.bootJar { 4 | enabled = false 5 | } 6 | 7 | tasks.jar { 8 | enabled = true 9 | } 10 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/event/CakeIncreaseViewsEvent.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.event 2 | 3 | data class CakeIncreaseViewsEvent( 4 | val cakeId: Long 5 | ) 6 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/user/GenerateCodeParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.user 2 | 3 | data class GenerateCodeParam( 4 | val email: String 5 | ) 6 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/response/like/HeartResponse.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.response.like 2 | 3 | data class HeartResponse( 4 | val isHeart: Boolean 5 | ) 6 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/event/IncreaseSearchCountEvent.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.event 2 | 3 | data class IncreaseSearchCountEvent( 4 | val keyword: String 5 | ) 6 | -------------------------------------------------------------------------------- /cakk-external/src/main/kotlin/com/cakk/external/sender/MessageSender.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.external.sender 2 | 3 | fun interface MessageSender { 4 | 5 | fun send(message: T) 6 | } 7 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/event/CakeShopIncreaseViewsEvent.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.event 2 | 3 | data class CakeShopIncreaseViewsEvent( 4 | val cakeShopId: Long 5 | ) 6 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/search/TopSearchedListParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.search 2 | 3 | data class TopSearchedListParam( 4 | val count: Long 5 | ) 6 | 7 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | > ### Issue Number 2 | 3 | #0 4 | 5 | > ### Description 6 | 7 | - do 8 | - do 9 | 10 | > ### Core Code 11 | 12 | ```java 13 | 14 | ``` 15 | 16 | > ### etc 17 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/user/VerifyEmailParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.user 2 | 3 | data class VerifyEmailParam( 4 | val email: String, 5 | val code: String 6 | ) 7 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/response/shop/CakeShopCreateResponse.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.response.shop 2 | 3 | data class CakeShopCreateResponse( 4 | val cakeShopId: Long 5 | ) 6 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/response/shop/CakeShopOwnerResponse.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.response.shop 2 | 3 | data class CakeShopOwnerResponse( 4 | val isOwned: Boolean 5 | ) 6 | -------------------------------------------------------------------------------- /cakk-external/src/main/kotlin/com/cakk/external/extractor/MessageExtractor.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.external.extractor 2 | 3 | fun interface MessageExtractor { 4 | 5 | fun extract(message: T): U 6 | } 7 | -------------------------------------------------------------------------------- /infra/mysql/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mysql:8.0.35 2 | 3 | ENV MYSQL_ROOT_PASSWORD=root_password 4 | ENV MYSQL_DATABASE=databasename 5 | ENV MYSQL_USER=username 6 | ENV MYSQL_PASSWORD=password 7 | 8 | EXPOSE 3306 9 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/shop/PromotionParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.shop 2 | 3 | data class PromotionParam( 4 | val userId: Long, 5 | val cakeShopId: Long 6 | ) 7 | 8 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/cake/CakeSearchByViewsParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.cake 2 | 3 | data class CakeSearchByViewsParam( 4 | val offset: Long, 5 | val pageSize: Int 6 | ) 7 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/vo/JsonWebToken.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.vo 2 | 3 | data class JsonWebToken( 4 | val accessToken: String, 5 | val refreshToken: String, 6 | val grantType: String 7 | ) 8 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/tag/TagParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.tag; 2 | 3 | public record TagParam( 4 | Long tagId, 5 | String tagName 6 | ) { 7 | } 8 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/annotation/AccessToken.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.annotation 2 | 3 | @Target(AnnotationTarget.VALUE_PARAMETER) 4 | @Retention(AnnotationRetention.RUNTIME) 5 | annotation class AccessToken 6 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/annotation/SignInUser.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.annotation 2 | 3 | @Target(AnnotationTarget.VALUE_PARAMETER) 4 | @Retention(AnnotationRetention.RUNTIME) 5 | annotation class SignInUser 6 | -------------------------------------------------------------------------------- /cakk-admin/src/main/kotlin/com/cakk/admin/annotation/AdminUser.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.admin.annotation 2 | 3 | @Target(AnnotationTarget.VALUE_PARAMETER) 4 | @Retention(AnnotationRetention.RUNTIME) 5 | annotation class AdminUser 6 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/annotation/RefreshToken.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.annotation 2 | 3 | @Target(AnnotationTarget.VALUE_PARAMETER) 4 | @Retention(AnnotationRetention.RUNTIME) 5 | annotation class RefreshToken 6 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/search/CakeShopSearchByViewsParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.search 2 | 3 | data class CakeShopSearchByViewsParam( 4 | val offset: Long?, 5 | val pageSize: Int 6 | ) 7 | -------------------------------------------------------------------------------- /cakk-external/src/main/kotlin/com/cakk/external/vo/message/VerificationMessage.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.external.vo.message 2 | 3 | data class VerificationMessage( 4 | val receiver: String, 5 | val verificationCode: String 6 | ) 7 | -------------------------------------------------------------------------------- /cakk-external/src/main/kotlin/com/cakk/external/vo/s3/PresignedUrl.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.external.vo.s3 2 | 3 | data class PresignedUrl( 4 | val imagePath: String, 5 | val imageUrl: String, 6 | val presignedUrl: String 7 | ) 8 | -------------------------------------------------------------------------------- /cakk-common/src/main/kotlin/com/cakk/common/enums/Role.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.common.enums 2 | 3 | enum class Role { 4 | 5 | ADMIN, 6 | USER; 7 | 8 | val securityRole: String 9 | get() = "ROLE_$this" 10 | } 11 | -------------------------------------------------------------------------------- /cakk-common/src/main/kotlin/com/cakk/common/utils/DecodeUtils.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.common.utils 2 | 3 | import java.util.* 4 | 5 | fun decodeBase64(string: String): ByteArray { 6 | return Base64.getUrlDecoder().decode(string) 7 | } 8 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/event/EmailWithVerificationCodeSendEvent.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.event 2 | 3 | data class EmailWithVerificationCodeSendEvent( 4 | val email: String, 5 | val code: String 6 | ) 7 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/response/shop/CakeShopByMineResponse.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.response.shop 2 | 3 | data class CakeShopByMineResponse( 4 | val isExist: Boolean, 5 | val cakeShopId: Long? 6 | ) 7 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | 4 | comment: 5 | layout: "reach,diff,flags,files,footer" 6 | behavior: default 7 | require_changes: false 8 | require_base: no 9 | require_head: yes 10 | 11 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/response/user/JwtResponse.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.response.user 2 | 3 | data class JwtResponse( 4 | val accessToken: String, 5 | val refreshToken: String, 6 | val grantType: String 7 | ) 8 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/cake/CakeSearchByShopParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.cake 2 | 3 | data class CakeSearchByShopParam( 4 | val cakeId: Long? = null, 5 | val shopId: Long, 6 | val pageSize: Int 7 | ) 8 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ### Application Version ### 2 | applicationVersion=2.0.2-SNAPSHOT 3 | 4 | ### Project Configs ### 5 | projectGroup=com.cakk 6 | 7 | ### Project Dependency Versions ### 8 | kotlinVersion=2.0.10 9 | javaVersion=21 10 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/response/search/TopSearchedListResponse.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.response.search 2 | 3 | data class TopSearchedListResponse( 4 | val keywordList: List, 5 | val totalCount: Int 6 | ) 7 | -------------------------------------------------------------------------------- /cakk-common/src/main/kotlin/com/cakk/common/enums/LinkKind.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.common.enums 2 | 3 | enum class LinkKind( 4 | val value: String 5 | ) { 6 | 7 | WEB("web"), 8 | KAKAOTALK("kakaotalk"), 9 | INSTAGRAM("instagram"); 10 | } 11 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/search/SearchShopByLocationParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.search 2 | 3 | data class SearchShopByLocationParam( 4 | val latitude: Double, 5 | val longitude: Double, 6 | val distance: Double 7 | ) 8 | 9 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/user/GenerateCodeRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.user 2 | 3 | import jakarta.validation.constraints.NotBlank 4 | 5 | data class GenerateCodeRequest( 6 | @field:NotBlank 7 | val email: String? 8 | ) 9 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/shop/ShopLinkParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.shop 2 | 3 | import com.cakk.common.enums.LinkKind 4 | 5 | data class ShopLinkParam( 6 | val linkKind: LinkKind, 7 | val linkPath: String 8 | ) 9 | 10 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/user/UserSignInParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.user 2 | 3 | import com.cakk.common.enums.Provider 4 | 5 | data class UserSignInParam( 6 | val provider: Provider, 7 | val idToken: String 8 | ) 9 | 10 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/mapper/HeartMapper.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.mapper 2 | 3 | import com.cakk.core.dto.response.like.HeartResponse 4 | 5 | fun supplyHeartResponseBy(isHeart: Boolean): HeartResponse { 6 | return HeartResponse(isHeart) 7 | } 8 | -------------------------------------------------------------------------------- /config/checkstyle/checkstyle-suppressions.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/search/TopSearchedListRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.search 2 | 3 | import jakarta.validation.constraints.NotNull 4 | 5 | data class TopSearchedListRequest( 6 | @field:NotNull 7 | val count: Long? = 10 8 | ) 9 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/config/AsyncConfig.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.config 2 | 3 | import org.springframework.context.annotation.Configuration 4 | import org.springframework.scheduling.annotation.EnableAsync 5 | 6 | @Configuration 7 | @EnableAsync 8 | class AsyncConfig 9 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/config/CacheConfig.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.config 2 | 3 | import org.springframework.cache.annotation.EnableCaching 4 | import org.springframework.context.annotation.Configuration 5 | 6 | @Configuration 7 | @EnableCaching 8 | class CacheConfig 9 | -------------------------------------------------------------------------------- /cakk-common/src/main/kotlin/com/cakk/common/enums/Days.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.common.enums 2 | 3 | enum class Days( 4 | val code: Int 5 | ) { 6 | 7 | MON(0), 8 | TUE(1), 9 | WED(2), 10 | THU(3), 11 | FRI(4), 12 | SAT(5), 13 | SUN(6); 14 | } 15 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dispatcher/OidcProviderDispatcher.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dispatcher 2 | 3 | import com.cakk.common.enums.Provider 4 | 5 | fun interface OidcProviderDispatcher { 6 | 7 | fun getProviderId(provider: Provider, idToken: String): String; 8 | } 9 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/shop/CakeShopLinkParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.shop; 2 | 3 | import com.cakk.common.enums.LinkKind; 4 | 5 | public record CakeShopLinkParam( 6 | LinkKind linkKind, 7 | String linkPath 8 | ) { 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [{*.java,*.gradle,*.kt,*.kts}] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_style = tab 7 | indent_size = 4 8 | tab_width = 4 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/cake/CakeSearchByViewsRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.cake 2 | 3 | import com.cakk.common.utils.DEFAULT_PAGE_SIZE 4 | 5 | data class CakeSearchByViewsRequest( 6 | val offset: Long = 0, 7 | val pageSize: Int? = DEFAULT_PAGE_SIZE 8 | ) 9 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/event/ErrorAlertEvent.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.event 2 | 3 | import jakarta.servlet.http.HttpServletRequest 4 | 5 | data class ErrorAlertEvent( 6 | val exception: Exception, 7 | val request: HttpServletRequest, 8 | val profile: String 9 | ) 10 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/response/shop/CakeShopSimpleResponse.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.response.shop 2 | 3 | data class CakeShopSimpleResponse( 4 | val cakeShopId: Long, 5 | val thumbnailUrl: String, 6 | val cakeShopName: String, 7 | val cakeShopBio: String 8 | ) 9 | -------------------------------------------------------------------------------- /cakk-external/src/main/kotlin/com/cakk/external/vo/key/OidcPublicKey.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.external.vo.key 2 | 3 | data class OidcPublicKey( 4 | val kid: String, 5 | val kty: String, 6 | val alg: String, 7 | val use: String, 8 | val n: String, 9 | val e: String 10 | ) 11 | -------------------------------------------------------------------------------- /cakk-admin/src/main/kotlin/com/cakk/admin/dto/request/PromotionRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.admin.dto.request 2 | 3 | import jakarta.validation.constraints.NotNull 4 | 5 | data class PromotionRequest( 6 | @field:NotNull 7 | val userId: Long?, 8 | @field:NotNull 9 | val cakeShopId: Long? 10 | ) 11 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/config/AspectConfig.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.config 2 | 3 | import org.springframework.context.annotation.Configuration 4 | import org.springframework.context.annotation.EnableAspectJAutoProxy 5 | 6 | @EnableAspectJAutoProxy 7 | @Configuration 8 | class AspectConfig 9 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/search/HeartCakeSearchParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.search 2 | 3 | import com.cakk.domain.mysql.entity.user.User 4 | 5 | data class HeartCakeSearchParam( 6 | val cakeHeartId: Long?, 7 | val pageSize: Int, 8 | val user: User 9 | ) 10 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/shop/CakeShopSimpleParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.shop; 2 | 3 | public record CakeShopSimpleParam( 4 | Long cakeShopId, 5 | String thumbnailUrl, 6 | String cakeShopName, 7 | String cakeShopBio 8 | ) { 9 | } 10 | -------------------------------------------------------------------------------- /cakk-external/src/main/kotlin/com/cakk/external/extractor/MimeMessageExtractor.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.external.extractor 2 | 3 | import jakarta.mail.internet.MimeMessage 4 | 5 | interface MimeMessageExtractor : MessageExtractor { 6 | 7 | override fun extract(message: T): MimeMessage 8 | } 9 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/like/HeartCakeSearchRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.like 2 | 3 | import com.cakk.common.utils.DEFAULT_PAGE_SIZE 4 | 5 | data class HeartCakeSearchRequest( 6 | val cakeHeartId: Long? = null, 7 | val pageSize: Int? = DEFAULT_PAGE_SIZE 8 | ) 9 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/shop/CakeShopSearchByViewsRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.shop 2 | 3 | import com.cakk.common.utils.DEFAULT_PAGE_SIZE 4 | 5 | data class CakeShopSearchByViewsRequest( 6 | val offset: Long?, 7 | val pageSize: Int? = DEFAULT_PAGE_SIZE 8 | ) 9 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/shop/PromotionRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.shop 2 | 3 | import jakarta.validation.constraints.NotNull 4 | 5 | data class PromotionRequest( 6 | @field:NotNull 7 | val userId: Long?, 8 | @field:NotNull 9 | val cakeShopId: Long? 10 | ) 11 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/response/shop/CakeShopOwnerCandidatesResponse.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.response.shop 2 | 3 | import com.cakk.core.dto.param.user.OwnerCandidateParam 4 | 5 | data class CakeShopOwnerCandidatesResponse( 6 | val candidates: List 7 | ) 8 | 9 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/like/HeartCakeImageResponseParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.like; 2 | 3 | public record HeartCakeImageResponseParam( 4 | Long cakeShopId, 5 | Long cakeId, 6 | Long cakeHeartId, 7 | String cakeImageUrl 8 | ) { 9 | } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/user/VerifyEmailRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.user 2 | 3 | import jakarta.validation.constraints.NotBlank 4 | 5 | data class VerifyEmailRequest( 6 | @field:NotBlank 7 | val email: String?, 8 | @field:NotBlank 9 | val code: String? 10 | ) 11 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/search/HeartCakeShopSearchParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.search 2 | 3 | import com.cakk.domain.mysql.entity.user.User 4 | 5 | data class HeartCakeShopSearchParam( 6 | val cakeShopHeartId: Long?, 7 | val pageSize: Int, 8 | val user: User 9 | ) 10 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/response/shop/CakeShopByMapResponse.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.response.shop 2 | 3 | import com.cakk.domain.mysql.dto.param.shop.CakeShopLocationResponseParam 4 | 5 | data class CakeShopByMapResponse( 6 | val cakeShops: List 7 | ) 8 | 9 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/jpa/CakeJpaRepository.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.repository.jpa 2 | 3 | import com.cakk.domain.mysql.entity.cake.Cake 4 | import org.springframework.data.jpa.repository.JpaRepository 5 | 6 | interface CakeJpaRepository : JpaRepository 7 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/like/HeartCakeShopSearchRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.like 2 | 3 | import com.cakk.common.utils.DEFAULT_PAGE_SIZE 4 | 5 | data class HeartCakeShopSearchRequest( 6 | val cakeShopHeartId: Long? = null, 7 | val pageSize: Int? = DEFAULT_PAGE_SIZE 8 | ) 9 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/cake/CakeSearchByCategoryParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.cake 2 | 3 | import com.cakk.common.enums.CakeDesignCategory 4 | 5 | data class CakeSearchByCategoryParam( 6 | val cakeId: Long?, 7 | val category: CakeDesignCategory, 8 | val pageSize: Int 9 | ) 10 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/cake/CakeSearchParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.cake 2 | 3 | import org.locationtech.jts.geom.Point 4 | 5 | data class CakeSearchParam( 6 | val cakeId: Long?, 7 | val keyword: String?, 8 | val location: Point?, 9 | val pageSize: Int 10 | ) 11 | -------------------------------------------------------------------------------- /cakk-external/src/main/kotlin/com/cakk/external/extractor/SlackMessageExtractor.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.external.extractor 2 | 3 | import net.gpedro.integrations.slack.SlackMessage 4 | 5 | fun interface SlackMessageExtractor : MessageExtractor { 6 | 7 | override fun extract(message: T): SlackMessage 8 | } 9 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/mapper/SearchMapper.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.mapper 2 | 3 | import com.cakk.core.dto.response.search.TopSearchedListResponse 4 | 5 | fun supplyTopSearchedListResponseBy(keywordList: List): TopSearchedListResponse { 6 | return TopSearchedListResponse(keywordList, keywordList.size) 7 | } 8 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/jpa/CakeShopJpaRepository.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.repository.jpa 2 | 3 | import com.cakk.domain.mysql.entity.shop.CakeShop 4 | import org.springframework.data.jpa.repository.JpaRepository 5 | 6 | interface CakeShopJpaRepository : JpaRepository 7 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/shop/CakeShopSearchParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.shop; 2 | 3 | import org.locationtech.jts.geom.Point; 4 | 5 | public record CakeShopSearchParam( 6 | Long cakeShopId, 7 | String keyword, 8 | Point location, 9 | Integer pageSize 10 | ) { 11 | } 12 | -------------------------------------------------------------------------------- /cakk-common/src/main/kotlin/com/cakk/common/enums/CakeDesignCategory.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.common.enums 2 | 3 | enum class CakeDesignCategory { 4 | 5 | THREE_DIMENSIONAL, 6 | CHARACTER, 7 | PHOTO, 8 | LUNCHBOX, 9 | FIGURE, 10 | FLOWER, 11 | LETTERING, 12 | RICE_CAKE, 13 | TIARA, 14 | ETC 15 | } 16 | -------------------------------------------------------------------------------- /cakk-common/src/main/kotlin/com/cakk/common/utils/RandomUtils.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.common.utils 2 | 3 | fun generateRandomStringOnlyNumber(length: Int): String { 4 | val sb = StringBuilder() 5 | 6 | for (i in 0 until length) { 7 | sb.append((Math.random() * 10).toInt()) 8 | } 9 | 10 | return sb.toString() 11 | } 12 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/shop/ShopOperationParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.shop 2 | 3 | import java.time.LocalTime 4 | import com.cakk.common.enums.Days 5 | 6 | data class ShopOperationParam( 7 | val operationDay: Days, 8 | val operationStartTime: LocalTime, 9 | val operationEndTime: LocalTime 10 | ) 11 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/cake/CakeImageWithShopInfoResponseParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.cake; 2 | 3 | public record CakeImageWithShopInfoResponseParam( 4 | Long cakeShopId, 5 | Long cakeId, 6 | String cakeImageUrl, 7 | String thumbnailUrl, 8 | String shopName 9 | ) { 10 | } 11 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/jpa/UserWithdrawalJpaRepository.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.repository.jpa 2 | 3 | import com.cakk.domain.mysql.entity.user.UserWithdrawal 4 | import org.springframework.data.jpa.repository.JpaRepository 5 | 6 | interface UserWithdrawalJpaRepository : JpaRepository 7 | -------------------------------------------------------------------------------- /cakk-domain/redis/build.gradle.kts: -------------------------------------------------------------------------------- 1 | description = "redis module" 2 | 3 | dependencies { 4 | implementation(projects.common) 5 | 6 | implementation(libs.spring.boot.starter.data.redis) 7 | implementation(libs.spring.boot.starter.redisson) 8 | } 9 | 10 | tasks.bootJar { 11 | enabled = false 12 | } 13 | 14 | tasks.jar { 15 | enabled = true 16 | } 17 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/user/OwnerCandidateParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.user 2 | 3 | import java.time.LocalDateTime 4 | 5 | data class OwnerCandidateParam( 6 | val userId: Long, 7 | val nickname: String, 8 | val profileImageUrl: String, 9 | val email: String, 10 | val timestamp: LocalDateTime 11 | ) 12 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/response/shop/CakeShopSearchResponse.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.response.shop 2 | 3 | import com.cakk.domain.mysql.dto.param.shop.CakeShopSearchResponseParam 4 | 5 | data class CakeShopSearchResponse( 6 | val cakeShops: List, 7 | val lastCakeShopId: Long?, 8 | val size: Int 9 | ) 10 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/shop/CakeShopInfoParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.shop; 2 | 3 | import java.util.List; 4 | 5 | import org.locationtech.jts.geom.Point; 6 | 7 | public record CakeShopInfoParam( 8 | String shopAddress, 9 | Point point, 10 | List shopOperationDays 11 | ) { 12 | } 13 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/response/like/HeartCakeImageListResponse.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.response.like 2 | 3 | import com.cakk.domain.mysql.dto.param.like.HeartCakeImageResponseParam 4 | 5 | data class HeartCakeImageListResponse( 6 | val cakeImages: List, 7 | val lastCakeHeartId: Long?, 8 | val size: Int 9 | ) 10 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/response/like/HeartCakeShopListResponse.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.response.like 2 | 3 | import com.cakk.domain.mysql.dto.param.like.HeartCakeShopResponseParam 4 | 5 | data class HeartCakeShopListResponse( 6 | val cakeShops: List, 7 | val lastCakeShopHeartId: Long?, 8 | val size: Int 9 | ) 10 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/shop/CakeShopOperationParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.shop; 2 | 3 | import java.time.LocalTime; 4 | 5 | import com.cakk.common.enums.Days; 6 | 7 | public record CakeShopOperationParam( 8 | Days operationDay, 9 | LocalTime operationStartTime, 10 | LocalTime operationEndTime 11 | ) { 12 | } 13 | -------------------------------------------------------------------------------- /cakk-admin/src/main/kotlin/com/cakk/admin/dto/request/ShopOperationUpdateByAdminRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.admin.dto.request 2 | 3 | import jakarta.validation.constraints.NotNull 4 | 5 | import com.cakk.domain.mysql.dto.param.shop.ShopOperationParam 6 | 7 | data class ShopOperationUpdateByAdminRequest( 8 | @field:NotNull 9 | val operationDays: List? 10 | ) 11 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/operation/UpdateShopOperationRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.operation 2 | 3 | import jakarta.validation.constraints.NotNull 4 | 5 | import com.cakk.core.dto.param.shop.ShopOperationParam 6 | 7 | data class UpdateShopOperationRequest( 8 | @field:NotNull 9 | val operationDays: MutableList? 10 | ) 11 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/shop/ShopOperationParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.shop; 2 | 3 | import java.time.LocalTime; 4 | 5 | import com.cakk.common.enums.Days; 6 | 7 | public record ShopOperationParam( 8 | Long cakeShopId, 9 | Days operationDay, 10 | LocalTime operationStartTime, 11 | LocalTime operationEndTime 12 | ) { 13 | } 14 | -------------------------------------------------------------------------------- /cakk-batch/src/main/java/com/cakk/batch/utils/BatchConstants.java: -------------------------------------------------------------------------------- 1 | package com.cakk.batch.utils; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | 6 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 7 | public class BatchConstants { 8 | 9 | public static final String CAKE_ID_LIST = "cakeIdList"; 10 | public static final String KEYWORD_LIST = "keywordList"; 11 | } 12 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/response/cake/CakeImageWithShopInfoListResponse.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.response.cake 2 | 3 | import com.cakk.domain.mysql.dto.param.cake.CakeImageWithShopInfoResponseParam 4 | 5 | data class CakeImageWithShopInfoListResponse( 6 | val cakeImages: List, 7 | val lastCakeId: Long?, 8 | val size: Int 9 | ) 10 | 11 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/response/shop/CakeShopOwnerCandidateResponse.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.response.shop 2 | 3 | data class CakeShopOwnerCandidateResponse( 4 | val userId: Long, 5 | val cakeShopId: Long, 6 | val email: String, 7 | val businessRegistrationImageUrl: String, 8 | val idCardImageUrl: String, 9 | val emergencyContact: String 10 | ) 11 | -------------------------------------------------------------------------------- /cakk-admin/src/main/kotlin/com/cakk/admin/config/ComponentScanConfig.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.admin.config 2 | 3 | import org.springframework.context.annotation.ComponentScan 4 | import org.springframework.context.annotation.Configuration 5 | 6 | @Configuration 7 | @ComponentScan(basePackages = [ 8 | "com.cakk.domain", 9 | "com.cakk.core", 10 | "com.cakk.admin" 11 | ]) 12 | class ComponentScanConfig 13 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/link/UpdateLinkRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.link 2 | 3 | import jakarta.validation.constraints.Size 4 | 5 | data class UpdateLinkRequest( 6 | @field:Size(min = 1, max = 200) 7 | val instagram: String?, 8 | @field:Size(min = 1, max = 200) 9 | val kakao: String?, 10 | @field:Size(min = 1, max = 200) 11 | val web: String? 12 | ) 13 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/response/shop/CakeShopInfoResponse.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.response.shop 2 | 3 | import com.cakk.domain.mysql.dto.param.shop.CakeShopOperationParam 4 | 5 | data class CakeShopInfoResponse( 6 | val shopAddress: String, 7 | val latitude: Double, 8 | val longitude: Double, 9 | val shopOperationDays: List 10 | ) 11 | -------------------------------------------------------------------------------- /cakk-api/src/test/resources/sql/delete-all.sql: -------------------------------------------------------------------------------- 1 | delete from cake_heart; 2 | delete from cake_tag; 3 | delete from tag; 4 | delete from cake_category; 5 | delete from cake; 6 | 7 | delete from cake_shop_heart; 8 | delete from cake_shop_like; 9 | delete from cake_shop_link; 10 | delete from cake_shop_operation; 11 | delete from business_information; 12 | delete from cake_shop; 13 | 14 | delete from users; 15 | -------------------------------------------------------------------------------- /cakk-admin/src/main/kotlin/com/cakk/admin/dto/request/LinkUpdateByAdminRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.admin.dto.request 2 | 3 | import jakarta.validation.constraints.Size 4 | 5 | data class LinkUpdateByAdminRequest( 6 | @field:Size(min = 1, max = 200) 7 | val instagram: String?, 8 | @field:Size(min = 1, max = 200) 9 | val kakao: String?, 10 | @field:Size(min = 1, max = 200) 11 | val web: String? 12 | ) 13 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/cake/CakeSearchByShopRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.cake 2 | 3 | import jakarta.validation.constraints.NotNull 4 | 5 | import com.cakk.common.utils.DEFAULT_PAGE_SIZE 6 | 7 | data class CakeSearchByShopRequest( 8 | val cakeId: Long? = null, 9 | @field:NotNull 10 | val cakeShopId: Long?, 11 | val pageSize: Int? = DEFAULT_PAGE_SIZE 12 | ) 13 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/user/UserSignInRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.user 2 | 3 | import jakarta.validation.constraints.NotBlank 4 | import jakarta.validation.constraints.NotNull 5 | 6 | import com.cakk.common.enums.Provider 7 | 8 | data class UserSignInRequest( 9 | @field:NotNull 10 | val provider: Provider?, 11 | @field:NotBlank 12 | val idToken: String? 13 | ) 14 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/jpa/UserJpaRepository.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.repository.jpa 2 | 3 | import com.cakk.domain.mysql.entity.user.User 4 | import org.springframework.data.jpa.repository.JpaRepository 5 | import java.util.* 6 | 7 | interface UserJpaRepository : JpaRepository { 8 | 9 | fun findByProviderId(providerId: String): User? 10 | } 11 | 12 | -------------------------------------------------------------------------------- /cakk-external/src/main/kotlin/com/cakk/external/vo/message/ErrorAlertMessage.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.external.vo.message 2 | 3 | data class ErrorAlertMessage( 4 | val serverProfile: String, 5 | val stackTrace: String?, 6 | val contextPath: String?, 7 | val requestURL: String?, 8 | val method: String?, 9 | val parameterMap: Map>?, 10 | val remoteAddr: String?, 11 | val header: String? 12 | ) 13 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/shop/CakeShopLocationResponseParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.shop; 2 | 3 | import java.util.Set; 4 | 5 | public record CakeShopLocationResponseParam( 6 | Long cakeShopId, 7 | String thumbnailUrl, 8 | String cakeShopName, 9 | String cakeShopBio, 10 | Set cakeImageUrls, 11 | Double longitude, 12 | Double latitude 13 | ) { 14 | } 15 | -------------------------------------------------------------------------------- /cakk-api/src/test/java/com/cakk/api/common/annotation/MockCustomUser.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.common.annotation 2 | 3 | import com.cakk.api.common.config.WithMockCustomUserSecurityContextFactory 4 | import org.springframework.security.test.context.support.WithSecurityContext 5 | 6 | @Retention(AnnotationRetention.RUNTIME) 7 | @WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory::class) 8 | annotation class MockCustomUser 9 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/shop/CakeShopSearchResponseParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.shop; 2 | 3 | import java.util.Set; 4 | 5 | public record CakeShopSearchResponseParam( 6 | Long cakeShopId, 7 | String thumbnailUrl, 8 | String cakeShopName, 9 | String cakeShopBio, 10 | Set cakeImageUrls, 11 | Set operationDays 12 | ) { 13 | 14 | } 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-template.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 | name: Feature request 12 | about: Suggest an idea for this project 13 | title: '' 14 | labels: '' 15 | assignees: '' 16 | 17 | --- 18 | 19 | ## Description 20 | 21 | > description 22 | 23 | ## To-do 24 | 25 | - [ ] todo 26 | 27 | ## ETC 28 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/config/ComponentScanConfig.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.config 2 | 3 | import org.springframework.context.annotation.ComponentScan 4 | import org.springframework.context.annotation.Configuration 5 | 6 | @Configuration 7 | @ComponentScan( 8 | basePackages = [ 9 | "com.cakk.domain", 10 | "com.cakk.external", 11 | "com.cakk.core", 12 | "com.cakk.api" 13 | ] 14 | ) 15 | class ComponentScanConfig 16 | -------------------------------------------------------------------------------- /cakk-common/src/main/kotlin/com/cakk/common/utils/SetUtils.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.common.utils 2 | 3 | fun keepOnlyNElements(set: MutableSet?, max: Int) { 4 | if (set == null || set.size <= max) { 5 | return 6 | } 7 | 8 | val iterator = set.iterator() 9 | var count = 0 10 | 11 | while (iterator.hasNext()) { 12 | iterator.next() 13 | count++ 14 | if (count > max) { 15 | iterator.remove() 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/facade/user/UserLikeFacade.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.facade.user 2 | 3 | import com.cakk.core.annotation.DomainFacade 4 | import com.cakk.domain.mysql.entity.shop.CakeShop 5 | import com.cakk.domain.mysql.entity.user.User 6 | 7 | @DomainFacade 8 | class UserLikeFacade { 9 | 10 | fun likeCakeShop(user: User, cakeShop: CakeShop) { 11 | user.likeCakeShop(cakeShop) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/jpa/CakeShopLinkJpaRepository.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.repository.jpa 2 | 3 | import com.cakk.domain.mysql.entity.shop.CakeShopLink 4 | import org.springframework.data.jpa.repository.JpaRepository 5 | 6 | interface CakeShopLinkJpaRepository : JpaRepository { 7 | 8 | fun findAllByCakeShopId(cakeShopId: Long): List 9 | } 10 | 11 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/cake/CakeUpdateParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.cake 2 | 3 | import com.cakk.domain.mysql.entity.cake.CakeCategory 4 | import com.cakk.domain.mysql.entity.user.User 5 | 6 | 7 | data class CakeUpdateParam( 8 | val owner: User, 9 | val cakeId: Long, 10 | val cakeImageUrl: String, 11 | val cakeCategories: List, 12 | val tagNames: List 13 | ) 14 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/shop/CakeShopUpdateParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.shop; 2 | 3 | import lombok.Builder; 4 | 5 | import com.cakk.domain.mysql.entity.user.User; 6 | 7 | @Builder 8 | public record CakeShopUpdateParam( 9 | String thumbnailUrl, 10 | String shopName, 11 | String shopBio, 12 | String shopDescription, 13 | User user, 14 | Long cakeShopId 15 | ) { 16 | } 17 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/shop/UpdateShopAddressParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.shop; 2 | 3 | import org.locationtech.jts.geom.Point; 4 | 5 | import lombok.Builder; 6 | 7 | import com.cakk.domain.mysql.entity.user.User; 8 | 9 | @Builder 10 | public record UpdateShopAddressParam( 11 | String shopAddress, 12 | Point location, 13 | User user, 14 | Long cakeShopId 15 | ) { 16 | } 17 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/jpa/CakeCategoryJpaRepository.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.repository.jpa 2 | 3 | import com.cakk.domain.mysql.entity.cake.CakeCategory 4 | import org.springframework.data.jpa.repository.JpaRepository 5 | import java.util.* 6 | 7 | interface CakeCategoryJpaRepository : JpaRepository { 8 | 9 | fun findByCakeId(cakeId: Long?): CakeCategory? 10 | } 11 | 12 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/shop/UpdateShopAddressRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.shop 2 | 3 | import jakarta.validation.constraints.* 4 | 5 | data class UpdateShopAddressRequest( 6 | @field:NotBlank @field:Size(max = 50) 7 | val shopAddress: String?, 8 | @field:NotNull @field:Min(-90) @field:Max(90) 9 | val latitude: Double?, 10 | @field:NotNull @field:Min(-180) @field:Max(180) 11 | val longitude: Double? 12 | ) 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/docs-edit-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Docs Edit template 3 | about: Describe docs edit 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | --- 11 | name: Docs edit request 12 | about: Describe this issue template's purpose here. 13 | title: '' 14 | labels: "✍️docs" 15 | assignees: '' 16 | 17 | --- 18 | 19 | ## Edit Contents 20 | 21 | - edit content1 22 | - edit content2 23 | 24 | ## Important 25 | 26 | - important 27 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/link/UpdateLinkParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.link; 2 | 3 | import java.util.List; 4 | 5 | import lombok.Builder; 6 | 7 | import com.cakk.domain.mysql.entity.shop.CakeShopLink; 8 | import com.cakk.domain.mysql.entity.user.User; 9 | 10 | @Builder 11 | public record UpdateLinkParam( 12 | User user, 13 | Long cakeShopId, 14 | List cakeShopLinks 15 | ) { 16 | } 17 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/shop/CakeShopDetailParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.shop; 2 | 3 | import java.util.Set; 4 | 5 | import com.cakk.common.enums.Days; 6 | 7 | public record CakeShopDetailParam( 8 | Long cakeShopId, 9 | String shopName, 10 | String thumbnailUrl, 11 | String shopBio, 12 | String shopDescription, 13 | Set operationDays, 14 | Set links 15 | ) { 16 | } 17 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/user/CertificationParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.user; 2 | 3 | import lombok.Builder; 4 | 5 | import com.cakk.domain.mysql.entity.user.User; 6 | 7 | @Builder 8 | public record CertificationParam( 9 | String businessRegistrationImageUrl, 10 | String idCardImageUrl, 11 | Long cakeShopId, 12 | String emergencyContact, 13 | String message, 14 | User user 15 | ) { 16 | } 17 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/jpa/CakeShopOperationJpaRepository.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.repository.jpa 2 | 3 | import com.cakk.domain.mysql.entity.shop.CakeShopOperation 4 | import org.springframework.data.jpa.repository.JpaRepository 5 | 6 | interface CakeShopOperationJpaRepository : JpaRepository { 7 | 8 | fun findAllByCakeShopId(cakeShopId: Long): List 9 | } 10 | 11 | -------------------------------------------------------------------------------- /cakk-api/src/main/resources/app-banner.dat: -------------------------------------------------------------------------------- 1 | ========================================== 2 | 3 | ,-----. ,---. ,--. ,--.,--. ,--. 4 | ' .--./ / O \ | .' /| .' / 5 | | | | .-. || . ' | . ' 6 | ' '--'\| | | || |\ \| |\ \ 7 | `-----'`--' `--'`--' '--'`--' '--' 8 | 9 | ${spring.application.title} ${spring.application.version} 10 | Powered by Spring Boot ${spring-boot.version} 11 | 12 | ========================================== 13 | 14 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/shop/CreateShopParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.shop 2 | 3 | 4 | 5 | data class CreateShopParam( 6 | val businessNumber: String?, 7 | val operationDays: List, 8 | val shopName: String, 9 | val shopBio: String?, 10 | val shopDescription: String?, 11 | val shopAddress: String, 12 | val latitude: Double, 13 | val longitude: Double, 14 | val links: List 15 | ) 16 | 17 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/cake/CakeSearchByCategoryRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.cake 2 | 3 | import jakarta.validation.constraints.NotNull 4 | 5 | import com.cakk.common.enums.CakeDesignCategory 6 | import com.cakk.common.utils.DEFAULT_PAGE_SIZE 7 | 8 | data class CakeSearchByCategoryRequest( 9 | val cakeId: Long?, 10 | @field:NotNull 11 | val category: CakeDesignCategory?, 12 | val pageSize: Int = DEFAULT_PAGE_SIZE 13 | ) 14 | -------------------------------------------------------------------------------- /cakk-batch/src/main/resources/batch-banner.dat: -------------------------------------------------------------------------------- 1 | ========================================== 2 | 3 | ,-----. ,---. ,--. ,--.,--. ,--. 4 | ' .--./ / O \ | .' /| .' / 5 | | | | .-. || . ' | . ' 6 | ' '--'\| | | || |\ \| |\ \ 7 | `-----'`--' `--'`--' '--'`--' '--' 8 | 9 | ${spring.application.title} ${spring.application.version} 10 | Powered by Spring Boot ${spring-boot.version} 11 | 12 | ========================================== 13 | 14 | -------------------------------------------------------------------------------- /cakk-common/src/main/kotlin/com/cakk/common/exception/CakkException.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.common.exception 2 | 3 | import com.cakk.common.enums.ReturnCode 4 | 5 | class CakkException( 6 | private val returnCode: ReturnCode 7 | ) : RuntimeException() { 8 | 9 | private val code 10 | get() = returnCode.code 11 | override val message: String 12 | get() = returnCode.message 13 | 14 | fun getReturnCode(): ReturnCode { 15 | return returnCode 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /cakk-external/src/main/kotlin/com/cakk/external/vo/message/CertificationMessage.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.external.vo.message 2 | 3 | data class CertificationMessage( 4 | val businessRegistrationImageUrl: String, 5 | val idCardImageUrl: String, 6 | val emergencyContact: String, 7 | val message: String, 8 | val userId: Long, 9 | val userEmail: String, 10 | val shopName: String, 11 | val latitude: Double, 12 | val longitude: Double 13 | ) 14 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/mapper/TagMapper.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.mapper; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | 6 | import com.cakk.domain.mysql.entity.cake.Tag; 7 | 8 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 9 | public class TagMapper { 10 | 11 | public static Tag supplyTagBy(String tagName) { 12 | return Tag.builder() 13 | .tagName(tagName) 14 | .build(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/fix-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Fix template 3 | about: Describe fix request 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | --- 10 | 11 | name: Fix request 12 | about: Create a report to help us improve 13 | title: '' 14 | labels: "" 15 | assignees: '' 16 | 17 | --- 18 | 19 | ## Description 20 | 21 | > description 22 | 23 | ## Code To Request 24 | 25 | **Prev** 26 | 27 | ```java 28 | 29 | ``` 30 | 31 | - [ ] to-do 32 | 33 | ## ETC 34 | -------------------------------------------------------------------------------- /appspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.0 2 | os: linux 3 | 4 | files: 5 | - source: / 6 | destination: /home/ubuntu/deploy 7 | overwrite: yes 8 | 9 | permissions: 10 | - object: / 11 | pattern: "**" 12 | owner: ubuntu 13 | group: ubuntu 14 | 15 | hooks: 16 | AfterInstall: 17 | - location: scripts/stop.sh 18 | timeout: 60 19 | runas: root 20 | 21 | ApplicationStart: 22 | - location: scripts/start.sh 23 | timeout: 60 24 | runas: root 25 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/like/HeartCakeShopResponseParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.like; 2 | 3 | import java.util.Set; 4 | 5 | import com.cakk.common.enums.Days; 6 | 7 | public record HeartCakeShopResponseParam( 8 | Long cakeShopHeartId, 9 | Long cakeShopId, 10 | String thumbnailUrl, 11 | String cakeShopName, 12 | String cakeShopBio, 13 | Set cakeImageUrls, 14 | Set operationDays 15 | ) { 16 | } 17 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/cake/CakeCreateParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.cake 2 | 3 | import com.cakk.domain.mysql.entity.cake.Cake 4 | import com.cakk.domain.mysql.entity.cake.CakeCategory 5 | import com.cakk.domain.mysql.entity.user.User 6 | 7 | 8 | data class CakeCreateParam( 9 | val cake: Cake, 10 | val cakeCategories: List, 11 | val tagNames: List, 12 | val owner: User, 13 | val cakeShopId: Long 14 | ) 15 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/response/user/ProfileInformationResponse.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.response.user 2 | 3 | import java.time.LocalDate 4 | 5 | import com.cakk.common.enums.Gender 6 | import com.cakk.common.enums.Role 7 | 8 | data class ProfileInformationResponse( 9 | val profileImageUrl: String?, 10 | val nickname: String, 11 | val email: String, 12 | val gender: Gender, 13 | val birthday: LocalDate, 14 | val role: Role 15 | ) 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/refactor-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Refactor request 3 | about: Describe Refactor request 4 | title: "" 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | --- 10 | 11 | name: Refactor request 12 | about: Describe Refactor request 13 | title: '' 14 | labels: '' 15 | assignees: '' 16 | 17 | --- 18 | 19 | ## Description 20 | 21 | > description 22 | 23 | ## Code To Request 24 | 25 | **Prev** 26 | 27 | ```java 28 | 29 | ``` 30 | 31 | - [ ] to-do 32 | 33 | ## ETC 34 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/response/cake/CakeDetailResponse.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.response.cake 2 | 3 | import com.cakk.common.enums.CakeDesignCategory 4 | import com.cakk.domain.mysql.dto.param.tag.TagParam 5 | 6 | data class CakeDetailResponse( 7 | val cakeImageUrl: String, 8 | val cakeShopName: String, 9 | val shopBio: String, 10 | val cakeShopId: Long, 11 | val cakeCategories: Set, 12 | val tags: Set 13 | ) 14 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/event/shop/CertificationEvent.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.event.shop; 2 | 3 | import org.locationtech.jts.geom.Point; 4 | 5 | import lombok.Builder; 6 | 7 | @Builder 8 | public record CertificationEvent( 9 | String businessRegistrationImageUrl, 10 | String idCardImageUrl, 11 | String emergencyContact, 12 | String message, 13 | Long userId, 14 | String userEmail, 15 | String shopName, 16 | Point location 17 | ) { 18 | } 19 | -------------------------------------------------------------------------------- /cakk-domain/redis/src/main/java/com/cakk/domain/redis/dto/param/ExecuteWithLockParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.redis.dto.param; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.util.function.Supplier; 5 | 6 | import lombok.Builder; 7 | 8 | import com.cakk.common.enums.RedisKey; 9 | 10 | @Builder 11 | public record ExecuteWithLockParam( 12 | String key, 13 | Supplier supplier, 14 | long waitTime, 15 | long leaseTime, 16 | TimeUnit timeUnit 17 | ) { 18 | } 19 | -------------------------------------------------------------------------------- /infra/redis/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | 3 | services: 4 | redis: 5 | image: redis:7.2-alpine 6 | container_name: cakk-redis 7 | ports: 8 | - "6379:6379" 9 | volumes: 10 | - /var/lib/docker/volumes/redis/_data:/data 11 | - /var/lib/docker/volumes/redis/redis.conf:/usr/local/etc/redis/redis.conf 12 | labels: 13 | - "name=redis" 14 | - "mode=standalone" 15 | restart: always 16 | command: redis-server /usr/local/etc/redis/redis.conf -------------------------------------------------------------------------------- /cakk-api/src/test/java/com/cakk/api/common/utils/TestUtils.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.common.utils 2 | 3 | const val GRANT_TYPE: String = "Bearer" 4 | const val USER_KEY: String = "USER" 5 | const val ACCESS_TOKEN_EXPIRED_SECOND: Long = 1814400000L 6 | const val REFRESH_TOKEN_EXPIRED_SECOND: Long = 2592000000L 7 | const val SECRET_KEY: String = ("testestestestestestestestestestestestestestestestestestestestestestestestestestestestestestestestes" 8 | + "testestestestestestestestestestestestestestestestest") 9 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/param/user/UserSignUpParam.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.param.user 2 | 3 | import com.cakk.common.enums.Gender 4 | import com.cakk.common.enums.Provider 5 | import java.time.LocalDate 6 | 7 | data class UserSignUpParam( 8 | val provider: Provider, 9 | val idToken: String, 10 | val deviceOs: String?, 11 | val deviceToken: String?, 12 | val nickname: String, 13 | val email: String, 14 | val birthday: LocalDate, 15 | val gender: Gender 16 | ) 17 | 18 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/config/P6spyConfig.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.config 2 | 3 | import com.p6spy.engine.spy.P6SpyOptions 4 | import jakarta.annotation.PostConstruct 5 | import org.springframework.context.annotation.Configuration 6 | 7 | @Configuration 8 | class P6spyConfig { 9 | @PostConstruct 10 | fun setLogMessageFormat() { 11 | P6SpyOptions.getActiveInstance().logMessageFormat = P6spySqlFormatterConfig::class.java.name 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/cake/CakeDetailParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.cake; 2 | 3 | import java.util.Set; 4 | 5 | import com.cakk.common.enums.CakeDesignCategory; 6 | import com.cakk.domain.mysql.dto.param.tag.TagParam; 7 | 8 | public record CakeDetailParam( 9 | String cakeImageUrl, 10 | String cakeShopName, 11 | String shopBio, 12 | Long cakeShopId, 13 | Set cakeCategories, 14 | Set tags 15 | ) { 16 | } 17 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/operation/UpdateShopOperationParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.operation; 2 | 3 | import java.util.List; 4 | 5 | import lombok.Builder; 6 | 7 | import com.cakk.domain.mysql.entity.shop.CakeShopOperation; 8 | import com.cakk.domain.mysql.entity.user.User; 9 | 10 | @Builder 11 | public record UpdateShopOperationParam( 12 | List cakeShopOperations, 13 | User user, 14 | Long cakeShopId 15 | ) { 16 | } 17 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/shop/UpdateShopRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.shop 2 | 3 | import jakarta.validation.constraints.NotBlank 4 | import jakarta.validation.constraints.Size 5 | 6 | data class UpdateShopRequest( 7 | @field:Size(max = 200) 8 | val thumbnailUrl: String?, 9 | @field:NotBlank @field:Size(max = 30) 10 | val shopName: String?, 11 | @field:Size(max = 40) 12 | val shopBio: String?, 13 | @field:Size(max = 500) 14 | val shopDescription: String? 15 | ) 16 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/annotation/DistributedLock.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.annotation 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class DistributedLock( 8 | 9 | val key: String = "", 10 | val timeUnit: TimeUnit = TimeUnit.MILLISECONDS, 11 | val waitTime: Long = 5000L, 12 | val leaseTime: Long = 3000L 13 | ) 14 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/dto/param/user/ProfileUpdateParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.dto.param.user; 2 | 3 | import java.time.LocalDate; 4 | 5 | import lombok.Builder; 6 | 7 | import com.cakk.common.enums.Gender; 8 | import com.cakk.domain.mysql.entity.user.User; 9 | 10 | @Builder 11 | public record ProfileUpdateParam( 12 | String profileImageUrl, 13 | String nickname, 14 | String email, 15 | Gender gender, 16 | LocalDate birthday, 17 | Long userId 18 | ) { 19 | } 20 | -------------------------------------------------------------------------------- /cakk-batch/src/main/resources/application-local.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | data: 3 | redis: 4 | host: 127.0.0.1 5 | port: 6379 6 | 7 | storage: 8 | datasource: 9 | core: 10 | jdbc-url: jdbc:mysql://localhost:3306/cakk 11 | username: ${DB_USERNAME} 12 | password: ${DB_PASSWORD} 13 | driver-class-name: com.mysql.cj.jdbc.Driver 14 | data-source-properties: 15 | rewriteBatchedStatements: true 16 | 17 | slack: 18 | webhook: 19 | is-enable: false 20 | url: url 21 | -------------------------------------------------------------------------------- /cakk-admin/src/main/kotlin/com/cakk/admin/dto/request/CakeShopUpdateByAdminRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.admin.dto.request 2 | 3 | import jakarta.validation.constraints.NotBlank 4 | import jakarta.validation.constraints.Size 5 | 6 | data class CakeShopUpdateByAdminRequest( 7 | @field:Size(max = 200) 8 | val thumbnailUrl: String?, 9 | @field:NotBlank @Size(max = 30) 10 | val shopName: String?, 11 | @field:Size(max = 40) 12 | val shopBio: String?, 13 | @field:Size(max = 500) 14 | val shopDescription: String? 15 | ) 16 | -------------------------------------------------------------------------------- /cakk-external/src/main/kotlin/com/cakk/external/template/MessageTemplate.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.external.template 2 | 3 | import com.cakk.external.extractor.MessageExtractor 4 | import com.cakk.external.sender.MessageSender 5 | 6 | class MessageTemplate { 7 | 8 | fun sendMessage( 9 | message: T, 10 | messageExtractor: MessageExtractor, 11 | messageSender: MessageSender, 12 | ) { 13 | val extractMessage: U = messageExtractor.extract(message) 14 | messageSender.send(extractMessage) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cakk-api/src/test/java/com/cakk/api/common/base/MockitoTest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.common.base 2 | 3 | import org.junit.jupiter.api.extension.ExtendWith 4 | import org.mockito.junit.jupiter.MockitoExtension 5 | import org.springframework.context.annotation.Import 6 | import org.springframework.test.context.ActiveProfiles 7 | 8 | import com.cakk.domain.mysql.config.JpaConfig 9 | 10 | 11 | @Import(JpaConfig::class) 12 | @ActiveProfiles("test") 13 | @ExtendWith(MockitoExtension::class) 14 | abstract class MockitoTest { 15 | } 16 | -------------------------------------------------------------------------------- /cakk-external/build.gradle.kts: -------------------------------------------------------------------------------- 1 | description = "external module" 2 | 3 | dependencies { 4 | implementation(projects.common) 5 | 6 | // Basic 7 | implementation(libs.spring.context) 8 | implementation(libs.spring.web) 9 | 10 | // AWS 11 | implementation(libs.aws.java.sdk) 12 | 13 | // Mail 14 | implementation(libs.spring.boot.starter.mail) 15 | 16 | // Slack 17 | implementation(libs.slack.webhook) 18 | } 19 | 20 | 21 | tasks.bootJar { 22 | enabled = false 23 | } 24 | 25 | tasks.jar { 26 | enabled = true 27 | } 28 | -------------------------------------------------------------------------------- /cakk-external/src/main/kotlin/com/cakk/external/vo/key/OidcPublicKeyList.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.external.vo.key 2 | 3 | import com.cakk.common.enums.ReturnCode 4 | import com.cakk.common.exception.CakkException 5 | 6 | data class OidcPublicKeyList( 7 | val keys: List 8 | ) { 9 | 10 | fun getMatchedKey(kid: String, alg: String): OidcPublicKey { 11 | return keys.find { key: OidcPublicKey -> 12 | key.kid == kid && key.alg == alg 13 | } ?: throw CakkException(ReturnCode.EXTERNAL_SERVER_ERROR) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/dto/response/shop/CakeShopDetailResponse.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.dto.response.shop 2 | 3 | import com.cakk.common.enums.Days 4 | import com.cakk.domain.mysql.dto.param.shop.CakeShopLinkParam 5 | 6 | data class CakeShopDetailResponse( 7 | val cakeShopId: Long, 8 | val cakeShopName: String, 9 | val thumbnailUrl: String, 10 | val cakeShopBio: String, 11 | val cakeShopDescription: String, 12 | val operationDays: Set, 13 | val links: Set 14 | ) 15 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/user/CertificationRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.user 2 | 3 | import jakarta.validation.constraints.NotBlank 4 | import jakarta.validation.constraints.NotNull 5 | 6 | data class CertificationRequest( 7 | @field:NotBlank 8 | val businessRegistrationImageUrl: String?, 9 | @field:NotBlank 10 | val idCardImageUrl: String?, 11 | @field:NotNull 12 | val cakeShopId: Long?, 13 | @field:NotBlank 14 | val emergencyContact: String?, 15 | val message: String? 16 | ) 17 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/jpa/TagJpaRepository.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.repository.jpa 2 | 3 | import com.cakk.domain.mysql.entity.cake.Tag 4 | import org.springframework.data.jpa.repository.JpaRepository 5 | import org.springframework.stereotype.Repository 6 | import java.util.* 7 | 8 | @Repository 9 | interface TagJpaRepository : JpaRepository { 10 | 11 | fun findTagByTagName(tagName: String?): Tag? 12 | fun findTagsByTagNameIsIn(tagNames: List): List 13 | } 14 | 15 | -------------------------------------------------------------------------------- /cakk-api/src/test/resources/sql/insert-heart.sql: -------------------------------------------------------------------------------- 1 | insert into cake_heart (cake_heart_id, cake_id, user_id, created_at) 2 | values (1, 3, 1, now()), 3 | (2, 8, 1, now()), 4 | (3, 5, 1, now()), 5 | (4, 2, 1, now()), 6 | (5, 9, 1, now()), 7 | (6, 4, 1, now()); 8 | 9 | insert into cake_shop_heart (shop_heart_id, shop_id, user_id, created_at) 10 | values (1, 4, 1, now()), 11 | (2, 3, 1, now()), 12 | (3, 9, 1, now()), 13 | (4, 5, 1, now()), 14 | (5, 7, 1, now()), 15 | (6, 1, 1, now()); 16 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/Application.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api 2 | 3 | import java.util.* 4 | 5 | import jakarta.annotation.PostConstruct 6 | 7 | import org.springframework.boot.autoconfigure.SpringBootApplication 8 | import org.springframework.boot.runApplication 9 | 10 | @SpringBootApplication 11 | class Application 12 | 13 | @PostConstruct 14 | fun started() { 15 | TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")) 16 | } 17 | 18 | fun main(args: Array) { 19 | started() 20 | runApplication(*args) 21 | } 22 | 23 | -------------------------------------------------------------------------------- /cakk-batch/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | title: Cakk-Batch 4 | version: 1.0.0 5 | banner: 6 | location: classpath:/batch-banner.dat 7 | jpa: 8 | open-in-view: false 9 | hibernate: 10 | ddl-auto: update 11 | properties: 12 | hibernate: 13 | dialect: org.hibernate.dialect.MySQLDialect 14 | format_sql: true 15 | show_sql: false 16 | batch: 17 | jdbc: 18 | initialize-schema: never 19 | job: 20 | enabled: false 21 | name: ${job.name:NONE} 22 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/shop/CakeShopSearchRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.shop 2 | 3 | import jakarta.validation.constraints.Max 4 | import jakarta.validation.constraints.Min 5 | 6 | import com.cakk.common.utils.DEFAULT_PAGE_SIZE 7 | 8 | data class CakeShopSearchRequest( 9 | val cakeShopId: Long?, 10 | val keyword: String?, 11 | @field:Min(-90) @field:Max(90) 12 | val latitude: Double?, 13 | @field:Min(-180) @field:Max(180) 14 | val longitude: Double?, 15 | val pageSize: Int? = DEFAULT_PAGE_SIZE 16 | ) 17 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/shop/SearchShopByLocationRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.shop 2 | 3 | import jakarta.validation.constraints.Max 4 | import jakarta.validation.constraints.Min 5 | import jakarta.validation.constraints.NotNull 6 | 7 | data class SearchShopByLocationRequest( 8 | @field:NotNull @field:Min(-90) @field:Max(90) 9 | val latitude: Double?, 10 | @field:NotNull @field:Min(-180) @field:Max(180) 11 | val longitude: Double?, 12 | @field:Min(0) @field:Max(10000) 13 | val distance: Double? = 1000.0 14 | ) 15 | 16 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/user/ProfileUpdateRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.user 2 | 3 | import java.time.LocalDate 4 | 5 | import jakarta.validation.constraints.NotEmpty 6 | import jakarta.validation.constraints.NotNull 7 | 8 | import com.cakk.common.enums.Gender 9 | 10 | data class ProfileUpdateRequest( 11 | val profileImageUrl: String?, 12 | @field:NotEmpty 13 | val nickname: String?, 14 | @field:NotEmpty 15 | val email: String?, 16 | @field:NotNull 17 | val gender: Gender?, 18 | val birthday: LocalDate? 19 | ) 20 | -------------------------------------------------------------------------------- /cakk-admin/src/main/kotlin/com/cakk/admin/dto/request/CakeUpdateByAdminRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.admin.dto.request 2 | 3 | import com.cakk.common.enums.CakeDesignCategory 4 | import jakarta.validation.constraints.NotBlank 5 | import jakarta.validation.constraints.NotNull 6 | import jakarta.validation.constraints.Size 7 | 8 | data class CakeUpdateByAdminRequest( 9 | @field:NotBlank @field:Size(max = 200) 10 | val cakeImageUrl: String?, 11 | @field:NotNull 12 | val cakeDesignCategories: List?, 13 | @field:NotNull 14 | val tagNames: List? 15 | ) 16 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/cake/CakeSearchByLocationRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.cake 2 | 3 | import jakarta.validation.constraints.Max 4 | import jakarta.validation.constraints.Min 5 | 6 | import com.cakk.common.utils.DEFAULT_PAGE_SIZE 7 | 8 | data class CakeSearchByLocationRequest( 9 | val cakeId: Long?, 10 | val keyword: String?, 11 | @field:Min(-90) @field:Max(90) 12 | val latitude: Double?, 13 | @field:Min(-180) @field:Max(180) 14 | val longitude: Double?, 15 | val pageSize: Int? = DEFAULT_PAGE_SIZE 16 | ) 17 | -------------------------------------------------------------------------------- /cakk-domain/redis/src/main/java/com/cakk/domain/redis/template/RedisZSetTemplate.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.redis.template; 2 | 3 | import java.util.List; 4 | 5 | public interface RedisZSetTemplate { 6 | 7 | void save(String key, T value); 8 | 9 | void increaseScore(String key, T value, int delta); 10 | 11 | List findAll(String key); 12 | 13 | List findAllReverseScore(String key, long count); 14 | 15 | List findAllReverseScore(String key, long offset, long count); 16 | 17 | void remove(String key, T value); 18 | 19 | void removeAll(String key); 20 | } 21 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/cake/CakeCreateRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.cake 2 | 3 | import jakarta.validation.constraints.NotBlank 4 | import jakarta.validation.constraints.NotEmpty 5 | import jakarta.validation.constraints.Size 6 | 7 | import com.cakk.common.enums.CakeDesignCategory 8 | 9 | data class CakeCreateRequest( 10 | @field:NotBlank @field:Size(max = 200) 11 | val cakeImageUrl: String?, 12 | @field:NotEmpty 13 | val cakeDesignCategories: List?, 14 | @field:NotEmpty 15 | val tagNames: List? 16 | ) 17 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/mapper/CakeHeartMapper.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.mapper; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | 6 | import com.cakk.domain.mysql.entity.cake.Cake; 7 | import com.cakk.domain.mysql.entity.cake.CakeHeart; 8 | import com.cakk.domain.mysql.entity.user.User; 9 | 10 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 11 | public class CakeHeartMapper { 12 | 13 | public static CakeHeart supplyCakeHeartBy(final Cake cake, User user) { 14 | return new CakeHeart(cake, user); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cakk-admin/src/main/kotlin/com/cakk/admin/AdminApplication.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.admin 2 | 3 | import java.util.TimeZone 4 | 5 | import jakarta.annotation.PostConstruct 6 | 7 | import org.springframework.boot.autoconfigure.SpringBootApplication 8 | import org.springframework.boot.runApplication 9 | 10 | 11 | @SpringBootApplication 12 | class AdminApplication 13 | 14 | @PostConstruct 15 | fun started() { 16 | TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")) 17 | } 18 | 19 | fun main(args: Array) { 20 | started() 21 | 22 | runApplication(*args) 23 | } 24 | -------------------------------------------------------------------------------- /cakk-admin/src/main/kotlin/com/cakk/admin/dto/request/CakeCreateByAdminRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.admin.dto.request 2 | 3 | import jakarta.validation.constraints.NotBlank 4 | import jakarta.validation.constraints.NotEmpty 5 | import jakarta.validation.constraints.Size 6 | 7 | import com.cakk.common.enums.CakeDesignCategory 8 | 9 | data class CakeCreateByAdminRequest( 10 | @field:NotBlank @field:Size(max = 100) 11 | val cakeImageUrl: String?, 12 | @field:NotEmpty 13 | val cakeDesignCategories: List?, 14 | @field:NotEmpty 15 | val tagNames: List? 16 | ) 17 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/provider/jwt/JwtProvider.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.provider.jwt 2 | 3 | import com.cakk.core.vo.JsonWebToken 4 | import com.cakk.domain.mysql.entity.user.User 5 | import io.jsonwebtoken.Claims 6 | import java.security.PublicKey 7 | 8 | interface JwtProvider { 9 | 10 | fun generateToken(user: User): JsonWebToken 11 | 12 | fun getUser(token: String): User 13 | 14 | fun getTokenExpiredSecond(token: String): Long 15 | 16 | fun parseClaims(token: String): Claims 17 | 18 | fun parseClaims(token: String, publicKey: PublicKey): Claims 19 | } 20 | -------------------------------------------------------------------------------- /cakk-domain/redis/src/main/java/com/cakk/domain/redis/template/RedisValueTemplate.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.redis.template; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import org.springframework.data.repository.NoRepositoryBean; 6 | 7 | @NoRepositoryBean 8 | public interface RedisValueTemplate { 9 | 10 | void save(String key, T value, long timeout, TimeUnit unit); 11 | 12 | Boolean saveIfAbsent(String key, T value, long timeout, TimeUnit unit); 13 | 14 | T findByKey(String key); 15 | 16 | Boolean existByKey(String key); 17 | 18 | Boolean delete(String key); 19 | } 20 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/cake/CakeUpdateRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.cake 2 | 3 | import jakarta.validation.constraints.NotBlank 4 | import jakarta.validation.constraints.NotEmpty 5 | import jakarta.validation.constraints.Size 6 | 7 | import com.cakk.common.enums.CakeDesignCategory 8 | 9 | 10 | data class CakeUpdateRequest( 11 | @field:NotBlank @field:Size(max = 200) 12 | val cakeImageUrl: String?, 13 | @field:NotEmpty 14 | val cakeDesignCategories: List?, 15 | @field:NotEmpty 16 | val tagNames: List? 17 | ) 18 | -------------------------------------------------------------------------------- /cakk-batch/src/main/java/com/cakk/batch/config/ComponentScanConfig.java: -------------------------------------------------------------------------------- 1 | package com.cakk.batch.config; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | import com.cakk.domain.mysql.config.DataSourceConfig; 7 | import com.cakk.domain.mysql.config.JpaConfig; 8 | 9 | @Configuration 10 | @ComponentScan(basePackages = { 11 | "com.cakk.domain.redis", 12 | "com.cakk.batch" 13 | }, basePackageClasses = { 14 | DataSourceConfig.class, 15 | JpaConfig.class 16 | }) 17 | public class ComponentScanConfig { 18 | } 19 | -------------------------------------------------------------------------------- /cakk-external/src/main/kotlin/com/cakk/external/config/SlackWebhookConfig.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.external.config 2 | 3 | import net.gpedro.integrations.slack.SlackApi 4 | 5 | import org.springframework.beans.factory.annotation.Value 6 | import org.springframework.context.annotation.Bean 7 | import org.springframework.context.annotation.Configuration 8 | 9 | @Configuration 10 | class SlackWebhookConfig( 11 | @Value("\${slack.webhook.url}") 12 | private val slackWebhookUrl: String 13 | ) { 14 | 15 | @Bean 16 | fun slackApi(): SlackApi { 17 | return SlackApi(slackWebhookUrl) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cakk-admin/src/main/kotlin/com/cakk/admin/annotation/OperationDay.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.admin.annotation 2 | 3 | import com.cakk.admin.validator.OperationValidator 4 | import jakarta.validation.Constraint 5 | import jakarta.validation.Payload 6 | import kotlin.reflect.KClass 7 | 8 | @Retention(AnnotationRetention.RUNTIME) 9 | @Target(AnnotationTarget.FIELD) 10 | @Constraint(validatedBy = [OperationValidator::class]) 11 | annotation class OperationDay( 12 | 13 | val message: String = "영업 일자 형식이 잘못됐습니다.", 14 | val groups: Array> = [], 15 | val payload: Array> = [] 16 | ) 17 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/mapper/CakeDesignCategoryMapper.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.mapper 2 | 3 | import com.cakk.common.enums.CakeDesignCategory 4 | import com.cakk.domain.mysql.entity.cake.CakeCategory 5 | 6 | fun supplyCakeCategoryListBy(cakeDesignCategories: List): List { 7 | return cakeDesignCategories.map { supplyCakeCategoryBy(it) }.toList() 8 | } 9 | 10 | private fun supplyCakeCategoryBy(cakeDesignCategory: CakeDesignCategory): CakeCategory { 11 | return CakeCategory.builder() 12 | .cakeDesignCategory(cakeDesignCategory) 13 | .build() 14 | } 15 | -------------------------------------------------------------------------------- /cakk-admin/src/main/kotlin/com/cakk/admin/mapper/CakeDesignCategoryMapper.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.admin.mapper 2 | 3 | import com.cakk.common.enums.CakeDesignCategory 4 | import com.cakk.domain.mysql.entity.cake.CakeCategory 5 | 6 | fun supplyCakeCategoryListBy(cakeDesignCategories: List): List { 7 | return cakeDesignCategories.map { supplyCakeCategoryBy(it) }.toList() 8 | } 9 | 10 | private fun supplyCakeCategoryBy(cakeDesignCategory: CakeDesignCategory?): CakeCategory { 11 | return CakeCategory.builder() 12 | .cakeDesignCategory(cakeDesignCategory) 13 | .build() 14 | } 15 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/mapper/CakeTagMapper.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.mapper; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | 6 | import com.cakk.domain.mysql.entity.cake.Cake; 7 | import com.cakk.domain.mysql.entity.cake.CakeTag; 8 | import com.cakk.domain.mysql.entity.cake.Tag; 9 | 10 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 11 | public class CakeTagMapper { 12 | 13 | public static CakeTag supplyCakeTagBy(Cake cake, Tag tag) { 14 | return CakeTag.builder() 15 | .cake(cake) 16 | .tag(tag) 17 | .build(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/annotation/OperationDay.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.annotation 2 | 3 | import jakarta.validation.Constraint 4 | import jakarta.validation.Payload 5 | 6 | import kotlin.reflect.KClass 7 | 8 | import com.cakk.api.validator.OperationValidator 9 | 10 | @Retention(AnnotationRetention.RUNTIME) 11 | @Target(AnnotationTarget.FIELD) 12 | @Constraint(validatedBy = [OperationValidator::class]) 13 | annotation class OperationDay( 14 | 15 | val message: String = "영업 일자 형식이 잘못됐습니다.", 16 | val groups: Array> = [], 17 | val payload: Array> = [] 18 | ) 19 | -------------------------------------------------------------------------------- /cakk-external/src/main/kotlin/com/cakk/external/sender/SlackMessageSender.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.external.sender 2 | 3 | import net.gpedro.integrations.slack.SlackApi 4 | import net.gpedro.integrations.slack.SlackMessage 5 | 6 | import org.springframework.beans.factory.annotation.Value 7 | 8 | class SlackMessageSender( 9 | private val slackApi: SlackApi, 10 | @Value("\${slack.webhook.is-enable}") 11 | private val isEnable: Boolean 12 | ): MessageSender { 13 | 14 | override fun send(message: SlackMessage) { 15 | if (!isEnable) { 16 | return 17 | } 18 | 19 | slackApi.call(message) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/config/JwtConfig.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.config 2 | 3 | import java.security.Key 4 | 5 | import io.jsonwebtoken.io.Decoders 6 | import io.jsonwebtoken.security.Keys 7 | import org.springframework.beans.factory.annotation.Value 8 | import org.springframework.context.annotation.Bean 9 | import org.springframework.context.annotation.Configuration 10 | 11 | @Configuration 12 | class JwtConfig( 13 | @Value("\${jwt.secret}") 14 | private val secretKey: String 15 | ) { 16 | 17 | @Bean 18 | fun key(): Key { 19 | return Keys.hmacShaKeyFor(Decoders.BASE64.decode(secretKey)) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /cakk-batch/src/main/java/com/cakk/batch/utils/BatchUtils.java: -------------------------------------------------------------------------------- 1 | package com.cakk.batch.utils; 2 | 3 | import org.springframework.batch.core.scope.context.ChunkContext; 4 | import org.springframework.batch.item.ExecutionContext; 5 | 6 | import lombok.AccessLevel; 7 | import lombok.NoArgsConstructor; 8 | 9 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 10 | public class BatchUtils { 11 | 12 | public static ExecutionContext getExecutionContext(final ChunkContext chunkContext) { 13 | return chunkContext.getStepContext() 14 | .getStepExecution() 15 | .getJobExecution() 16 | .getExecutionContext(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/mapper/CakeShopLikeMapper.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.mapper; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | 6 | import com.cakk.domain.mysql.entity.shop.CakeShop; 7 | import com.cakk.domain.mysql.entity.shop.CakeShopLike; 8 | import com.cakk.domain.mysql.entity.user.User; 9 | 10 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 11 | public class CakeShopLikeMapper { 12 | 13 | public static CakeShopLike supplyCakeShopLikeBy(final CakeShop cakeShop, final User user) { 14 | return new CakeShopLike(cakeShop, user); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/mapper/CakeShopHeartMapper.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.mapper; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | 6 | import com.cakk.domain.mysql.entity.shop.CakeShop; 7 | import com.cakk.domain.mysql.entity.shop.CakeShopHeart; 8 | import com.cakk.domain.mysql.entity.user.User; 9 | 10 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 11 | public class CakeShopHeartMapper { 12 | 13 | public static CakeShopHeart supplyCakeShopHeartBy(final CakeShop cakeShop, final User user) { 14 | return new CakeShopHeart(cakeShop, user); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /cakk-admin/build.gradle.kts: -------------------------------------------------------------------------------- 1 | description = "admin module" 2 | 3 | tasks.bootJar { 4 | enabled = true 5 | } 6 | 7 | tasks.jar { 8 | enabled = false 9 | } 10 | 11 | dependencies { 12 | implementation(projects.common) 13 | implementation(projects.persistenceMysql) 14 | implementation(projects.application) 15 | 16 | implementation(libs.spring.boot.starter.web) 17 | implementation(libs.spring.boot.starter.validation) 18 | implementation(libs.spring.boot.starter.security) 19 | implementation(libs.spring.boot.starter.oauth) 20 | 21 | implementation(libs.spring.tx) 22 | 23 | // Point 24 | implementation(libs.jts.core) 25 | } 26 | -------------------------------------------------------------------------------- /cakk-admin/src/main/kotlin/com/cakk/admin/dto/request/AddressUpdateByAdminRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.admin.dto.request 2 | 3 | import jakarta.validation.constraints.Max 4 | import jakarta.validation.constraints.Min 5 | import jakarta.validation.constraints.NotBlank 6 | import jakarta.validation.constraints.NotNull 7 | import jakarta.validation.constraints.Size 8 | 9 | data class AddressUpdateByAdminRequest( 10 | @field:NotBlank @field:Size(max = 50) 11 | val shopAddress: String?, 12 | @field:NotNull @field:Min(-90) @field:Max(90) 13 | val latitude: Double?, 14 | @field:NotNull @field:Min(-180) @field:Max(180) 15 | val longitude: Double? 16 | ) 17 | -------------------------------------------------------------------------------- /cakk-batch/src/main/java/com/cakk/batch/BatchApplication.java: -------------------------------------------------------------------------------- 1 | package com.cakk.batch; 2 | 3 | import java.util.TimeZone; 4 | 5 | import jakarta.annotation.PostConstruct; 6 | 7 | import org.springframework.boot.SpringApplication; 8 | import org.springframework.boot.autoconfigure.SpringBootApplication; 9 | 10 | @SpringBootApplication 11 | public class BatchApplication { 12 | 13 | @PostConstruct 14 | public static void started() { 15 | TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul")); 16 | } 17 | 18 | public static void main(String[] args) { 19 | started(); 20 | 21 | SpringApplication.run(BatchApplication.class, args); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /cakk-external/src/main/kotlin/com/cakk/external/sender/EmailMessageSender.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.external.sender 2 | 3 | import jakarta.mail.internet.MimeMessage 4 | 5 | import org.springframework.mail.javamail.JavaMailSender 6 | 7 | import com.cakk.common.enums.ReturnCode 8 | import com.cakk.common.exception.CakkException 9 | 10 | class EmailMessageSender( 11 | private val mailSender: JavaMailSender 12 | ) : MessageSender { 13 | 14 | override fun send(message: MimeMessage) { 15 | try { 16 | mailSender.send(message) 17 | } catch (e: RuntimeException) { 18 | throw CakkException(ReturnCode.SEND_EMAIL_ERROR) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /cakk-common/src/main/kotlin/com/cakk/common/enums/VerificationStatus.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.common.enums 2 | 3 | enum class VerificationStatus(@JvmField val code: Int) { 4 | UNREQUESTED(0), 5 | APPROVED(1), 6 | REJECTED(2), 7 | PENDING(3); 8 | 9 | val isCandidate: Boolean 10 | get() = code == 3 11 | 12 | val isNotCandidate: Boolean 13 | get() = code != 3 14 | 15 | val isApproved: Boolean 16 | get() = code == 1 17 | 18 | val isRejected: Boolean 19 | get() = code == 2 20 | 21 | fun makeApproved(): VerificationStatus { 22 | return APPROVED 23 | } 24 | 25 | fun makePending(): VerificationStatus { 26 | return PENDING 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/jpa/CakeHeartJpaRepository.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.repository.jpa 2 | 3 | import com.cakk.domain.mysql.entity.cake.Cake 4 | import com.cakk.domain.mysql.entity.cake.CakeHeart 5 | import com.cakk.domain.mysql.entity.user.User 6 | import org.springframework.data.jpa.repository.JpaRepository 7 | import java.util.* 8 | 9 | interface CakeHeartJpaRepository : JpaRepository { 10 | 11 | fun findAllByUser(user: User): List 12 | fun findByUserAndCake(user: User, cake: Cake): CakeHeart? 13 | fun existsByUserAndCake(user: User, cake: Cake): Boolean 14 | } 15 | 16 | -------------------------------------------------------------------------------- /cakk-api/src/test/java/com/cakk/api/common/fixture/MockGoogleIdToken.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.common.fixture 2 | 3 | import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken 4 | import com.google.api.client.json.webtoken.JsonWebSignature 5 | 6 | fun createMockGoogleIdToken(providerId: String): GoogleIdToken { 7 | val header = JsonWebSignature.Header() 8 | val payload = createMockPayload(providerId) 9 | 10 | return GoogleIdToken(header, payload, ByteArray(0), ByteArray(0)) 11 | } 12 | 13 | private fun createMockPayload(providerId: String?): GoogleIdToken.Payload { 14 | val payload = GoogleIdToken.Payload() 15 | payload.setSubject(providerId) 16 | 17 | return payload 18 | } 19 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/bo/shop/CakeShopBySearchParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.bo.shop; 2 | 3 | import java.util.Set; 4 | 5 | import lombok.AccessLevel; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | import lombok.experimental.SuperBuilder; 10 | 11 | import com.cakk.domain.mysql.dto.param.shop.CakeShopOperationParam; 12 | 13 | @Getter 14 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 15 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 16 | @SuperBuilder 17 | public class CakeShopBySearchParam extends CakeShopParam { 18 | 19 | private Set operationDays; 20 | } 21 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/config/JpaConfig.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.config 2 | 3 | import org.springframework.boot.autoconfigure.domain.EntityScan 4 | import org.springframework.context.annotation.Configuration 5 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing 6 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories 7 | import org.springframework.transaction.annotation.EnableTransactionManagement 8 | 9 | @Configuration 10 | @EnableJpaAuditing 11 | @EnableTransactionManagement 12 | @EntityScan("com.cakk.domain.mysql.entity.**") 13 | @EnableJpaRepositories("com.cakk.domain.mysql.repository.**") 14 | class JpaConfig 15 | -------------------------------------------------------------------------------- /cakk-api/src/test/java/com/cakk/api/common/container/MysqlTestContainer.java: -------------------------------------------------------------------------------- 1 | package com.cakk.api.common.container; 2 | 3 | import org.testcontainers.containers.MySQLContainer; 4 | import org.testcontainers.junit.jupiter.Container; 5 | import org.testcontainers.junit.jupiter.Testcontainers; 6 | 7 | @Testcontainers 8 | public abstract class MysqlTestContainer { 9 | 10 | private static final String MYSQL_VERSION = "mysql:8"; 11 | private static final String DATABASE_NAME = "cakk"; 12 | 13 | @Container 14 | public static final MySQLContainer MYSQL_CONTAINER = new MySQLContainer(MYSQL_VERSION) 15 | .withDatabaseName(DATABASE_NAME); 16 | 17 | static { 18 | MYSQL_CONTAINER.start(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/mapper/PointMapper.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.mapper 2 | 3 | import org.locationtech.jts.geom.Coordinate 4 | import org.locationtech.jts.geom.GeometryFactory 5 | import org.locationtech.jts.geom.Point 6 | import org.locationtech.jts.geom.PrecisionModel 7 | 8 | private const val SPATIAL_REFERENCE_IDENTIFIER_NUMBER = 4326 9 | 10 | private val geometryFactory = GeometryFactory(PrecisionModel(), SPATIAL_REFERENCE_IDENTIFIER_NUMBER) 11 | 12 | fun supplyPointBy(latitude: Double?, longitude: Double?): Point? { 13 | if (latitude == null || longitude == null) { 14 | return null 15 | } 16 | 17 | return geometryFactory.createPoint(Coordinate(longitude, latitude)) 18 | } 19 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/mapper/ShopOperationMapper.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.mapper 2 | 3 | import com.cakk.core.dto.param.shop.ShopOperationParam 4 | import com.cakk.domain.mysql.entity.shop.CakeShopOperation 5 | 6 | fun supplyCakeShopOperationListBy(operationDays: List): List { 7 | return operationDays.map { supplyCakeShopOperationBy(it) }.toList() 8 | } 9 | 10 | private fun supplyCakeShopOperationBy(param: ShopOperationParam): CakeShopOperation { 11 | return CakeShopOperation.builder() 12 | .operationDay(param.operationDay) 13 | .operationStartTime(param.operationStartTime) 14 | .operationEndTime(param.operationEndTime) 15 | .build() 16 | } 17 | -------------------------------------------------------------------------------- /cakk-batch/src/main/java/com/cakk/batch/config/SlackWebhookConfig.java: -------------------------------------------------------------------------------- 1 | package com.cakk.batch.config; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | import net.gpedro.integrations.slack.SlackApi; 8 | 9 | @Configuration 10 | public class SlackWebhookConfig { 11 | 12 | private final String slackWebhookUrl; 13 | 14 | public SlackWebhookConfig(@Value("${slack.webhook.url}") String slackWebhookUrl) { 15 | this.slackWebhookUrl = slackWebhookUrl; 16 | } 17 | 18 | @Bean 19 | public SlackApi slackApi() { 20 | return new SlackApi(slackWebhookUrl); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/config/QuerydslConfig.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.config 2 | 3 | import com.querydsl.jpa.JPQLTemplates 4 | import com.querydsl.jpa.impl.JPAQueryFactory 5 | import jakarta.persistence.EntityManager 6 | import jakarta.persistence.PersistenceContext 7 | import org.springframework.context.annotation.Bean 8 | import org.springframework.context.annotation.Configuration 9 | 10 | @Configuration 11 | class QuerydslConfig { 12 | 13 | @PersistenceContext 14 | private val entityManager: EntityManager? = null 15 | @Bean 16 | fun queryFactory(): JPAQueryFactory { 17 | return JPAQueryFactory(JPQLTemplates.DEFAULT, entityManager) 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/entity/audit/AuditCreatedEntity.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.entity.audit; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | import jakarta.persistence.Column; 6 | import jakarta.persistence.EntityListeners; 7 | import jakarta.persistence.MappedSuperclass; 8 | 9 | import org.springframework.data.annotation.CreatedDate; 10 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 11 | 12 | import lombok.Getter; 13 | 14 | @Getter 15 | @MappedSuperclass 16 | @EntityListeners(AuditingEntityListener.class) 17 | public class AuditCreatedEntity { 18 | 19 | @CreatedDate 20 | @Column(updatable = false) 21 | private LocalDateTime createdAt; 22 | } 23 | -------------------------------------------------------------------------------- /cakk-admin/src/main/kotlin/com/cakk/admin/mapper/ShopOperationMapper.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.admin.mapper 2 | 3 | import com.cakk.domain.mysql.dto.param.shop.ShopOperationParam 4 | import com.cakk.domain.mysql.entity.shop.CakeShopOperation 5 | 6 | fun supplyCakeShopOperationListBy(operationDays: List): List { 7 | return operationDays 8 | .map { supplyCakeShopOperationBy(it) } 9 | .toList() 10 | } 11 | 12 | fun supplyCakeShopOperationBy(param: ShopOperationParam): CakeShopOperation { 13 | return CakeShopOperation.builder() 14 | .operationDay(param.operationDay) 15 | .operationStartTime(param.operationStartTime) 16 | .operationEndTime(param.operationEndTime) 17 | .build() 18 | } 19 | -------------------------------------------------------------------------------- /cakk-api/src/test/resources/sql/insert-like-for-concurrency-test.sql: -------------------------------------------------------------------------------- 1 | -- users 2 | INSERT INTO users (user_id, provider, provider_id, nickname, profile_image_url, email, gender, birthday, role, created_at, updated_at, deleted_at) 3 | VALUES 4 | (1, 'GOOGLE', '1', '테스트 유저1', 'image_url1', 'test1@google.com', 'MALE', '1998-01-01', 'USER', NOW(), NOW(), NULL); 5 | 6 | 7 | -- cake_shop 8 | SET @g1 = 'Point(37.197734 127.098190)'; 9 | 10 | insert into cake_shop (shop_id, thumbnail_url, shop_name, shop_bio, shop_description, location, like_count, heart_count, 11 | created_at, updated_at) 12 | values (1, 'thumbnail_url', '케이크 맛집', '케이크 맛집입니다.', '케이크 맛집입니다.', ST_GeomFromText(@g1, 4326), 0, 0, now(), now()); 13 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/bo/user/VerificationPolicy.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.bo.user; 2 | 3 | import com.cakk.common.enums.VerificationStatus; 4 | import com.cakk.domain.mysql.dto.param.user.CertificationParam; 5 | import com.cakk.domain.mysql.entity.user.BusinessInformation; 6 | import com.cakk.domain.mysql.event.shop.CertificationEvent; 7 | 8 | public interface VerificationPolicy { 9 | 10 | boolean isCandidate(VerificationStatus verificationStatus); 11 | 12 | VerificationStatus approveToBusinessOwner(VerificationStatus verificationStatus); 13 | 14 | CertificationEvent requestCertificationBusinessOwner(BusinessInformation businessInformation, CertificationParam param); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /.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 | ### MAC OS ### 40 | .DS_Store 41 | 42 | ### generated ### 43 | src/main/generated/ 44 | **/.jqwik-database 45 | .kotlin/** 46 | -------------------------------------------------------------------------------- /cakk-admin/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | servlet: 3 | context-path: /api/v1/admin 4 | 5 | spring: 6 | application: 7 | title: Cakk Admin 8 | version: 1.0.0 9 | 10 | jpa: 11 | open-in-view: false 12 | hibernate: 13 | ddl-auto: update 14 | properties: 15 | hibernate: 16 | dialect: org.hibernate.dialect.MySQLDialect 17 | format_sql: true 18 | show_sql: false 19 | default_batch_fetch_size: 1000 20 | jackson: 21 | date-format: yyyy-MM-dd HH:mm:ss 22 | 23 | decorator: 24 | datasource: 25 | p6spy: 26 | enable-logging: true 27 | 28 | jwt: 29 | access-header: Authorization 30 | refresh-header: Refresh 31 | grant-type: Bearer 32 | user-key: USER 33 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/controller/s3/AwsS3Controller.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.controller.s3 2 | 3 | import org.springframework.web.bind.annotation.GetMapping 4 | import org.springframework.web.bind.annotation.RequestMapping 5 | import org.springframework.web.bind.annotation.RestController 6 | 7 | import com.cakk.common.response.ApiResponse 8 | import com.cakk.external.service.S3Service 9 | import com.cakk.external.vo.s3.PresignedUrl 10 | 11 | @RestController 12 | @RequestMapping("/aws") 13 | class AwsS3Controller( 14 | private val s3Service: S3Service 15 | ) { 16 | 17 | @GetMapping("/img") 18 | fun getImageUrl(): ApiResponse { 19 | return ApiResponse.success(s3Service.getPresignedUrlWithImagePath()) 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /cakk-external/src/main/kotlin/com/cakk/external/config/RestClientConfig.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.external.config 2 | 3 | import java.time.Duration 4 | 5 | import org.springframework.boot.web.client.RestTemplateBuilder 6 | import org.springframework.context.annotation.Bean 7 | import org.springframework.context.annotation.Configuration 8 | import org.springframework.web.client.RestClient 9 | 10 | @Configuration 11 | class RestClientConfig { 12 | 13 | @Bean 14 | fun restClient(): RestClient { 15 | val restTemplate = RestTemplateBuilder() 16 | .setConnectTimeout(Duration.ofSeconds(10)) 17 | .setReadTimeout(Duration.ofSeconds(5)) 18 | .build() 19 | 20 | return RestClient.create(restTemplate) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/user/UserSignUpRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.user 2 | 3 | import java.time.LocalDate 4 | 5 | import jakarta.validation.constraints.NotBlank 6 | import jakarta.validation.constraints.NotNull 7 | 8 | import com.cakk.common.enums.Gender 9 | import com.cakk.common.enums.Provider 10 | 11 | data class UserSignUpRequest( 12 | @field:NotNull 13 | val provider: Provider?, 14 | @field:NotBlank 15 | val idToken: String?, 16 | val deviceOs: String?, 17 | val deviceToken: String?, 18 | @field:NotBlank 19 | val nickname: String?, 20 | @field:NotBlank 21 | val email: String?, 22 | @field:NotNull 23 | val birthday: LocalDate?, 24 | @field:NotNull 25 | val gender: Gender? 26 | ) 27 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/facade/tag/TagReadFacade.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.facade.tag 2 | 3 | import com.cakk.core.annotation.DomainFacade 4 | import com.cakk.domain.mysql.entity.cake.Tag 5 | import com.cakk.domain.mysql.mapper.TagMapper 6 | import com.cakk.domain.mysql.repository.jpa.TagJpaRepository 7 | 8 | @DomainFacade 9 | class TagReadFacade( 10 | private val tagJpaRepository: TagJpaRepository 11 | ) { 12 | 13 | fun getTagsByTagName(tagNames: List): List { 14 | val tags: List = tagJpaRepository.findTagsByTagNameIsIn(tagNames) 15 | return tagNames.map { 16 | tags.find { 17 | tag: Tag -> tag.tagName == it 18 | } ?: tagJpaRepository.save(TagMapper.supplyTagBy(it)) 19 | }.toList() 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/service/like/LikeService.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.service.like 2 | 3 | import org.springframework.stereotype.Service 4 | 5 | import com.cakk.core.annotation.DistributedLock 6 | import com.cakk.core.facade.cake.CakeShopReadFacade 7 | import com.cakk.core.facade.user.UserLikeFacade 8 | import com.cakk.domain.mysql.entity.user.User 9 | 10 | @Service 11 | class LikeService( 12 | private val cakeShopReadFacade: CakeShopReadFacade, 13 | private val userLikeFacade: UserLikeFacade 14 | ) { 15 | 16 | @DistributedLock(key = "#cakeShopId") 17 | fun likeCakeShop(user: User, cakeShopId: Long) { 18 | val cakeShop = cakeShopReadFacade.findByIdWithLike(cakeShopId) 19 | 20 | userLikeFacade.likeCakeShop(user, cakeShop) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/aspect/AopForTransaction.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.aspect 2 | 3 | import org.aspectj.lang.ProceedingJoinPoint 4 | import org.springframework.stereotype.Component 5 | import org.springframework.transaction.annotation.Propagation 6 | import org.springframework.transaction.annotation.Transactional 7 | 8 | import com.cakk.common.exception.CakkException 9 | 10 | @Component 11 | class AopForTransaction { 12 | 13 | @Transactional(propagation = Propagation.REQUIRES_NEW) 14 | fun proceed(joinPoint: ProceedingJoinPoint): Any? { 15 | return try { 16 | joinPoint.proceed() 17 | } catch (e: Throwable) { 18 | when (e) { 19 | is CakkException -> throw e 20 | else -> throw RuntimeException(e) 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /cakk-api/src/test/java/com/cakk/api/ApplicationTest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api 2 | 3 | import io.kotest.matchers.shouldNotBe 4 | 5 | import org.springframework.beans.factory.annotation.Autowired 6 | import org.springframework.boot.test.context.SpringBootTest 7 | import org.springframework.context.ApplicationContext 8 | import org.springframework.test.context.ActiveProfiles 9 | 10 | import com.cakk.api.common.annotation.TestWithDisplayName 11 | 12 | @ActiveProfiles("test") 13 | @SpringBootTest(properties = ["spring.profiles.active=test"]) 14 | internal class ApplicationTest { 15 | 16 | @Autowired 17 | private lateinit var context: ApplicationContext 18 | 19 | @TestWithDisplayName("Application이 잘 실행된다.") 20 | fun contextLoads() { 21 | context shouldNotBe null 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /scripts/stop.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PREFIX=/home/ubuntu 4 | PROJECT_NAME=prod-app-api 5 | REPOSITORY="$PREFIX/deploy" 6 | BUILD_DIR=cakk-api/build/libs 7 | DEPLOY_LOG_PATH="$PREFIX/$PROJECT_NAME/deploy.log" 8 | BUILD_JAR=$(ls -tr $REPOSITORY/$BUILD_DIR/*.jar | tail -n 1) 9 | JAR_NAME=$(basename "$BUILD_JAR") 10 | 11 | TIME_NOW=$(date +%c) 12 | 13 | chmod +x "$DEPLOY_LOG_PATH" 14 | echo "$TIME_NOW> 현재 실행 중인 서버 pid 확인" >> DEPLOY_LOG_PATH 15 | CURRENT_PID=$(pgrep -f "$JAR_NAME") 16 | 17 | if [ -z "$CURRENT_PID" ] 18 | then 19 | echo "$TIME_NOW > 현재 구동중인 서버가 없습니다." >> DEPLOY_LOG_PATH 20 | else 21 | echo "$TIME_NOW > 현재 구동중인 서버를 종료시키겠습니다." >> DEPLOY_LOG_PATH 22 | echo "$TIME_NOW > kill -9 $CURRENT_PID" >> DEPLOY_LOG_PATH 23 | kill -9 "$CURRENT_PID" 24 | sleep 5 25 | fi 26 | -------------------------------------------------------------------------------- /cakk-api/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | servlet: 3 | context-path: /api/v1 4 | 5 | spring: 6 | application: 7 | title: Cakk 8 | version: 1.0.0 9 | banner: 10 | location: classpath:/app-banner.dat 11 | jpa: 12 | open-in-view: false 13 | hibernate: 14 | ddl-auto: update 15 | properties: 16 | hibernate: 17 | dialect: org.hibernate.dialect.MySQLDialect 18 | format_sql: true 19 | show_sql: false 20 | default_batch_fetch_size: 1000 21 | jackson: 22 | date-format: yyyy-MM-dd HH:mm:ss 23 | 24 | decorator: 25 | datasource: 26 | p6spy: 27 | enable-logging: true 28 | 29 | jwt: 30 | access-header: Authorization 31 | refresh-header: Refresh 32 | grant-type: Bearer 33 | user-key: USER 34 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/provider/oauth/OidcProvider.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.provider.oauth 2 | 3 | import com.cakk.common.enums.ReturnCode 4 | import com.cakk.common.exception.CakkException 5 | import com.cakk.common.utils.decodeBase64 6 | import com.fasterxml.jackson.databind.ObjectMapper 7 | import java.io.IOException 8 | 9 | interface OidcProvider { 10 | fun getProviderId(idToken: String): String 11 | 12 | @Suppress("UNCHECKED_CAST") 13 | fun parseHeaders(token: String): Map { 14 | val header = token.split(".")[0] 15 | 16 | return try { 17 | ObjectMapper().readValue(decodeBase64(header), MutableMap::class.java) as Map 18 | } catch (e: IOException) { 19 | throw CakkException(ReturnCode.INTERNAL_SERVER_ERROR) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/service/search/KeywordService.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.service.search 2 | 3 | import org.springframework.stereotype.Service 4 | 5 | import com.cakk.core.dto.param.search.TopSearchedListParam 6 | import com.cakk.core.dto.response.search.TopSearchedListResponse 7 | import com.cakk.core.mapper.supplyTopSearchedListResponseBy 8 | import com.cakk.domain.redis.repository.KeywordRedisRepository 9 | 10 | @Service 11 | class KeywordService( 12 | private val keywordRedisRepository: KeywordRedisRepository 13 | ) { 14 | 15 | fun findTopSearched(param: TopSearchedListParam): TopSearchedListResponse { 16 | val keywordList = keywordRedisRepository.findTopSearchedLimitCount(param.count) 17 | 18 | return supplyTopSearchedListResponseBy(keywordList) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/config/WebMvcConfig.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.config 2 | 3 | import org.springframework.context.annotation.Configuration 4 | import org.springframework.web.method.support.HandlerMethodArgumentResolver 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer 6 | 7 | import com.cakk.api.resolver.AccessTokenResolver 8 | import com.cakk.api.resolver.AuthorizedUserResolver 9 | import com.cakk.api.resolver.RefreshTokenResolver 10 | 11 | @Configuration 12 | class WebMvcConfig : WebMvcConfigurer { 13 | 14 | override fun addArgumentResolvers(resolvers: MutableList) { 15 | resolvers.add(AuthorizedUserResolver()) 16 | resolvers.add(AccessTokenResolver()) 17 | resolvers.add(RefreshTokenResolver()) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/config/GoogleConfig.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.config 2 | 3 | import org.springframework.beans.factory.annotation.Value 4 | import org.springframework.context.annotation.Bean 5 | import org.springframework.context.annotation.Configuration 6 | 7 | import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier 8 | import com.google.api.client.http.javanet.NetHttpTransport 9 | import com.google.api.client.json.gson.GsonFactory 10 | 11 | @Configuration 12 | class GoogleConfig( 13 | @Value("\${oauth.google.client-id}") 14 | private val googleClientId: String 15 | ) { 16 | 17 | @Bean 18 | fun googleIdTokenVerifier(): GoogleIdTokenVerifier { 19 | return GoogleIdTokenVerifier.Builder(NetHttpTransport(), GsonFactory()) 20 | .setAudience(listOf(googleClientId)) 21 | .build() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/facade/user/UserHeartFacade.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.facade.user 2 | 3 | import com.cakk.core.annotation.DomainFacade 4 | import com.cakk.domain.mysql.entity.cake.Cake 5 | import com.cakk.domain.mysql.entity.shop.CakeShop 6 | import com.cakk.domain.mysql.entity.user.User 7 | 8 | @DomainFacade 9 | class UserHeartFacade { 10 | 11 | fun heartCake(user: User, cake: Cake) { 12 | if (!cake.isHeartedBy(user)) { 13 | user.heartCake(cake) 14 | } else { 15 | user.unHeartCake(cake) 16 | } 17 | } 18 | 19 | fun heartCakeShop(user: User, cakeShop: CakeShop) { 20 | if (!cakeShop.isHeartedBy(user)) { 21 | user.heartCakeShop(cakeShop) 22 | } else { 23 | user.unHeartCakeShop(cakeShop) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/utils/CustomSpringExpressionLanguageParser.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.utils 2 | 3 | import org.springframework.expression.ExpressionParser 4 | import org.springframework.expression.spel.standard.SpelExpressionParser 5 | import org.springframework.expression.spel.support.StandardEvaluationContext 6 | 7 | object CustomSpringExpressionLanguageParser { 8 | 9 | fun getDynamicValue(spEL: String, parameterNames: Array, args: Array): Any? { 10 | val parser: ExpressionParser = SpelExpressionParser() 11 | val context = StandardEvaluationContext() 12 | 13 | for (i in parameterNames.indices) { 14 | context.setVariable(parameterNames[i], args[i]) 15 | } 16 | 17 | return parser.parseExpression(spEL).getValue(context, Any::class.java) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/annotation/DomainFacade.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.annotation 2 | 3 | import org.springframework.core.annotation.AliasFor 4 | import org.springframework.stereotype.Component 5 | 6 | /** 7 | * Indicates that an annotated class is a "Service" (e.g. a domain service object). 8 | * 9 | * 10 | * This annotation serves as a specialization of [@Component][Component], 11 | * allowing for implementation classes to be autodetected through classpath scanning. 12 | * 13 | * @author komment 14 | * @see Component 15 | */ 16 | @Target(AnnotationTarget.CLASS) 17 | @Retention(AnnotationRetention.RUNTIME) 18 | @MustBeDocumented 19 | @Component 20 | annotation class DomainFacade( 21 | 22 | /** 23 | * Alias for [Component.value]. 24 | */ 25 | @get:AliasFor(annotation = Component::class) val value: String = "" 26 | ) 27 | -------------------------------------------------------------------------------- /cakk-batch/src/main/java/com/cakk/batch/controller/WeeklyJobController.java: -------------------------------------------------------------------------------- 1 | package com.cakk.batch.controller; 2 | 3 | import org.springframework.web.bind.annotation.GetMapping; 4 | import org.springframework.web.bind.annotation.RequestMapping; 5 | import org.springframework.web.bind.annotation.RestController; 6 | 7 | import lombok.RequiredArgsConstructor; 8 | 9 | import com.cakk.batch.job.launcher.WeeklyJobLauncher; 10 | import com.cakk.common.response.ApiResponse; 11 | 12 | @RequiredArgsConstructor 13 | @RestController 14 | @RequestMapping("/api/v1") 15 | public class WeeklyJobController { 16 | 17 | private final WeeklyJobLauncher jobLauncher; 18 | 19 | @GetMapping("/weekly-job") 20 | public ApiResponse executeWeeklyJob() throws Exception { 21 | jobLauncher.launch(); 22 | 23 | return ApiResponse.Companion.success(null); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /cakk-external/src/main/kotlin/com/cakk/external/client/AppleAuthClient.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.external.client 2 | 3 | import com.cakk.common.enums.ReturnCode 4 | import com.cakk.common.exception.CakkException 5 | import org.springframework.beans.factory.annotation.Value 6 | import org.springframework.stereotype.Component 7 | import org.springframework.web.client.RestClient 8 | 9 | import com.cakk.external.vo.key.OidcPublicKeyList 10 | 11 | @Component 12 | class AppleAuthClient( 13 | private val restClient: RestClient, 14 | @Value("\${oauth.apple.public-key-url}") 15 | private val publicKeyUrl: String 16 | ) { 17 | 18 | fun getPublicKeys(): OidcPublicKeyList { 19 | return restClient.get() 20 | .uri(publicKeyUrl) 21 | .retrieve() 22 | .body(OidcPublicKeyList::class.java) 23 | ?: throw CakkException(ReturnCode.EXTERNAL_SERVER_ERROR) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /cakk-external/src/main/kotlin/com/cakk/external/client/KakaoAuthClient.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.external.client 2 | 3 | import com.cakk.common.enums.ReturnCode 4 | import com.cakk.common.exception.CakkException 5 | import org.springframework.beans.factory.annotation.Value 6 | import org.springframework.stereotype.Component 7 | import org.springframework.web.client.RestClient 8 | 9 | import com.cakk.external.vo.key.OidcPublicKeyList 10 | 11 | @Component 12 | class KakaoAuthClient( 13 | private val restClient: RestClient, 14 | @Value("\${oauth.kakao.public-key-info}") 15 | private val publicKeyUrl: String 16 | ) { 17 | 18 | fun getPublicKeys(): OidcPublicKeyList { 19 | return restClient.get() 20 | .uri(publicKeyUrl) 21 | .retrieve() 22 | .body(OidcPublicKeyList::class.java) 23 | ?: throw CakkException(ReturnCode.EXTERNAL_SERVER_ERROR) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/provider/oauth/AppleAuthProvider.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.provider.oauth 2 | 3 | import org.springframework.stereotype.Component 4 | 5 | import com.cakk.api.provider.jwt.JwtProviderImpl 6 | import com.cakk.core.provider.oauth.OidcProvider 7 | import com.cakk.external.client.AppleAuthClient 8 | 9 | @Component 10 | class AppleAuthProvider( 11 | private val appleAuthClient: AppleAuthClient, 12 | private val publicKeyProvider: PublicKeyProvider, 13 | private val jwtProviderImpl: JwtProviderImpl 14 | ) : OidcProvider { 15 | 16 | override fun getProviderId(idToken: String): String { 17 | val oidcPublicKeyList = appleAuthClient.getPublicKeys() 18 | val publicKey = publicKeyProvider.generatePublicKey(parseHeaders(idToken), oidcPublicKeyList) 19 | 20 | return jwtProviderImpl.parseClaims(idToken, publicKey).subject 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/provider/oauth/KakaoAuthProvider.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.provider.oauth 2 | 3 | import org.springframework.stereotype.Component 4 | 5 | import com.cakk.api.provider.jwt.JwtProviderImpl 6 | import com.cakk.core.provider.oauth.OidcProvider 7 | import com.cakk.external.client.KakaoAuthClient 8 | 9 | @Component 10 | class KakaoAuthProvider( 11 | private val kakaoAuthClient: KakaoAuthClient, 12 | private val publicKeyProvider: PublicKeyProvider, 13 | private val jwtProviderImpl: JwtProviderImpl 14 | ) : OidcProvider { 15 | 16 | override fun getProviderId(idToken: String): String { 17 | val oidcPublicKeyList = kakaoAuthClient.getPublicKeys() 18 | val publicKey = publicKeyProvider.generatePublicKey(parseHeaders(idToken), oidcPublicKeyList) 19 | 20 | return jwtProviderImpl.parseClaims(idToken, publicKey).subject 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/config/DataSourceConfig.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.config 2 | 3 | import com.zaxxer.hikari.HikariConfig 4 | import com.zaxxer.hikari.HikariDataSource 5 | import org.springframework.boot.context.properties.ConfigurationProperties 6 | import org.springframework.context.annotation.Bean 7 | import org.springframework.context.annotation.Configuration 8 | import org.springframework.context.annotation.Primary 9 | import javax.sql.DataSource 10 | 11 | @Configuration 12 | class DataSourceConfig { 13 | @Bean 14 | @ConfigurationProperties(prefix = "storage.datasource.core") 15 | fun hikariConfig(): HikariConfig { 16 | return HikariConfig() 17 | } 18 | 19 | @Bean 20 | @Primary 21 | fun dataSource(): DataSource { 22 | return HikariDataSource(hikariConfig()) 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/facade/tag/TagManageFacade.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.facade.tag 2 | 3 | import com.cakk.core.annotation.DomainFacade 4 | import com.cakk.domain.mysql.entity.cake.Tag 5 | import com.cakk.domain.mysql.mapper.TagMapper 6 | import com.cakk.domain.mysql.repository.jpa.TagJpaRepository 7 | 8 | @DomainFacade 9 | class TagManageFacade( 10 | private val tagJpaRepository: TagJpaRepository 11 | ) { 12 | 13 | fun create(tagName: String): Tag { 14 | return tagJpaRepository.findTagByTagName(tagName) ?: tagJpaRepository.save(TagMapper.supplyTagBy(tagName)) 15 | } 16 | 17 | fun createAll(tagNames: List): List { 18 | val tags = tagJpaRepository.findTagsByTagNameIsIn(tagNames) 19 | 20 | return tagNames.map { 21 | tags.find { tag: Tag -> tag.tagName == it } ?: tagJpaRepository.save(TagMapper.supplyTagBy(it)) 22 | }.toList() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/listener/SearchEventListener.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.listener 2 | 3 | import org.springframework.scheduling.annotation.Async 4 | import org.springframework.transaction.event.TransactionPhase 5 | import org.springframework.transaction.event.TransactionalEventListener 6 | 7 | import com.cakk.core.annotation.ApplicationEventListener 8 | import com.cakk.core.dto.event.IncreaseSearchCountEvent 9 | import com.cakk.domain.redis.repository.KeywordRedisRepository 10 | 11 | @ApplicationEventListener 12 | class SearchEventListener( 13 | private val keywordRedisRepository: KeywordRedisRepository 14 | ) { 15 | 16 | @Async 17 | @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 18 | fun increaseSearchCount(event: IncreaseSearchCountEvent) { 19 | keywordRedisRepository.saveOrIncreaseSearchCount(event.keyword) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/annotation/ApplicationEventListener.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.annotation 2 | 3 | import org.springframework.core.annotation.AliasFor 4 | import org.springframework.stereotype.Component 5 | 6 | /** 7 | * Indicates that an annotated class is a "ApplicationEventListener" (e.g. an event publish object). 8 | * 9 | * 10 | * This annotation serves as a specialization of [@Component][Component], 11 | * allowing for implementation classes to be autodetected through classpath scanning. 12 | * 13 | * @author komment 14 | * @see Component 15 | */ 16 | @Target(AnnotationTarget.CLASS) 17 | @Retention(AnnotationRetention.RUNTIME) 18 | @MustBeDocumented 19 | @Component 20 | annotation class ApplicationEventListener( 21 | /** 22 | * Alias for [Component.value]. 23 | */ 24 | @get:AliasFor(annotation = Component::class) val value: String = "" 25 | ) 26 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/converter/DayOfWeekConverter.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.converter 2 | 3 | import com.cakk.common.enums.Days 4 | import com.cakk.common.enums.ReturnCode 5 | import com.cakk.common.exception.CakkException 6 | import jakarta.persistence.AttributeConverter 7 | import java.util.stream.Stream 8 | 9 | class DayOfWeekConverter : AttributeConverter { 10 | 11 | override fun convertToDatabaseColumn(days: Days?): Int? { 12 | return days?.code 13 | } 14 | 15 | override fun convertToEntityAttribute(code: Int?): Days? { 16 | return if (code == null) { 17 | null 18 | } else Stream.of(*Days.values()) 19 | .filter { days: Days -> days.code == code } 20 | .findFirst() 21 | .orElseThrow { CakkException(ReturnCode.INTERNAL_SERVER_ERROR) } 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /cakk-domain/redis/src/main/java/com/cakk/domain/redis/annotation/RedisRepository.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.redis.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | import org.springframework.stereotype.Component; 10 | 11 | /** 12 | * Indicates that an annotated class is a "RedisRepository" (e.g. a data access object). 13 | * 14 | *

This annotation serves as a specialization of {@link Component @Component}, 15 | * allowing for implementation classes to be autodetected through classpath scanning. 16 | * 17 | * @author komment 18 | * @see Component 19 | */ 20 | 21 | @Target(ElementType.TYPE) 22 | @Retention(RetentionPolicy.RUNTIME) 23 | @Documented 24 | @Component 25 | public @interface RedisRepository { 26 | } 27 | -------------------------------------------------------------------------------- /infra/nginx/conf.d/app-server.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name prod-app-api.lovebird-wooda.com; 4 | return 301 https://prod-app-api.lovebird-wooda.com$request_uri; 5 | } 6 | 7 | server { 8 | listen 443 ssl http2; 9 | server_name prod-app-api.lovebird-wooda.com; 10 | 11 | ssl_certificate /etc/letsencrypt/live/prod-app-api.lovebird-wooda.com/fullchain.pem; 12 | ssl_certificate_key /etc/letsencrypt/live/prod-app-api.lovebird-wooda.com/privkey.pem; 13 | 14 | location / { 15 | proxy_pass http://prod-app-api.lovebird-wooda.com:8080; 16 | proxy_set_header Host $http_host; 17 | proxy_set_header X-Real-IP $remote_addr; 18 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 19 | proxy_set_header X-Forwarded-Proto $scheme; 20 | proxy_buffer_size 128k; 21 | proxy_buffers 4 256k; 22 | proxy_busy_buffers_size 256k; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/validator/OperationValidator.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.validator 2 | 3 | import java.util.* 4 | 5 | import jakarta.validation.ConstraintValidator 6 | import jakarta.validation.ConstraintValidatorContext 7 | 8 | import com.cakk.api.annotation.OperationDay 9 | import com.cakk.common.enums.Days 10 | import com.cakk.core.dto.param.shop.ShopOperationParam 11 | 12 | class OperationValidator : ConstraintValidator?> { 13 | override fun isValid(operationParams: List?, context: ConstraintValidatorContext): Boolean { 14 | operationParams ?: return false 15 | 16 | val days: MutableMap = EnumMap(Days::class.java) 17 | operationParams.forEach { 18 | when { 19 | days.containsKey(it.operationDay) -> return false 20 | else -> days[it.operationDay] = true 21 | } 22 | } 23 | 24 | return true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /cakk-domain/redis/src/main/java/com/cakk/domain/redis/annotation/RedisCustomTemplate.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.redis.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | import org.springframework.stereotype.Component; 10 | 11 | /** 12 | * Indicates that an annotated class is a "RedisTemplate" (e.g. a data access object). 13 | * 14 | *

This annotation serves as a specialization of {@link Component @Component}, 15 | * allowing for implementation classes to be autodetected through classpath scanning. 16 | * 17 | * @author komment 18 | * @see Component 19 | */ 20 | 21 | @Target(ElementType.TYPE) 22 | @Retention(RetentionPolicy.RUNTIME) 23 | @Documented 24 | @Component 25 | public @interface RedisCustomTemplate { 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/weekly-batch-job.yml: -------------------------------------------------------------------------------- 1 | # disable 2 | 3 | # name: weekly-batch-job 4 | 5 | # on: 6 | # schedule: 7 | # - cron: '0 0 * * 1' 8 | 9 | # jobs: 10 | # build: 11 | # name: execute batch job 12 | # runs-on: ubuntu-latest 13 | # environment: Cakk-Weekly-Batch 14 | 15 | # steps: 16 | # - name: Get Current Time 17 | # uses: 1466587594/get-current-time@v2 18 | # id: current-time 19 | # with: 20 | # format: YYYY-MM-DDTHH:mm:ss 21 | # timezone: 'Asia/Seoul' 22 | # - name: Print Current Time 23 | # run: echo "Current Time=${{steps.current-time.outputs.formattedTime}}" 24 | # shell: bash 25 | 26 | # - name: Send GET request to API with Authorization 27 | # env: 28 | # # API_TOKEN: ${{ secrets.API_TOKEN }} 29 | # SERVER_URL: ${{ secrets.PROD_BATCH_LAMBDA }} 30 | # run: | 31 | # curl -X GET $SERVER_URL 32 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/converter/LinkKindConverter.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.converter 2 | 3 | import com.cakk.common.enums.LinkKind 4 | import com.cakk.common.enums.ReturnCode 5 | import com.cakk.common.exception.CakkException 6 | import jakarta.persistence.AttributeConverter 7 | import java.util.stream.Stream 8 | 9 | class LinkKindConverter : AttributeConverter { 10 | 11 | override fun convertToDatabaseColumn(linkKind: LinkKind?): String? { 12 | return linkKind?.value 13 | } 14 | 15 | override fun convertToEntityAttribute(value: String?): LinkKind? { 16 | return if (value == null) { 17 | null 18 | } else Stream.of(*LinkKind.values()) 19 | .filter { linkKind: LinkKind -> linkKind.value == value } 20 | .findFirst() 21 | .orElseThrow { CakkException(ReturnCode.INTERNAL_SERVER_ERROR) } 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /cakk-admin/src/main/kotlin/com/cakk/admin/mapper/LinkMapper.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.admin.mapper 2 | 3 | import com.cakk.common.enums.LinkKind 4 | import com.cakk.domain.mysql.entity.shop.CakeShop 5 | import com.cakk.domain.mysql.entity.shop.CakeShopLink 6 | 7 | fun supplyCakeShopLinkByWeb(web: String, cakeShop: CakeShop? = null): CakeShopLink { 8 | return CakeShopLink.builder() 9 | .linkKind(LinkKind.WEB) 10 | .linkPath(web) 11 | .cakeShop(cakeShop) 12 | .build() 13 | } 14 | 15 | fun supplyCakeShopLinkByInstagram(instagram: String, cakeShop: CakeShop? = null): CakeShopLink { 16 | return CakeShopLink.builder() 17 | .linkKind(LinkKind.INSTAGRAM) 18 | .linkPath(instagram) 19 | .cakeShop(cakeShop) 20 | .build() 21 | } 22 | 23 | fun supplyCakeShopLinkByKakao(kakao: String, cakeShop: CakeShop? = null): CakeShopLink { 24 | return CakeShopLink.builder() 25 | .linkKind(LinkKind.KAKAOTALK) 26 | .linkPath(kakao) 27 | .cakeShop(cakeShop) 28 | .build() 29 | } 30 | -------------------------------------------------------------------------------- /cakk-admin/src/main/kotlin/com/cakk/admin/validator/OperationValidator.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.admin.validator 2 | 3 | import java.util.* 4 | 5 | import jakarta.validation.ConstraintValidator 6 | import jakarta.validation.ConstraintValidatorContext 7 | 8 | import com.cakk.admin.annotation.OperationDay 9 | import com.cakk.common.enums.Days 10 | import com.cakk.domain.mysql.dto.param.shop.ShopOperationParam 11 | 12 | class OperationValidator : ConstraintValidator> { 13 | 14 | override fun isValid(operationParams: List?, context: ConstraintValidatorContext): Boolean { 15 | operationParams ?: return false 16 | 17 | val days: MutableMap = EnumMap(Days::class.java) 18 | for (operationParam in operationParams) { 19 | if (days.containsKey(operationParam.operationDay)) { 20 | return false 21 | } else { 22 | days[operationParam.operationDay] = true 23 | } 24 | } 25 | return true 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /cakk-common/src/main/kotlin/com/cakk/common/response/ApiResponse.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.common.response 2 | 3 | import com.cakk.common.enums.ReturnCode 4 | 5 | data class ApiResponse( 6 | val returnCode: String, 7 | val returnMessage: String, 8 | val data: T? = null 9 | ) { 10 | 11 | constructor(returnCode: ReturnCode, data: T? = null) : this(returnCode.code, returnCode.message, data) 12 | 13 | companion object { 14 | 15 | fun success(data: T? = null): ApiResponse { 16 | return ApiResponse( 17 | returnCode = ReturnCode.SUCCESS, 18 | data = data 19 | ) 20 | } 21 | 22 | fun fail(returnCode: ReturnCode, data: T? = null): ApiResponse { 23 | return ApiResponse( 24 | returnCode = returnCode, 25 | data = data 26 | ) 27 | } 28 | 29 | fun error(returnCode: ReturnCode, errorMessage: String?): ApiResponse { 30 | return ApiResponse( 31 | returnCode = returnCode, 32 | data = errorMessage 33 | ) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/service/views/ViewsService.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.service.views 2 | 3 | import org.springframework.context.ApplicationEventPublisher 4 | import org.springframework.stereotype.Service 5 | import org.springframework.transaction.annotation.Transactional 6 | 7 | import com.cakk.common.exception.CakkException 8 | import com.cakk.core.facade.cake.CakeReadFacade 9 | import com.cakk.core.mapper.supplyCakeIncreaseViewsEventBy 10 | 11 | @Service 12 | class ViewsService( 13 | private val cakeReadFacade: CakeReadFacade, 14 | private val publisher: ApplicationEventPublisher 15 | ) { 16 | 17 | @Transactional(readOnly = true) 18 | fun increaseCakeViews(cakeId: Long?) { 19 | cakeId?.let { 20 | try { 21 | val cake = cakeReadFacade.findById(cakeId) 22 | val event = supplyCakeIncreaseViewsEventBy(cake.id) 23 | 24 | publisher.publishEvent(event) 25 | } catch (ignored: CakkException) { 26 | // ignored 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cakk-common/src/main/kotlin/com/cakk/common/enums/RedisKey.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.common.enums 2 | 3 | import com.cakk.common.exception.CakkException 4 | 5 | enum class RedisKey( 6 | val value: String 7 | ) { 8 | REFRESH_TOKEN("REFRESH_TOKEN::"), 9 | EMAIL_VERIFICATION("EMAIL::"), 10 | SEARCH_KEYWORD("SEARCH::keyword"), 11 | VIEWS_CAKE("VIEWS::cake"), 12 | VIEWS_CAKE_SHOP("VIEWS::cake-shop"), 13 | LOCK_CAKE_HEART("LOCK::cake-heart"), 14 | LOCK_SHOP_HEART("LOCK::shop-heart"), 15 | LOCK_SHOP_LIKE("LOCK::shop-like"); 16 | 17 | companion object { 18 | @JvmStatic 19 | fun getLockByMethodName(method: String): RedisKey { 20 | return when (method) { 21 | "heartCake" -> LOCK_CAKE_HEART 22 | "heartCakeShop" -> LOCK_SHOP_HEART 23 | "likeCakeShop" -> LOCK_SHOP_LIKE 24 | else -> throw CakkException(ReturnCode.INTERNAL_SERVER_ERROR) 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/bo/shop/CakeShopByLocationParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.bo.shop; 2 | 3 | import java.util.Set; 4 | 5 | import org.locationtech.jts.geom.Point; 6 | 7 | import lombok.AccessLevel; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Getter; 10 | import lombok.NoArgsConstructor; 11 | import lombok.experimental.SuperBuilder; 12 | 13 | @Getter 14 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 15 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 16 | @SuperBuilder 17 | public class CakeShopByLocationParam extends CakeShopParam { 18 | 19 | private Double longitude; 20 | private Double latitude; 21 | 22 | public CakeShopByLocationParam(Long cakeShopId, String thumbnailUrl, String cakeShopName, String cakeShopBio, 23 | Set cakeImageUrls, Point location) { 24 | super(cakeShopId, thumbnailUrl, cakeShopName, cakeShopBio, cakeImageUrls); 25 | longitude = location.getX(); 26 | latitude = location.getY(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/dto/request/shop/CreateShopRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.dto.request.shop 2 | 3 | import jakarta.validation.constraints.* 4 | 5 | import com.cakk.api.annotation.OperationDay 6 | import com.cakk.core.dto.param.shop.ShopLinkParam 7 | import com.cakk.core.dto.param.shop.ShopOperationParam 8 | 9 | data class CreateShopRequest( 10 | @field:Size(max = 20) 11 | val businessNumber: String?, 12 | @field:OperationDay 13 | val operationDays: List?, 14 | @field:NotBlank @field:Size(max = 30) 15 | val shopName: String?, 16 | @field:Size(max = 40) 17 | val shopBio: String?, 18 | @field:Size(max = 500) 19 | val shopDescription: String?, 20 | @field:NotBlank @field:Size(max = 50) 21 | val shopAddress: String?, 22 | @field:NotNull @field:Min(-90) @field:Max(90) 23 | val latitude: Double?, 24 | @field:NotNull @field:Min(-180) @field:Max(180) 25 | val longitude: Double?, 26 | @field:NotNull 27 | val links: List? 28 | ) 29 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/mapper/EventMapper.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.mapper; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.NoArgsConstructor; 5 | 6 | import com.cakk.domain.mysql.dto.param.user.CertificationParam; 7 | import com.cakk.domain.mysql.entity.shop.CakeShop; 8 | import com.cakk.domain.mysql.event.shop.CertificationEvent; 9 | 10 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 11 | public class EventMapper { 12 | public static CertificationEvent supplyCertificationInfoWithCakeShopInfo(CertificationParam param, CakeShop cakeShop) { 13 | return CertificationEvent.builder() 14 | .idCardImageUrl(param.idCardImageUrl()) 15 | .businessRegistrationImageUrl(param.businessRegistrationImageUrl()) 16 | .emergencyContact(param.emergencyContact()) 17 | .message(param.message()) 18 | .userId(param.user().getId()) 19 | .userEmail(param.user().getEmail()) 20 | .shopName(cakeShop.getShopName()) 21 | .location(cakeShop.getLocation()) 22 | .build(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/facade/shop/CakeShopManageFacade.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.facade.shop 2 | 3 | import com.cakk.core.annotation.DomainFacade 4 | import com.cakk.domain.mysql.entity.shop.CakeShop 5 | import com.cakk.domain.mysql.entity.shop.CakeShopLink 6 | import com.cakk.domain.mysql.entity.shop.CakeShopOperation 7 | import com.cakk.domain.mysql.entity.user.BusinessInformation 8 | import com.cakk.domain.mysql.repository.jpa.CakeShopJpaRepository 9 | 10 | @DomainFacade 11 | class CakeShopManageFacade( 12 | private val cakeShopJpaRepository: CakeShopJpaRepository 13 | ) { 14 | 15 | fun create( 16 | cakeShop: CakeShop, 17 | cakeShopOperations: List, 18 | businessInformation: BusinessInformation, 19 | cakeShopLinks: List 20 | ): CakeShop { 21 | cakeShop.addShopOperationDays(cakeShopOperations) 22 | cakeShop.addShopLinks(cakeShopLinks) 23 | cakeShop.registerBusinessInformation(businessInformation) 24 | 25 | return cakeShopJpaRepository.save(cakeShop) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/bo/shop/CakeShopParam.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.bo.shop; 2 | 3 | import java.util.HashSet; 4 | import java.util.Set; 5 | import java.util.stream.Collectors; 6 | 7 | import lombok.AccessLevel; 8 | import lombok.AllArgsConstructor; 9 | import lombok.Getter; 10 | import lombok.NoArgsConstructor; 11 | import lombok.experimental.SuperBuilder; 12 | 13 | @Getter 14 | @AllArgsConstructor(access = AccessLevel.PROTECTED) 15 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 16 | @SuperBuilder 17 | public class CakeShopParam { 18 | 19 | private Long cakeShopId; 20 | private String thumbnailUrl; 21 | private String cakeShopName; 22 | private String cakeShopBio; 23 | private Set cakeImageUrls; 24 | 25 | 26 | public void setImageCountMaxCount(final int maxCount) { 27 | cakeImageUrls = cakeImageUrls.stream().limit(maxCount).collect(Collectors.toSet()); 28 | } 29 | 30 | public void setImagesEmptySet() { 31 | cakeImageUrls = new HashSet<>(); 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/converter/VerificationStatusConverter.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.converter 2 | 3 | import com.cakk.common.enums.ReturnCode 4 | import com.cakk.common.enums.VerificationStatus 5 | import com.cakk.common.exception.CakkException 6 | import jakarta.persistence.AttributeConverter 7 | import java.util.stream.Stream 8 | 9 | class VerificationStatusConverter : AttributeConverter { 10 | 11 | override fun convertToDatabaseColumn(verificationStatus: VerificationStatus?): Int? { 12 | return verificationStatus?.code 13 | } 14 | 15 | override fun convertToEntityAttribute(code: Int?): VerificationStatus? { 16 | return if (code == null) { 17 | null 18 | } else Stream.of(*VerificationStatus.values()) 19 | .filter { verificationStatus: VerificationStatus -> verificationStatus.code == code } 20 | .findFirst() 21 | .orElseThrow { CakkException(ReturnCode.INTERNAL_SERVER_ERROR) } 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/TokenRedisRepository.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.redis.repository; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import lombok.RequiredArgsConstructor; 6 | 7 | import com.cakk.common.enums.RedisKey; 8 | import com.cakk.domain.redis.annotation.RedisRepository; 9 | import com.cakk.domain.redis.template.RedisValueTemplate; 10 | 11 | @RedisRepository 12 | @RequiredArgsConstructor 13 | public class TokenRedisRepository { 14 | 15 | private final RedisValueTemplate redisValueTemplate; 16 | 17 | private final String key = RedisKey.REFRESH_TOKEN.getValue(); 18 | 19 | public void registerBlackList(final String token, final long timeout) { 20 | redisValueTemplate.save(key + token, "token", timeout, TimeUnit.MILLISECONDS); 21 | } 22 | 23 | public Boolean isBlackListToken(final String token) { 24 | return redisValueTemplate.existByKey(key + token); 25 | } 26 | 27 | public void deleteByToken(final String token) { 28 | redisValueTemplate.delete(key + token); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/entity/cake/Tag.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.entity.cake; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Entity; 5 | import jakarta.persistence.GeneratedValue; 6 | import jakarta.persistence.GenerationType; 7 | import jakarta.persistence.Id; 8 | import jakarta.persistence.Table; 9 | 10 | import lombok.AccessLevel; 11 | import lombok.Builder; 12 | import lombok.Getter; 13 | import lombok.NoArgsConstructor; 14 | 15 | import com.cakk.domain.mysql.entity.audit.AuditCreatedEntity; 16 | 17 | @Getter 18 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 19 | @Entity 20 | @Table(name = "tag") 21 | public class Tag extends AuditCreatedEntity { 22 | 23 | @Id 24 | @GeneratedValue(strategy = GenerationType.IDENTITY) 25 | @Column(name = "tag_id", nullable = false) 26 | private Long id; 27 | 28 | @Column(name = "tag_name", length = 20, nullable = false) 29 | private String tagName; 30 | 31 | @Builder 32 | public Tag(String tagName) { 33 | this.tagName = tagName; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/query/UserQueryRepository.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.repository.query; 2 | 3 | import static com.cakk.domain.mysql.entity.user.QUser.*; 4 | 5 | import org.springframework.stereotype.Repository; 6 | 7 | import com.querydsl.core.types.dsl.BooleanExpression; 8 | import com.querydsl.jpa.impl.JPAQueryFactory; 9 | 10 | import lombok.RequiredArgsConstructor; 11 | 12 | import com.cakk.domain.mysql.entity.user.User; 13 | 14 | @Repository 15 | @RequiredArgsConstructor 16 | public class UserQueryRepository { 17 | 18 | private final JPAQueryFactory queryFactory; 19 | 20 | public User searchByIdWithAll(final Long userId) { 21 | return queryFactory.selectFrom(user) 22 | .leftJoin(user.businessInformationSet).fetchJoin() 23 | .leftJoin(user.cakeHearts).fetchJoin() 24 | .leftJoin(user.cakeShopHearts).fetchJoin() 25 | .leftJoin(user.cakeShopLikes).fetchJoin() 26 | .where(eqUserId(userId)) 27 | .fetchOne(); 28 | } 29 | 30 | private BooleanExpression eqUserId(final Long userId) { 31 | return user.id.eq(userId); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cakk-api/spy.log: -------------------------------------------------------------------------------- 1 | 1718345996733|0|rollback|connection 275|url jdbc:mysql://localhost:62157/test?useSSL=false&allowPublicKeyRetrieval=true|| 2 | 1718345996733|0|rollback|connection 274|url jdbc:mysql://localhost:62157/test?useSSL=false&allowPublicKeyRetrieval=true|| 3 | 1718345996735|0|rollback|connection 273|url jdbc:mysql://localhost:62157/test?useSSL=false&allowPublicKeyRetrieval=true|| 4 | 1718345996735|0|rollback|connection 269|url jdbc:mysql://localhost:62157/test?useSSL=false&allowPublicKeyRetrieval=true|| 5 | 1718345996758|0|rollback|connection 270|url jdbc:mysql://localhost:62157/test?useSSL=false&allowPublicKeyRetrieval=true|| 6 | 1718345996771|0|rollback|connection 268|url jdbc:mysql://localhost:62157/test?useSSL=false&allowPublicKeyRetrieval=true|| 7 | 1718345996813|0|rollback|connection 266|url jdbc:mysql://localhost:62157/test?useSSL=false&allowPublicKeyRetrieval=true|| 8 | 1718345996821|0|rollback|connection 267|url jdbc:mysql://localhost:62157/test?useSSL=false&allowPublicKeyRetrieval=true|| 9 | 1718345996829|0|rollback|connection 272|url jdbc:mysql://localhost:62157/test?useSSL=false&allowPublicKeyRetrieval=true|| 10 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "cakk" 2 | 3 | pluginManagement { 4 | repositories { 5 | mavenCentral() 6 | gradlePluginPortal() 7 | } 8 | } 9 | 10 | plugins { 11 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.6.0" 12 | } 13 | 14 | // presentation 15 | module(name=":app-api", "cakk-api") 16 | module(name=":admin-api", "cakk-admin") 17 | module(name=":batch", "cakk-batch") 18 | 19 | // external 20 | module(name=":external", "cakk-external") 21 | 22 | // application 23 | module(name=":application", "cakk-core") 24 | 25 | // domain & persistence 26 | module(name=":persistence", "cakk-domain") 27 | module(name=":persistence-mysql", "cakk-domain/mysql") 28 | module(name=":persistence-redis", "cakk-domain/redis") 29 | 30 | // common 31 | module(name=":common", "cakk-common") 32 | 33 | dependencyResolutionManagement { 34 | versionCatalogs { 35 | create("libs") { 36 | from(files("libs.versions.toml")) 37 | } 38 | } 39 | } 40 | 41 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 42 | 43 | fun module(name: String, path: String) { 44 | include(name) 45 | project(name).projectDir = file(path) 46 | } 47 | -------------------------------------------------------------------------------- /cakk-admin/src/main/kotlin/com/cakk/admin/mapper/CakeMapper.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.admin.mapper 2 | 3 | import com.cakk.admin.dto.request.CakeCreateByAdminRequest 4 | import com.cakk.admin.dto.request.CakeUpdateByAdminRequest 5 | import com.cakk.core.dto.param.cake.CakeCreateParam 6 | import com.cakk.core.dto.param.cake.CakeUpdateParam 7 | import com.cakk.core.mapper.supplyCakeBy 8 | import com.cakk.domain.mysql.entity.user.User 9 | 10 | fun supplyCakeCreateParamBy(dto: CakeCreateByAdminRequest, user: User, cakeShopId: Long): CakeCreateParam { 11 | return CakeCreateParam( 12 | cake = supplyCakeBy(dto.cakeImageUrl!!), 13 | cakeCategories = supplyCakeCategoryListBy(dto.cakeDesignCategories!!), 14 | tagNames = dto.tagNames!!, 15 | owner = user, 16 | cakeShopId = cakeShopId 17 | ) 18 | } 19 | 20 | fun supplyCakeUpdateParamBy(dto: CakeUpdateByAdminRequest, owner: User, cakeId: Long): CakeUpdateParam { 21 | return CakeUpdateParam( 22 | owner = owner, 23 | cakeId = cakeId, 24 | cakeImageUrl = dto.cakeImageUrl!!, 25 | cakeCategories = supplyCakeCategoryListBy(dto.cakeDesignCategories!!), 26 | tagNames = dto.tagNames!! 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/LockRedisRepository.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.redis.repository; 2 | 3 | import java.util.function.Supplier; 4 | 5 | import org.redisson.api.RLock; 6 | import org.redisson.api.RedissonClient; 7 | 8 | import lombok.RequiredArgsConstructor; 9 | 10 | import com.cakk.domain.redis.annotation.RedisRepository; 11 | import com.cakk.domain.redis.dto.param.ExecuteWithLockParam; 12 | 13 | @RedisRepository 14 | @RequiredArgsConstructor 15 | public class LockRedisRepository { 16 | 17 | private final RedissonClient redissonClient; 18 | 19 | public Object executeWithLock(final ExecuteWithLockParam param) { 20 | final String lockName = param.key(); 21 | final Supplier supplier = param.supplier(); 22 | final RLock rLock = redissonClient.getLock(lockName); 23 | 24 | try { 25 | boolean available = rLock.tryLock(param.waitTime(), param.leaseTime(), param.timeUnit()); 26 | 27 | if (!available) { 28 | return false; 29 | } 30 | 31 | return supplier.get(); 32 | } catch (InterruptedException e) { 33 | throw new RuntimeException(e); 34 | } finally { 35 | rLock.unlock(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /cakk-batch/src/main/java/com/cakk/batch/handler/LambdaHandler.java: -------------------------------------------------------------------------------- 1 | package com.cakk.batch.handler; 2 | 3 | import com.amazonaws.serverless.exceptions.ContainerInitializationException; 4 | import com.amazonaws.serverless.proxy.model.AwsProxyRequest; 5 | import com.amazonaws.serverless.proxy.model.AwsProxyResponse; 6 | import com.amazonaws.serverless.proxy.spring.SpringBootLambdaContainerHandler; 7 | import com.amazonaws.services.lambda.runtime.Context; 8 | import com.amazonaws.services.lambda.runtime.RequestHandler; 9 | 10 | import com.cakk.batch.BatchApplication; 11 | 12 | public class LambdaHandler implements RequestHandler { 13 | 14 | private static final SpringBootLambdaContainerHandler handler; 15 | 16 | static { 17 | try { 18 | handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(BatchApplication.class); 19 | } catch (ContainerInitializationException e) { 20 | throw new RuntimeException("Spring Boot Application 실행 실패", e); 21 | } 22 | } 23 | 24 | @Override 25 | public AwsProxyResponse handleRequest(AwsProxyRequest input, Context context) { 26 | return handler.proxy(input, context); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/facade/cake/CakeManageFacade.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.facade.cake 2 | 3 | import com.cakk.core.annotation.DomainFacade 4 | import com.cakk.domain.mysql.entity.cake.Cake 5 | import com.cakk.domain.mysql.entity.cake.CakeCategory 6 | import com.cakk.domain.mysql.entity.cake.Tag 7 | import com.cakk.domain.mysql.entity.shop.CakeShop 8 | import com.cakk.domain.mysql.repository.jpa.CakeJpaRepository 9 | 10 | @DomainFacade 11 | class CakeManageFacade( 12 | private val cakeJpaRepository: CakeJpaRepository 13 | ) { 14 | 15 | 16 | fun create(cakeShop: CakeShop, cake: Cake, tags: List, cakeCategories: List) { 17 | cake.registerTags(tags) 18 | cake.registerCategories(cakeCategories) 19 | cakeShop.registerCake(cake) 20 | 21 | cakeJpaRepository.save(cake) 22 | } 23 | 24 | fun update(cake: Cake, cakeImageUrl: String, tags: List, cakeCategories: List) { 25 | cake.updateCakeImageUrl(cakeImageUrl) 26 | cake.updateCakeCategories(cakeCategories) 27 | cake.updateCakeTags(tags) 28 | } 29 | 30 | fun delete(cake: Cake) { 31 | cakeJpaRepository.delete(cake) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cakk-admin/src/main/kotlin/com/cakk/admin/dto/request/CakeShopCreateByAdminRequest.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.admin.dto.request 2 | 3 | import jakarta.validation.constraints.Max 4 | import jakarta.validation.constraints.Min 5 | import jakarta.validation.constraints.NotBlank 6 | import jakarta.validation.constraints.NotNull 7 | import jakarta.validation.constraints.Size 8 | 9 | import com.cakk.admin.annotation.OperationDay 10 | import com.cakk.core.dto.param.shop.ShopLinkParam 11 | import com.cakk.core.dto.param.shop.ShopOperationParam 12 | 13 | data class CakeShopCreateByAdminRequest( 14 | @field:Size(max = 20) 15 | val businessNumber: String?, 16 | @field:OperationDay 17 | val operationDays: List?, 18 | @field:NotBlank @field:Size(max = 30) 19 | val shopName: String?, 20 | @field:Size(max = 40) 21 | val shopBio: String?, 22 | @field:Size(max = 500) 23 | val shopDescription: String?, 24 | @field:NotBlank @field:Size(max = 50) 25 | val shopAddress: String?, 26 | @field:NotNull @field:Min(-90) @field:Max(90) 27 | val latitude: Double?, 28 | @field:NotNull @field:Min(-180) @field:Max(180) 29 | val longitude: Double?, 30 | @field:NotNull 31 | val links: List? 32 | ) 33 | -------------------------------------------------------------------------------- /cakk-core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | description = "core module" 2 | 3 | dependencies { 4 | implementation(projects.common) 5 | implementation(projects.persistenceMysql) 6 | implementation(projects.persistenceRedis) 7 | implementation(projects.external) 8 | 9 | // basic 10 | implementation(libs.spring.boot.starter.web) 11 | implementation(libs.spring.boot.starter.validation) 12 | implementation(libs.spring.boot.starter.aop) 13 | annotationProcessor(libs.spring.boot.configuration.processor) 14 | 15 | // test 16 | testImplementation(libs.archunit) 17 | testImplementation(libs.spring.boot.starter.test) 18 | testImplementation(libs.mockito.kotlin) 19 | testImplementation(libs.fixture.monkey.starter) 20 | testImplementation(libs.kotest.junit) 21 | testImplementation(libs.mockk) 22 | 23 | // Jwt 24 | implementation(libs.jwt.api) 25 | implementation(libs.jwt.impl) 26 | implementation(libs.jwt.jackson) 27 | 28 | // Point 29 | implementation(libs.jts.core) 30 | 31 | // Mail 32 | implementation(libs.spring.boot.starter.mail) 33 | 34 | // Slack 35 | implementation(libs.slack.webhook) 36 | } 37 | 38 | tasks.bootJar { 39 | enabled = false 40 | } 41 | 42 | tasks.jar { 43 | enabled = true 44 | } 45 | -------------------------------------------------------------------------------- /cakk-api/src/test/java/com/cakk/api/common/container/RedisTestContainer.java: -------------------------------------------------------------------------------- 1 | package com.cakk.api.common.container; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.test.context.ActiveProfiles; 5 | import org.testcontainers.containers.GenericContainer; 6 | import org.testcontainers.junit.jupiter.Container; 7 | import org.testcontainers.utility.DockerImageName; 8 | 9 | @ActiveProfiles("test") 10 | @Configuration 11 | public class RedisTestContainer { 12 | 13 | private static final String REDIS_IMAGE = "redis:7.2-alpine"; 14 | private static final int REDIS_PORT = 6379; 15 | private static final String REDIS_PASSWORD = "test_redis_password"; 16 | @Container 17 | public static final GenericContainer REDIS_CONTAINER = new GenericContainer<>(DockerImageName.parse(REDIS_IMAGE)) 18 | .withExposedPorts(REDIS_PORT) 19 | .withReuse(true); 20 | 21 | static { 22 | REDIS_CONTAINER.start(); 23 | 24 | System.setProperty("spring.data.redis.host", REDIS_CONTAINER.getHost()); 25 | System.setProperty("spring.data.redis.port", REDIS_CONTAINER.getMappedPort(6379).toString()); 26 | System.setProperty("spring.data.redis.password", REDIS_PASSWORD); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /cakk-admin/src/main/resources/application-local.yml: -------------------------------------------------------------------------------- 1 | storage: 2 | datasource: 3 | core: 4 | jdbc-url: jdbc:mysql://localhost:3306/cakk 5 | username: ${DB_USERNAME} 6 | password: ${DB_PASSWORD} 7 | driver-class-name: com.mysql.cj.jdbc.Driver 8 | data-source-properties: 9 | rewriteBatchedStatements: true 10 | 11 | oauth: 12 | kakao: 13 | public-key-info: https://kauth.kakao.com/.well-known/jwks.json 14 | apple: 15 | public-key-url: https://appleid.apple.com/auth/keys 16 | google: 17 | client-id: ${GOOGLE_CLIENT_ID} 18 | 19 | jwt: 20 | secret: localocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocalocal 21 | expiration: 22 | access-token: 1814400000 23 | refresh-token: 2592000000 24 | 25 | slack: 26 | webhook: 27 | is-enable: false 28 | url: url 29 | 30 | cloud: 31 | aws: 32 | credentials: 33 | access-key: test 34 | secret-key: test 35 | s3: 36 | bucket: test-bucket 37 | expire-in: 180000 38 | object-key: key 39 | region: 40 | static: 41 | ap-northeast-2 42 | stack: 43 | auto: 44 | false 45 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/controller/search/KeywordController.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.controller.search 2 | 3 | import jakarta.validation.Valid 4 | 5 | import org.springframework.web.bind.annotation.GetMapping 6 | import org.springframework.web.bind.annotation.ModelAttribute 7 | import org.springframework.web.bind.annotation.RequestMapping 8 | import org.springframework.web.bind.annotation.RestController 9 | 10 | import com.cakk.api.dto.request.search.TopSearchedListRequest 11 | import com.cakk.api.mapper.supplyTopSearchedListParamBy 12 | import com.cakk.core.dto.response.search.TopSearchedListResponse 13 | import com.cakk.core.service.search.KeywordService 14 | import com.cakk.common.response.ApiResponse 15 | 16 | @RestController 17 | @RequestMapping("/search") 18 | class KeywordController( 19 | private val keywordService: KeywordService 20 | ) { 21 | 22 | @GetMapping("/top-searched") 23 | fun topSearched( 24 | @ModelAttribute @Valid request: TopSearchedListRequest 25 | ): ApiResponse { 26 | val param = supplyTopSearchedListParamBy(request) 27 | val response = keywordService.findTopSearched(param) 28 | 29 | return ApiResponse.success(response) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/KeywordRedisRepository.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.redis.repository; 2 | 3 | import java.util.List; 4 | 5 | import lombok.RequiredArgsConstructor; 6 | 7 | import com.cakk.common.enums.RedisKey; 8 | import com.cakk.domain.redis.annotation.RedisRepository; 9 | import com.cakk.domain.redis.template.RedisZSetTemplate; 10 | 11 | @RedisRepository 12 | @RequiredArgsConstructor 13 | public class KeywordRedisRepository { 14 | 15 | private final RedisZSetTemplate redisZSetTemplate; 16 | 17 | private final String key = RedisKey.SEARCH_KEYWORD.getValue(); 18 | 19 | public void saveOrIncreaseSearchCount(final String value) { 20 | redisZSetTemplate.save(key, value); 21 | redisZSetTemplate.increaseScore(key, value, 1); 22 | } 23 | 24 | public List findTopSearchedLimitCount(final long count) { 25 | return redisZSetTemplate.findAllReverseScore(key, count); 26 | } 27 | 28 | public List findAll() { 29 | return redisZSetTemplate.findAll(key); 30 | } 31 | 32 | public void deleteByValue(final String value) { 33 | redisZSetTemplate.remove(key, value); 34 | } 35 | 36 | public void clear() { 37 | redisZSetTemplate.removeAll(key); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/facade/user/UserReadFacade.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.facade.user 2 | 3 | import com.cakk.common.enums.ReturnCode 4 | import com.cakk.common.exception.CakkException 5 | import com.cakk.core.annotation.DomainFacade 6 | import com.cakk.domain.mysql.entity.user.User 7 | import com.cakk.domain.mysql.repository.jpa.UserJpaRepository 8 | import com.cakk.domain.mysql.repository.query.UserQueryRepository 9 | 10 | @DomainFacade 11 | class UserReadFacade( 12 | private val userJpaRepository: UserJpaRepository, 13 | private val userQueryRepository: UserQueryRepository 14 | ) { 15 | 16 | fun findByUserId(userId: Long): User { 17 | return userJpaRepository.findById(userId).orElseThrow { CakkException(ReturnCode.NOT_EXIST_USER) } 18 | } 19 | 20 | fun findByProviderId(providerId: String): User { 21 | return userJpaRepository.findByProviderId(providerId) ?: throw CakkException(ReturnCode.NOT_EXIST_USER) 22 | } 23 | 24 | fun findByIdWithAll(userId: Long): User { 25 | return userQueryRepository.searchByIdWithAll(userId) ?: throw CakkException(ReturnCode.NOT_EXIST_USER) 26 | } 27 | 28 | fun findAll(): List { 29 | return userJpaRepository.findAll() 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/EmailVerificationRedisRepository.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.redis.repository; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import lombok.RequiredArgsConstructor; 6 | 7 | import com.cakk.common.enums.RedisKey; 8 | import com.cakk.domain.redis.annotation.RedisRepository; 9 | import com.cakk.domain.redis.template.RedisValueTemplate; 10 | 11 | @RedisRepository 12 | @RequiredArgsConstructor 13 | public class EmailVerificationRedisRepository { 14 | 15 | private final RedisValueTemplate redisValueTemplate; 16 | 17 | private final String key = RedisKey.EMAIL_VERIFICATION.getValue(); 18 | 19 | public void save(final String email, final String verificationCode) { 20 | deleteByEmail(email); 21 | redisValueTemplate.save(key + email, verificationCode, 180, TimeUnit.SECONDS); 22 | } 23 | 24 | public String findCodeByEmail(final String email) { 25 | return redisValueTemplate.findByKey(key + email); 26 | } 27 | 28 | public Boolean existByEmail(final String email) { 29 | return redisValueTemplate.existByKey(key + email); 30 | } 31 | 32 | public Boolean deleteByEmail(final String email) { 33 | return redisValueTemplate.delete(key + email); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/provider/oauth/GoogleAuthProvider.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.provider.oauth 2 | 3 | import java.io.IOException 4 | import java.security.GeneralSecurityException 5 | 6 | import org.springframework.stereotype.Component 7 | 8 | import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken 9 | import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier 10 | 11 | import com.cakk.common.enums.ReturnCode 12 | import com.cakk.common.exception.CakkException 13 | import com.cakk.core.provider.oauth.OidcProvider 14 | 15 | @Component 16 | class GoogleAuthProvider( 17 | private val googleIdTokenVerifier: GoogleIdTokenVerifier 18 | ) : OidcProvider { 19 | 20 | override fun getProviderId(idToken: String): String { 21 | return getGoogleIdToken(idToken).payload.subject 22 | } 23 | 24 | private fun getGoogleIdToken(idToken: String): GoogleIdToken { 25 | return try { 26 | googleIdTokenVerifier.verify(idToken) ?: throw CakkException(ReturnCode.EXTERNAL_SERVER_ERROR) 27 | } catch (e: GeneralSecurityException) { 28 | throw CakkException(ReturnCode.EXTERNAL_SERVER_ERROR) 29 | } catch (e: IOException) { 30 | throw CakkException(ReturnCode.EXTERNAL_SERVER_ERROR) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/CakeViewsRedisRepository.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.redis.repository; 2 | 3 | import java.util.List; 4 | 5 | import lombok.RequiredArgsConstructor; 6 | 7 | import com.cakk.common.enums.RedisKey; 8 | import com.cakk.domain.redis.annotation.RedisRepository; 9 | import com.cakk.domain.redis.template.RedisZSetTemplate; 10 | 11 | @RedisRepository 12 | @RequiredArgsConstructor 13 | public class CakeViewsRedisRepository { 14 | 15 | private final RedisZSetTemplate redisZSetTemplate; 16 | 17 | private final String key = RedisKey.VIEWS_CAKE.getValue(); 18 | 19 | public void saveOrIncreaseSearchCount(final Long value) { 20 | redisZSetTemplate.save(key, value); 21 | redisZSetTemplate.increaseScore(key, value, 1); 22 | } 23 | 24 | public List findTopCakeIdsByOffsetAndCount(final long offset, final long count) { 25 | return redisZSetTemplate.findAllReverseScore(key, offset, count); 26 | } 27 | 28 | public List findAll() { 29 | return redisZSetTemplate.findAll(key); 30 | } 31 | 32 | public void deleteByValue(final Long value) { 33 | redisZSetTemplate.remove(key, value); 34 | } 35 | 36 | public void clear() { 37 | redisZSetTemplate.removeAll(key); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /cakk-batch/build.gradle: -------------------------------------------------------------------------------- 1 | description = "batch module" 2 | 3 | dependencies { 4 | implementation(projects.common) 5 | implementation(projects.persistenceMysql) 6 | implementation(projects.persistenceRedis) 7 | 8 | // basic 9 | implementation(libs.spring.boot.starter.web) { 10 | exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat" 11 | } 12 | implementation(libs.spring.boot.starter.batch) 13 | implementation(libs.spring.boot.starter.validation) 14 | annotationProcessor(libs.spring.boot.configuration.processor) 15 | 16 | // test 17 | testImplementation(libs.archunit) 18 | testImplementation(libs.spring.boot.starter.test) 19 | 20 | // test container 21 | testImplementation(libs.testcontainers.junit) 22 | 23 | // slack 설정 24 | implementation(libs.slack.webhook) 25 | 26 | // Point 27 | implementation(libs.jts.core) 28 | 29 | // aws lambda 30 | implementation(libs.aws.serverless) 31 | } 32 | 33 | tasks.register('buildZip', Zip) { 34 | dependsOn('bootJar') 35 | from compileJava 36 | from processResources 37 | into('lib') { 38 | from(jar) 39 | from(configurations.runtimeClasspath) 40 | } 41 | } 42 | 43 | tasks.named("bootJar") { 44 | enabled = true 45 | } 46 | 47 | tasks.named("jar") { 48 | enabled = false 49 | } 50 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/config/JacksonConfig.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.config 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper 4 | import com.fasterxml.jackson.databind.SerializationFeature 5 | import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule 6 | import com.fasterxml.jackson.module.kotlin.KotlinFeature 7 | import com.fasterxml.jackson.module.kotlin.KotlinModule 8 | import org.springframework.context.annotation.Bean 9 | import org.springframework.context.annotation.Configuration 10 | 11 | @Configuration 12 | class JacksonConfig { 13 | @Bean 14 | fun objectMapper(): ObjectMapper { 15 | val objectMapper = ObjectMapper() 16 | objectMapper.registerModule(JavaTimeModule()) 17 | objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) 18 | objectMapper.registerModule( 19 | KotlinModule.Builder() 20 | .withReflectionCacheSize(512) 21 | .configure(KotlinFeature.NullToEmptyCollection, false) 22 | .configure(KotlinFeature.NullToEmptyMap, false) 23 | .configure(KotlinFeature.NullIsSameAsDefault, false) 24 | .configure(KotlinFeature.SingletonSupport, false) 25 | .configure(KotlinFeature.StrictNullChecks, false) 26 | .build()) 27 | 28 | return objectMapper 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/CakeShopViewsRedisRepository.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.redis.repository; 2 | 3 | import java.util.List; 4 | 5 | import lombok.RequiredArgsConstructor; 6 | 7 | import com.cakk.common.enums.RedisKey; 8 | import com.cakk.domain.redis.annotation.RedisRepository; 9 | import com.cakk.domain.redis.template.RedisZSetTemplate; 10 | 11 | @RedisRepository 12 | @RequiredArgsConstructor 13 | public class CakeShopViewsRedisRepository { 14 | 15 | private final RedisZSetTemplate redisZSetTemplate; 16 | 17 | private final String key = RedisKey.VIEWS_CAKE_SHOP.getValue(); 18 | 19 | public void saveOrIncreaseSearchCount(final Long value) { 20 | redisZSetTemplate.save(key, value); 21 | redisZSetTemplate.increaseScore(key, value, 1); 22 | } 23 | 24 | public List findTopShopIdsByOffsetAndCount(final long offset, final long count) { 25 | return redisZSetTemplate.findAllReverseScore(key, offset, count); 26 | } 27 | 28 | public List findAll() { 29 | return redisZSetTemplate.findAll(key); 30 | } 31 | 32 | public void deleteByValue(final Long value) { 33 | redisZSetTemplate.remove(key, value); 34 | } 35 | 36 | public void clear() { 37 | redisZSetTemplate.removeAll(key); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/listener/EmailSendEventListener.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.listener 2 | 3 | import jakarta.mail.internet.MimeMessage 4 | 5 | import org.springframework.context.event.EventListener 6 | import org.springframework.scheduling.annotation.Async 7 | 8 | import com.cakk.core.annotation.ApplicationEventListener 9 | import com.cakk.core.dto.event.EmailWithVerificationCodeSendEvent 10 | import com.cakk.core.mapper.supplyVerificationMessageBy 11 | import com.cakk.external.extractor.MessageExtractor 12 | import com.cakk.external.sender.MessageSender 13 | import com.cakk.external.template.MessageTemplate 14 | import com.cakk.external.vo.message.VerificationMessage 15 | 16 | @ApplicationEventListener 17 | class EmailSendEventListener( 18 | private val messageTemplate: MessageTemplate, 19 | private val messageExtractor: MessageExtractor, 20 | private val messageSender: MessageSender 21 | ) { 22 | 23 | @Async 24 | @EventListener 25 | fun sendEmailIncludeVerificationCode(event: EmailWithVerificationCodeSendEvent) { 26 | val verificationMessage = supplyVerificationMessageBy(event) 27 | messageTemplate.sendMessage(verificationMessage, messageExtractor, messageSender) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /cakk-external/src/main/kotlin/com/cakk/external/config/S3Config.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.external.config 2 | 3 | import org.springframework.beans.factory.annotation.Value 4 | import org.springframework.context.annotation.Bean 5 | import org.springframework.context.annotation.Configuration 6 | import org.springframework.context.annotation.Primary 7 | 8 | import com.amazonaws.auth.AWSStaticCredentialsProvider 9 | import com.amazonaws.auth.BasicAWSCredentials 10 | import com.amazonaws.services.s3.AmazonS3 11 | import com.amazonaws.services.s3.AmazonS3ClientBuilder 12 | 13 | @Configuration 14 | class S3Config( 15 | @Value("\${cloud.aws.credentials.access-key}") private val accessKey: String, 16 | @Value("\${cloud.aws.credentials.secret-key}") private val secretKey: String, 17 | @Value("\${cloud.aws.region.static}") private val region: String 18 | ) { 19 | @Bean 20 | @Primary 21 | fun awsCredentialsProvider(): BasicAWSCredentials { 22 | return BasicAWSCredentials(accessKey, secretKey) 23 | } 24 | 25 | @Bean 26 | fun amazonS3(): AmazonS3 { 27 | return AmazonS3ClientBuilder.standard() 28 | .withRegion(region) 29 | .withCredentials(AWSStaticCredentialsProvider(awsCredentialsProvider())) 30 | .build() 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /cakk-core/src/test/kotlin/com/cakk/core/common/annotation/TestWithDisplayName.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.common.annotation 2 | 3 | import org.junit.jupiter.api.DisplayNameGeneration 4 | import org.junit.jupiter.api.DisplayNameGenerator 5 | import org.junit.jupiter.api.Test 6 | import java.lang.reflect.Method 7 | 8 | @Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) 9 | @Retention(AnnotationRetention.RUNTIME) 10 | @MustBeDocumented 11 | @Test 12 | @DisplayNameGeneration(TestWithDisplayName.TestDisplayNameGenerator::class) 13 | annotation class TestWithDisplayName(val value: String = "") { 14 | 15 | class TestDisplayNameGenerator : DisplayNameGenerator.Standard() { 16 | override fun generateDisplayNameForClass(testClass: Class<*>): String { 17 | val testWithDisplayName = testClass.getAnnotation( 18 | TestWithDisplayName::class.java 19 | ) 20 | 21 | if (testWithDisplayName != null && testWithDisplayName.value.isNotEmpty()) { 22 | return testWithDisplayName.value 23 | } 24 | 25 | return super.generateDisplayNameForClass(testClass) 26 | } 27 | 28 | override fun generateDisplayNameForMethod(testClass: Class<*>?, testMethod: Method): String { 29 | return testMethod.name 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/resolver/RefreshTokenResolver.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.resolver 2 | 3 | import org.springframework.core.MethodParameter 4 | import org.springframework.web.bind.support.WebDataBinderFactory 5 | import org.springframework.web.context.request.NativeWebRequest 6 | import org.springframework.web.method.support.HandlerMethodArgumentResolver 7 | import org.springframework.web.method.support.ModelAndViewContainer 8 | 9 | import com.cakk.api.annotation.RefreshToken 10 | import com.cakk.common.enums.ReturnCode 11 | import com.cakk.common.exception.CakkException 12 | 13 | class RefreshTokenResolver : HandlerMethodArgumentResolver { 14 | 15 | override fun supportsParameter(parameter: MethodParameter): Boolean { 16 | return parameter.hasParameterAnnotation(RefreshToken::class.java) 17 | && String::class.java.isAssignableFrom(parameter.parameterType) 18 | } 19 | 20 | override fun resolveArgument( 21 | parameter: MethodParameter, 22 | mavContainer: ModelAndViewContainer?, 23 | webRequest: NativeWebRequest, 24 | binderFactory: WebDataBinderFactory? 25 | ): Any { 26 | val refreshToken = webRequest.getHeader("Refresh") 27 | 28 | if (refreshToken.isNullOrBlank()) { 29 | throw CakkException(ReturnCode.EMPTY_REFRESH) 30 | } 31 | 32 | return refreshToken 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /cakk-admin/src/main/kotlin/com/cakk/admin/resolver/AdminUserResolver.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.admin.resolver 2 | 3 | import org.springframework.core.MethodParameter 4 | import org.springframework.security.core.context.SecurityContextHolder 5 | import org.springframework.web.bind.support.WebDataBinderFactory 6 | import org.springframework.web.context.request.NativeWebRequest 7 | import org.springframework.web.method.support.HandlerMethodArgumentResolver 8 | import org.springframework.web.method.support.ModelAndViewContainer 9 | 10 | import com.cakk.admin.annotation.AdminUser 11 | import com.cakk.admin.vo.OAuthUserDetails 12 | 13 | import com.cakk.domain.mysql.entity.user.User 14 | 15 | class AdminUserResolver : HandlerMethodArgumentResolver { 16 | override fun supportsParameter(parameter: MethodParameter): Boolean { 17 | return parameter.hasParameterAnnotation(AdminUser::class.java) 18 | && User::class.java.isAssignableFrom(parameter.parameterType) 19 | } 20 | 21 | override fun resolveArgument( 22 | parameter: MethodParameter, 23 | mavContainer: ModelAndViewContainer?, 24 | webRequest: NativeWebRequest, 25 | binderFactory: WebDataBinderFactory? 26 | ): Any { 27 | val userDetails = SecurityContextHolder.getContext().authentication.principal as OAuthUserDetails 28 | 29 | return userDetails.getUser() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/resolver/AuthorizedUserResolver.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.resolver 2 | 3 | import org.springframework.core.MethodParameter 4 | import org.springframework.security.core.context.SecurityContextHolder 5 | import org.springframework.web.bind.support.WebDataBinderFactory 6 | import org.springframework.web.context.request.NativeWebRequest 7 | import org.springframework.web.method.support.HandlerMethodArgumentResolver 8 | import org.springframework.web.method.support.ModelAndViewContainer 9 | 10 | import com.cakk.api.annotation.SignInUser 11 | import com.cakk.api.vo.OAuthUserDetails 12 | import com.cakk.domain.mysql.entity.user.User 13 | 14 | class AuthorizedUserResolver : HandlerMethodArgumentResolver { 15 | override fun supportsParameter(parameter: MethodParameter): Boolean { 16 | return parameter.hasParameterAnnotation(SignInUser::class.java) 17 | && User::class.java.isAssignableFrom(parameter.parameterType) 18 | } 19 | 20 | override fun resolveArgument( 21 | parameter: MethodParameter, 22 | mavContainer: ModelAndViewContainer?, 23 | webRequest: NativeWebRequest, 24 | binderFactory: WebDataBinderFactory? 25 | ): Any? { 26 | val userDetails = SecurityContextHolder.getContext().authentication.principal as OAuthUserDetails? 27 | 28 | return userDetails?.getUser() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cakk-api/src/test/java/com/cakk/api/common/annotation/TestWithDisplayName.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.common.annotation 2 | 3 | import org.junit.jupiter.api.DisplayNameGeneration 4 | import org.junit.jupiter.api.DisplayNameGenerator 5 | import org.junit.jupiter.api.Test 6 | import java.lang.reflect.Method 7 | 8 | @Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) 9 | @Retention( 10 | AnnotationRetention.RUNTIME 11 | ) 12 | @MustBeDocumented 13 | @Test 14 | @DisplayNameGeneration( 15 | TestWithDisplayName.TestDisplayNameGenerator::class 16 | ) 17 | annotation class TestWithDisplayName(val value: String = "") { 18 | 19 | class TestDisplayNameGenerator : DisplayNameGenerator.Standard() { 20 | override fun generateDisplayNameForClass(testClass: Class<*>): String { 21 | val testWithDisplayName = testClass.getAnnotation( 22 | TestWithDisplayName::class.java 23 | ) 24 | 25 | if (testWithDisplayName != null && testWithDisplayName.value.isNotEmpty()) { 26 | return testWithDisplayName.value 27 | } 28 | 29 | return super.generateDisplayNameForClass(testClass) 30 | } 31 | 32 | override fun generateDisplayNameForMethod(testClass: Class<*>?, testMethod: Method): String { 33 | return testMethod.name 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /scripts/start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PREFIX=/home/ubuntu 4 | REPOSITORY="$PREFIX/deploy" 5 | BUILD_DIR=cakk-api/build/libs 6 | PROJECT_NAME=prod-app-api 7 | DEPLOY_PATH="$REPOSITORY/$BUILD_DIR" 8 | DEPLOY_LOG_PATH="$PREFIX/$PROJECT_NAME/deploy.log" 9 | DEPLOY_ERROR_LOG_PATH="$PREFIX/$PROJECT_NAME/deploy_error.log" 10 | APPLICATION_LOG_PATH="$PREFIX/$PROJECT_NAME/application.log" 11 | SPRING_ACTIVE_PROFILES=prod 12 | BUILD_JAR=$(ls -tr $REPOSITORY/$BUILD_DIR/*.jar | tail -n 1) 13 | JAR_NAME=$(basename "$BUILD_JAR") 14 | TIME_NOW=$(date +%c) 15 | 16 | echo "$TIME_NOW > 배포를 시작합니다." >> "$DEPLOY_LOG_PATH" 17 | 18 | echo "$TIME_NOW > build 위치: $DEPLOY_PATH" >> $DEPLOY_LOG_PATH 19 | echo "$TIME_NOW > build 파일명: $JAR_NAME" >> $DEPLOY_LOG_PATH 20 | echo "$TIME_NOW > build 파일 복사" >> $DEPLOY_LOG_PATH 21 | cp "$BUILD_JAR" "$DEPLOY_PATH" 22 | 23 | DEPLOY_JAR="$DEPLOY_PATH/$JAR_NAME" 24 | 25 | echo "$TIME_NOW > DEPLOY_JAR : $DEPLOY_JAR" >> $DEPLOY_LOG_PATH 26 | echo "$TIME_NOW > 배포 환경 : $SPRING_ACTIVE_PROFILES" >> $DEPLOY_LOG_PATH 27 | echo "$TIME_NOW > DEPLOY_JAR 배포" >> $DEPLOY_LOG_PATH 28 | nohup java -jar -Dspring.profiles.active="$SPRING_ACTIVE_PROFILES" "$DEPLOY_JAR" >> $APPLICATION_LOG_PATH 2> $DEPLOY_ERROR_LOG_PATH & 29 | 30 | sleep 5 31 | 32 | echo "$TIME_NOW > 배포를 종료합니다." >> $DEPLOY_LOG_PATH 33 | echo "" >> $DEPLOY_LOG_PATH 34 | -------------------------------------------------------------------------------- /cakk-api/src/main/kotlin/com/cakk/api/resolver/AccessTokenResolver.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.resolver 2 | 3 | import org.springframework.core.MethodParameter 4 | import org.springframework.web.bind.support.WebDataBinderFactory 5 | import org.springframework.web.context.request.NativeWebRequest 6 | import org.springframework.web.method.support.HandlerMethodArgumentResolver 7 | import org.springframework.web.method.support.ModelAndViewContainer 8 | 9 | import com.cakk.api.annotation.AccessToken 10 | import com.cakk.common.enums.ReturnCode 11 | import com.cakk.common.exception.CakkException 12 | 13 | 14 | class AccessTokenResolver : HandlerMethodArgumentResolver { 15 | 16 | override fun supportsParameter(parameter: MethodParameter): Boolean { 17 | return parameter.hasParameterAnnotation(AccessToken::class.java) 18 | && String::class.java.isAssignableFrom(parameter.parameterType) 19 | } 20 | 21 | override fun resolveArgument( 22 | parameter: MethodParameter, 23 | mavContainer: ModelAndViewContainer?, 24 | webRequest: NativeWebRequest, 25 | binderFactory: WebDataBinderFactory? 26 | ): Any { 27 | val accessToken = webRequest.getHeader("Authorization") 28 | 29 | if (accessToken.isNullOrBlank()) { 30 | throw CakkException(ReturnCode.EMPTY_ACCESS) 31 | } 32 | 33 | return accessToken.replace("Bearer ", "") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /infra/nginx/conf.d/nginx.conf: -------------------------------------------------------------------------------- 1 | user www-data; 2 | worker_processes auto; 3 | pid /run/nginx.pid; 4 | error_log /var/log/nginx/error.log; 5 | include /etc/nginx/modules-enabled/*.conf; 6 | 7 | events { 8 | worker_connections 768; 9 | # multi_accept on; 10 | } 11 | 12 | http { 13 | 14 | ## 15 | # Basic Settings 16 | ## 17 | 18 | sendfile on; 19 | tcp_nopush on; 20 | types_hash_max_size 2048; 21 | # server_tokens off; 22 | 23 | # server_names_hash_bucket_size 64; 24 | # server_name_in_redirect off; 25 | 26 | include /etc/nginx/mime.types; 27 | default_type application/octet-stream; 28 | 29 | ## 30 | # SSL Settings 31 | ## 32 | 33 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE 34 | ssl_prefer_server_ciphers on; 35 | 36 | ## 37 | # Logging Settings 38 | ## 39 | 40 | access_log /var/log/nginx/access.log; 41 | 42 | ## 43 | # Gzip Settings 44 | ## 45 | 46 | gzip on; 47 | 48 | # gzip_vary on; 49 | # gzip_proxied any; 50 | # gzip_comp_level 6; 51 | # gzip_buffers 16 8k; 52 | # gzip_http_version 1.1; 53 | # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; 54 | 55 | ## 56 | # Virtual Host Configs 57 | ## 58 | 59 | include /etc/nginx/conf.d/*.conf; 60 | include /etc/nginx/sites-enabled/*; 61 | } 62 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/listener/ViewsIncreaseEventListener.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.listener 2 | 3 | 4 | import org.springframework.scheduling.annotation.Async 5 | import org.springframework.transaction.event.TransactionPhase 6 | import org.springframework.transaction.event.TransactionalEventListener 7 | 8 | import com.cakk.core.annotation.ApplicationEventListener 9 | import com.cakk.core.dto.event.CakeIncreaseViewsEvent 10 | import com.cakk.core.dto.event.CakeShopIncreaseViewsEvent 11 | import com.cakk.domain.redis.repository.CakeShopViewsRedisRepository 12 | import com.cakk.domain.redis.repository.CakeViewsRedisRepository 13 | 14 | @ApplicationEventListener 15 | class ViewsIncreaseEventListener( 16 | private val cakeViewsRedisRepository: CakeViewsRedisRepository, 17 | private val cakeShopViewsRedisRepository: CakeShopViewsRedisRepository 18 | ) { 19 | 20 | @Async 21 | @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 22 | fun increaseCakeViews(event: CakeIncreaseViewsEvent) { 23 | cakeViewsRedisRepository.saveOrIncreaseSearchCount(event.cakeId) 24 | } 25 | 26 | @Async 27 | @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 28 | fun increaseCakeShopViews(event: CakeShopIncreaseViewsEvent) { 29 | cakeShopViewsRedisRepository.saveOrIncreaseSearchCount(event.cakeShopId) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/facade/user/UserManageFacade.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.facade.user 2 | 3 | import com.cakk.common.enums.ReturnCode 4 | import com.cakk.common.exception.CakkException 5 | import com.cakk.core.annotation.DomainFacade 6 | import com.cakk.domain.mysql.dto.param.user.ProfileUpdateParam 7 | import com.cakk.domain.mysql.entity.user.User 8 | import com.cakk.domain.mysql.entity.user.UserWithdrawal 9 | import com.cakk.domain.mysql.repository.jpa.UserJpaRepository 10 | import com.cakk.domain.mysql.repository.jpa.UserWithdrawalJpaRepository 11 | 12 | @DomainFacade 13 | class UserManageFacade( 14 | private val userJpaRepository: UserJpaRepository, 15 | private val userWithdrawalJpaRepository: UserWithdrawalJpaRepository 16 | ) { 17 | 18 | fun create(user: User): User { 19 | userJpaRepository.findByProviderId(user.providerId)?.let { 20 | throw CakkException(ReturnCode.ALREADY_EXIST_USER) 21 | } ?: return userJpaRepository.save(user) 22 | } 23 | 24 | fun updateProfile(user: User, param: ProfileUpdateParam) { 25 | user.updateProfile(param) 26 | } 27 | 28 | fun withdraw(user: User, withdrawal: UserWithdrawal) { 29 | user.unHeartAndLikeAll() 30 | user.businessInformationSet.forEach { it.unLinkBusinessOwner() } 31 | 32 | userWithdrawalJpaRepository.save(withdrawal) 33 | userJpaRepository.delete(user) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/entity/cake/CakeHeart.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.entity.cake; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Entity; 5 | import jakarta.persistence.GeneratedValue; 6 | import jakarta.persistence.GenerationType; 7 | import jakarta.persistence.Id; 8 | import jakarta.persistence.JoinColumn; 9 | import jakarta.persistence.ManyToOne; 10 | import jakarta.persistence.Table; 11 | 12 | import lombok.AccessLevel; 13 | import lombok.Getter; 14 | import lombok.NoArgsConstructor; 15 | 16 | import com.cakk.domain.mysql.entity.audit.AuditCreatedEntity; 17 | import com.cakk.domain.mysql.entity.user.User; 18 | 19 | @Getter 20 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 21 | @Entity 22 | @Table(name = "cake_heart") 23 | public class CakeHeart extends AuditCreatedEntity { 24 | 25 | @Id 26 | @GeneratedValue(strategy = GenerationType.IDENTITY) 27 | @Column(name = "cake_heart_id", nullable = false) 28 | private Long id; 29 | 30 | @ManyToOne 31 | @JoinColumn(name = "cake_id", referencedColumnName = "cake_id", nullable = false) 32 | private Cake cake; 33 | 34 | @ManyToOne 35 | @JoinColumn(name = "user_id", referencedColumnName = "user_id", nullable = false) 36 | private User user; 37 | 38 | public CakeHeart(Cake cake, User user) { 39 | this.cake = cake; 40 | this.user = user; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/listener/ErrorAlertEventListener.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.listener 2 | 3 | import org.springframework.scheduling.annotation.Async 4 | import org.springframework.transaction.event.TransactionPhase 5 | import org.springframework.transaction.event.TransactionalEventListener 6 | 7 | import net.gpedro.integrations.slack.SlackMessage 8 | 9 | import com.cakk.core.annotation.ApplicationEventListener 10 | import com.cakk.core.dto.event.ErrorAlertEvent 11 | import com.cakk.core.mapper.supplyErrorAlertMessageBy 12 | import com.cakk.external.extractor.MessageExtractor 13 | import com.cakk.external.sender.MessageSender 14 | import com.cakk.external.template.MessageTemplate 15 | import com.cakk.external.vo.message.ErrorAlertMessage 16 | 17 | @ApplicationEventListener 18 | class ErrorAlertEventListener( 19 | private val messageTemplate: MessageTemplate, 20 | private val messageExtractor: MessageExtractor, 21 | private val messageSender: MessageSender 22 | ) { 23 | 24 | @Async 25 | @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 26 | fun sendMessageToSlack(errorAlertEvent: ErrorAlertEvent) { 27 | val certificationMessage = supplyErrorAlertMessageBy(errorAlertEvent) 28 | messageTemplate.sendMessage(certificationMessage, messageExtractor, messageSender) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/facade/cake/CakeShopUserReadFacade.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.facade.cake 2 | 3 | import com.cakk.core.annotation.DomainFacade 4 | import com.cakk.domain.mysql.dto.param.like.HeartCakeImageResponseParam 5 | import com.cakk.domain.mysql.dto.param.like.HeartCakeShopResponseParam 6 | import com.cakk.domain.mysql.repository.query.CakeHeartQueryRepository 7 | import com.cakk.domain.mysql.repository.query.CakeShopHeartQueryRepository 8 | 9 | @DomainFacade 10 | class CakeShopUserReadFacade( 11 | private val cakeShopHeartQueryRepository: CakeShopHeartQueryRepository, 12 | private val cakeHeartQueryRepository: CakeHeartQueryRepository 13 | ) { 14 | 15 | fun searchAllCakeShopsByCursorAndHeart( 16 | cakeShopHeartId: Long?, 17 | userId: Long?, 18 | pageSize: Int 19 | ): List { 20 | val cakeShopHeartIds: List = cakeShopHeartQueryRepository.searchIdsByCursorAndHeart(cakeShopHeartId, userId, pageSize) 21 | return cakeShopHeartQueryRepository.searchAllByCursorAndHeart(cakeShopHeartIds) 22 | } 23 | 24 | fun searchCakeImagesByCursorAndHeart( 25 | cakeHeartId: Long?, 26 | userId: Long?, 27 | pageSize: Int 28 | ): List { 29 | return cakeHeartQueryRepository.searchCakeImagesByCursorAndHeart(cakeHeartId, userId, pageSize) 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/entity/audit/AuditEntity.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.entity.audit; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | import jakarta.persistence.Column; 6 | import jakarta.persistence.EntityListeners; 7 | import jakarta.persistence.MappedSuperclass; 8 | 9 | import org.springframework.data.annotation.CreatedDate; 10 | import org.springframework.data.annotation.LastModifiedDate; 11 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 12 | 13 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 14 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 15 | import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; 16 | import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; 17 | 18 | import lombok.Getter; 19 | 20 | @Getter 21 | @MappedSuperclass 22 | @EntityListeners(AuditingEntityListener.class) 23 | public class AuditEntity { 24 | 25 | @JsonSerialize(using = LocalDateTimeSerializer.class) 26 | @JsonDeserialize(using = LocalDateTimeDeserializer.class) 27 | @CreatedDate 28 | @Column(updatable = false) 29 | private LocalDateTime createdAt; 30 | 31 | @JsonSerialize(using = LocalDateTimeSerializer.class) 32 | @JsonDeserialize(using = LocalDateTimeDeserializer.class) 33 | @LastModifiedDate 34 | @Column 35 | private LocalDateTime updatedAt; 36 | } 37 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/entity/shop/CakeShopLike.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.entity.shop; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Entity; 5 | import jakarta.persistence.GeneratedValue; 6 | import jakarta.persistence.GenerationType; 7 | import jakarta.persistence.Id; 8 | import jakarta.persistence.JoinColumn; 9 | import jakarta.persistence.ManyToOne; 10 | import jakarta.persistence.Table; 11 | 12 | import lombok.AccessLevel; 13 | import lombok.Getter; 14 | import lombok.NoArgsConstructor; 15 | 16 | import com.cakk.domain.mysql.entity.audit.AuditCreatedEntity; 17 | import com.cakk.domain.mysql.entity.user.User; 18 | 19 | @Getter 20 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 21 | @Entity 22 | @Table(name = "cake_shop_like") 23 | public class CakeShopLike extends AuditCreatedEntity { 24 | 25 | @Id 26 | @GeneratedValue(strategy = GenerationType.IDENTITY) 27 | @Column(name = "shop_like_id", nullable = false) 28 | private Long id; 29 | 30 | @ManyToOne 31 | @JoinColumn(name = "shop_id", referencedColumnName = "shop_id", nullable = false) 32 | private CakeShop cakeShop; 33 | 34 | @ManyToOne 35 | @JoinColumn(name = "user_id", referencedColumnName = "user_id", nullable = false) 36 | private User user; 37 | 38 | public CakeShopLike(CakeShop cakeShop, User user) { 39 | this.cakeShop = cakeShop; 40 | this.user = user; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/entity/shop/CakeShopHeart.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.entity.shop; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Entity; 5 | import jakarta.persistence.GeneratedValue; 6 | import jakarta.persistence.GenerationType; 7 | import jakarta.persistence.Id; 8 | import jakarta.persistence.JoinColumn; 9 | import jakarta.persistence.ManyToOne; 10 | import jakarta.persistence.Table; 11 | 12 | import lombok.AccessLevel; 13 | import lombok.Getter; 14 | import lombok.NoArgsConstructor; 15 | 16 | import com.cakk.domain.mysql.entity.audit.AuditCreatedEntity; 17 | import com.cakk.domain.mysql.entity.user.User; 18 | 19 | @Getter 20 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 21 | @Entity 22 | @Table(name = "cake_shop_heart") 23 | public class CakeShopHeart extends AuditCreatedEntity { 24 | 25 | @Id 26 | @GeneratedValue(strategy = GenerationType.IDENTITY) 27 | @Column(name = "shop_heart_id", nullable = false) 28 | private Long id; 29 | 30 | @ManyToOne 31 | @JoinColumn(name = "shop_id", referencedColumnName = "shop_id", nullable = false) 32 | private CakeShop cakeShop; 33 | 34 | @ManyToOne 35 | @JoinColumn(name = "user_id", referencedColumnName = "user_id", nullable = false) 36 | private User user; 37 | 38 | public CakeShopHeart(CakeShop cakeShop, User user) { 39 | this.cakeShop = cakeShop; 40 | this.user = user; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/bo/shop/CakeShops.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.bo.shop; 2 | 3 | import java.util.List; 4 | 5 | public class CakeShops { 6 | 7 | private List cakeShops; 8 | 9 | public CakeShops(List cakeShops, int imageMaxCount) { 10 | validationEmptyCakeImage(cakeShops); 11 | validationImageCountMaxCount(cakeShops, imageMaxCount); 12 | this.cakeShops = cakeShops; 13 | } 14 | 15 | public CakeShops(List cakeShops, int imageMaxCount, int pageSize) { 16 | validationImageCountMaxCount(cakeShops, imageMaxCount); 17 | cakeShops = validationPageSize(cakeShops, pageSize); 18 | this.cakeShops = cakeShops; 19 | } 20 | 21 | public List getCakeShops() { 22 | return cakeShops; 23 | } 24 | 25 | private void validationImageCountMaxCount(List cakeShops, final int maxCount) { 26 | for (T cakeShop : cakeShops) { 27 | if (cakeShop.getCakeImageUrls().size() > maxCount) { 28 | cakeShop.setImageCountMaxCount(maxCount); 29 | } 30 | } 31 | } 32 | 33 | private List validationPageSize(List cakeShops, int pageSize) { 34 | return cakeShops.stream().limit(pageSize).toList(); 35 | } 36 | 37 | private void validationEmptyCakeImage(List cakeShops) { 38 | for (T cakeShop : cakeShops) { 39 | if (cakeShop.getCakeImageUrls().contains("")) { 40 | cakeShop.setImagesEmptySet(); 41 | } 42 | } 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/listener/CertificationEventListener.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.listener 2 | 3 | import org.springframework.scheduling.annotation.Async 4 | import org.springframework.transaction.event.TransactionPhase 5 | import org.springframework.transaction.event.TransactionalEventListener 6 | 7 | import net.gpedro.integrations.slack.SlackMessage 8 | 9 | import com.cakk.core.annotation.ApplicationEventListener 10 | import com.cakk.core.mapper.supplyCertificationMessageBy 11 | import com.cakk.domain.mysql.event.shop.CertificationEvent 12 | import com.cakk.external.extractor.MessageExtractor 13 | import com.cakk.external.sender.MessageSender 14 | import com.cakk.external.template.MessageTemplate 15 | import com.cakk.external.vo.message.CertificationMessage 16 | 17 | @ApplicationEventListener 18 | class CertificationEventListener( 19 | private val messageTemplate: MessageTemplate, 20 | private val messageExtractor: MessageExtractor, 21 | private val messageSender: MessageSender 22 | ) { 23 | 24 | @Async 25 | @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 26 | fun sendMessageToSlack(certificationEvent: CertificationEvent) { 27 | val certificationMessage = supplyCertificationMessageBy(certificationEvent) 28 | messageTemplate.sendMessage(certificationMessage, messageExtractor, messageSender) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cakk-batch/src/main/java/com/cakk/batch/tasklet/FindKeywordListTaskLet.java: -------------------------------------------------------------------------------- 1 | package com.cakk.batch.tasklet; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.batch.core.StepContribution; 6 | import org.springframework.batch.core.configuration.annotation.StepScope; 7 | import org.springframework.batch.core.scope.context.ChunkContext; 8 | import org.springframework.batch.core.step.tasklet.Tasklet; 9 | import org.springframework.batch.item.ExecutionContext; 10 | import org.springframework.batch.repeat.RepeatStatus; 11 | import org.springframework.stereotype.Component; 12 | 13 | import lombok.RequiredArgsConstructor; 14 | 15 | import com.cakk.batch.utils.BatchConstants; 16 | import com.cakk.domain.redis.repository.KeywordRedisRepository; 17 | 18 | @StepScope 19 | @Component 20 | @RequiredArgsConstructor 21 | public class FindKeywordListTaskLet implements Tasklet { 22 | 23 | private final KeywordRedisRepository keywordRedisRepository; 24 | 25 | @Override 26 | public RepeatStatus execute(final StepContribution contribution, final ChunkContext chunkContext) { 27 | final ExecutionContext executionContext = chunkContext.getStepContext() 28 | .getStepExecution() 29 | .getJobExecution() 30 | .getExecutionContext(); 31 | 32 | final List keywords = keywordRedisRepository.findAll(); 33 | executionContext.put(BatchConstants.KEYWORD_LIST, keywords); 34 | 35 | return RepeatStatus.FINISHED; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/entity/cake/CakeTag.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.mysql.entity.cake; 2 | 3 | import jakarta.persistence.Column; 4 | import jakarta.persistence.Entity; 5 | import jakarta.persistence.FetchType; 6 | import jakarta.persistence.GeneratedValue; 7 | import jakarta.persistence.GenerationType; 8 | import jakarta.persistence.Id; 9 | import jakarta.persistence.JoinColumn; 10 | import jakarta.persistence.ManyToOne; 11 | import jakarta.persistence.Table; 12 | 13 | import lombok.AccessLevel; 14 | import lombok.Builder; 15 | import lombok.Getter; 16 | import lombok.NoArgsConstructor; 17 | 18 | import com.cakk.domain.mysql.entity.audit.AuditCreatedEntity; 19 | 20 | @Getter 21 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 22 | @Entity 23 | @Table(name = "cake_tag") 24 | public class CakeTag extends AuditCreatedEntity { 25 | 26 | @Id 27 | @GeneratedValue(strategy = GenerationType.IDENTITY) 28 | @Column(name = "cake_tag_id", nullable = false) 29 | private Long id; 30 | 31 | @ManyToOne(fetch = FetchType.LAZY) 32 | @JoinColumn(name = "cake_id", referencedColumnName = "cake_id", nullable = false) 33 | private Cake cake; 34 | 35 | @ManyToOne(fetch = FetchType.LAZY) 36 | @JoinColumn(name = "tag_id", referencedColumnName = "tag_id", nullable = false) 37 | private Tag tag; 38 | 39 | @Builder 40 | public CakeTag(Cake cake, Tag tag) { 41 | this.cake = cake; 42 | this.tag = tag; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /cakk-batch/src/main/java/com/cakk/batch/tasklet/FindViewsCakeIdListTaskLet.java: -------------------------------------------------------------------------------- 1 | package com.cakk.batch.tasklet; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.batch.core.StepContribution; 6 | import org.springframework.batch.core.configuration.annotation.StepScope; 7 | import org.springframework.batch.core.scope.context.ChunkContext; 8 | import org.springframework.batch.core.step.tasklet.Tasklet; 9 | import org.springframework.batch.item.ExecutionContext; 10 | import org.springframework.batch.repeat.RepeatStatus; 11 | import org.springframework.stereotype.Component; 12 | 13 | import lombok.RequiredArgsConstructor; 14 | 15 | import com.cakk.batch.utils.BatchConstants; 16 | import com.cakk.domain.redis.repository.CakeViewsRedisRepository; 17 | 18 | @StepScope 19 | @Component 20 | @RequiredArgsConstructor 21 | public class FindViewsCakeIdListTaskLet implements Tasklet { 22 | 23 | private final CakeViewsRedisRepository cakeViewsRedisRepository; 24 | 25 | @Override 26 | public RepeatStatus execute(final StepContribution contribution, final ChunkContext chunkContext) { 27 | final ExecutionContext executionContext = chunkContext.getStepContext() 28 | .getStepExecution() 29 | .getJobExecution() 30 | .getExecutionContext(); 31 | 32 | final List cakeIds = cakeViewsRedisRepository.findAll(); 33 | executionContext.put(BatchConstants.CAKE_ID_LIST, cakeIds); 34 | 35 | return RepeatStatus.FINISHED; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /cakk-api/src/test/java/com/cakk/api/common/config/WithMockCustomUserSecurityContextFactory.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.api.common.config 2 | 3 | import org.springframework.beans.factory.annotation.Autowired 4 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken 5 | import org.springframework.security.core.context.SecurityContext 6 | import org.springframework.security.core.context.SecurityContextHolder 7 | import org.springframework.security.test.context.support.WithSecurityContextFactory 8 | import org.springframework.stereotype.Component 9 | 10 | import com.cakk.api.common.annotation.MockCustomUser 11 | import com.cakk.api.vo.OAuthUserDetails 12 | import com.cakk.core.facade.user.UserReadFacade 13 | 14 | @Component 15 | class WithMockCustomUserSecurityContextFactory : WithSecurityContextFactory { 16 | 17 | @Autowired 18 | private lateinit var userReadFacade: UserReadFacade 19 | 20 | override fun createSecurityContext(annotation: MockCustomUser?): SecurityContext { 21 | val securityContext = SecurityContextHolder.createEmptyContext() 22 | 23 | val user = userReadFacade.findByUserId(1L) 24 | val userDetails = OAuthUserDetails(user) 25 | val authenticationToken = UsernamePasswordAuthenticationToken(userDetails, null, userDetails.authorities) 26 | 27 | securityContext.authentication = authenticationToken 28 | return securityContext 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cakk-core/src/main/kotlin/com/cakk/core/service/user/UserService.kt: -------------------------------------------------------------------------------- 1 | package com.cakk.core.service.user 2 | 3 | import org.springframework.stereotype.Service 4 | import org.springframework.transaction.annotation.Transactional 5 | 6 | import com.cakk.core.dto.response.user.ProfileInformationResponse 7 | import com.cakk.core.facade.user.UserManageFacade 8 | import com.cakk.core.facade.user.UserReadFacade 9 | import com.cakk.core.mapper.supplyProfileInformationResponseBy 10 | import com.cakk.core.mapper.supplyUserWithdrawalBy 11 | import com.cakk.domain.mysql.dto.param.user.ProfileUpdateParam 12 | import com.cakk.domain.mysql.entity.user.User 13 | 14 | @Service 15 | class UserService( 16 | private val userReadFacade: UserReadFacade, 17 | private val userManageFacade: UserManageFacade 18 | ) { 19 | 20 | 21 | @Transactional(readOnly = true) 22 | fun findProfile(signInUser: User): ProfileInformationResponse { 23 | val user = userReadFacade.findByUserId(signInUser.id) 24 | 25 | return supplyProfileInformationResponseBy(user) 26 | } 27 | 28 | @Transactional 29 | fun updateInformation(param: ProfileUpdateParam) { 30 | val user = userReadFacade.findByUserId(param.userId) 31 | userManageFacade.updateProfile(user, param) 32 | } 33 | 34 | @Transactional 35 | fun withdraw(signInUser: User) { 36 | val user = userReadFacade.findByIdWithAll(signInUser.id) 37 | val withdrawal = supplyUserWithdrawalBy(user) 38 | 39 | userManageFacade.withdraw(user, withdrawal) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /cakk-batch/src/main/java/com/cakk/batch/job/launcher/WeeklyJobLauncher.java: -------------------------------------------------------------------------------- 1 | package com.cakk.batch.job.launcher; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | import org.springframework.batch.core.JobParameters; 6 | import org.springframework.batch.core.JobParametersBuilder; 7 | import org.springframework.batch.core.launch.JobLauncher; 8 | import org.springframework.stereotype.Component; 9 | 10 | import lombok.RequiredArgsConstructor; 11 | 12 | import com.cakk.batch.job.config.ClearKeywordListJobConfig; 13 | import com.cakk.batch.job.config.ClearViewsCakeIdListJobConfig; 14 | 15 | @RequiredArgsConstructor 16 | @Component 17 | public class WeeklyJobLauncher { 18 | 19 | private final JobLauncher jobLauncher; 20 | 21 | private final ClearKeywordListJobConfig clearKeywordListJobConfig; 22 | private final ClearViewsCakeIdListJobConfig clearViewsCakeIdListJobConfig; 23 | 24 | public void launch() throws Exception { 25 | final JobParameters clearKeywordListJobParams = new JobParametersBuilder() 26 | .addLocalDateTime("clearKeywordListDate", LocalDateTime.now()) 27 | .toJobParameters(); 28 | 29 | final JobParameters clearViewsCakeIdListJobParams = new JobParametersBuilder() 30 | .addLocalDateTime("clearViewsCakeIdListDate", LocalDateTime.now()) 31 | .toJobParameters(); 32 | 33 | jobLauncher.run(clearKeywordListJobConfig.runJob(), clearKeywordListJobParams); 34 | jobLauncher.run(clearViewsCakeIdListJobConfig.runJob(), clearViewsCakeIdListJobParams); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /cakk-api/src/test/resources/sql/insert-business-information.sql: -------------------------------------------------------------------------------- 1 | SET @g1 = 'Point(37.197734 127.098190)'; 2 | SET @g2 = 'Point(37.201623 127.091568)'; 3 | SET @g3 = 'Point(37.209769 127.100107)'; 4 | 5 | insert into cake_shop (shop_id, thumbnail_url, shop_name, shop_address, shop_bio, shop_description, location, like_count, heart_count, created_at, updated_at) 6 | values (1, 'thumbnail_url1', '케이크 맛집1', '서울시 강남구 어쩌고로1', '케이크 맛집입니다.', '케이크 맛집입니다.', ST_GeomFromText(@g1, 4326), 0, 0, now(), now()), 7 | (2, 'thumbnail_url2', '케이크 맛집2', '서울시 강남구 어쩌고로2', '케이크 맛집입니다.', '케이크 맛집입니다.', ST_GeomFromText(@g2, 4326), 0, 0, now(), now()), 8 | (3, 'thumbnail_url3', '케이크 맛집3', '서울시 강남구 어쩌고로3', '케이크 맛집입니다.', '케이크 맛집입니다.', ST_GeomFromText(@g3, 4326), 0, 0, now(), now()); 9 | 10 | insert into business_information(business_number, business_registration_image_url, id_card_image_url, emergency_contact, shop_id, user_id) 11 | values ('010-3375-5556', 'https://business_registration_image_url2', 'https://id_card_image_url2', '010-0000-0001', 2, 2), 12 | ('010-3375-5557', 'https://business_registration_image_url3', 'https://id_card_image_url3', '010-0000-0002', 3, 3); 13 | 14 | insert into business_information(business_number, business_registration_image_url, id_card_image_url, emergency_contact, shop_id, user_id, verification_status, created_at, updated_at) 15 | values ('010-3375-5555', 'https://business_registration_image_url1', 'https://id_card_image_url1', '010-0000-0000', 1, 1, 3, now(), now()); 16 | -------------------------------------------------------------------------------- /cakk-domain/mysql/src/test/java/com/cakk/domain/annotation/TestWithDisplayName.java: -------------------------------------------------------------------------------- 1 | package com.cakk.domain.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | import java.lang.reflect.Method; 9 | 10 | import org.junit.jupiter.api.DisplayNameGeneration; 11 | import org.junit.jupiter.api.DisplayNameGenerator; 12 | import org.junit.jupiter.api.Test; 13 | 14 | @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD}) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Documented 17 | @Test 18 | @DisplayNameGeneration(TestWithDisplayName.TestDisplayNameGenerator.class) 19 | public @interface TestWithDisplayName { 20 | 21 | String value() default ""; 22 | 23 | class TestDisplayNameGenerator extends DisplayNameGenerator.Standard { 24 | 25 | @Override 26 | public String generateDisplayNameForClass(Class testClass) { 27 | final TestWithDisplayName testWithDisplayName = testClass.getAnnotation(TestWithDisplayName.class); 28 | 29 | if (testWithDisplayName != null && !testWithDisplayName.value().isEmpty()) { 30 | return testWithDisplayName.value(); 31 | } 32 | 33 | return super.generateDisplayNameForClass(testClass); 34 | } 35 | 36 | @Override 37 | public String generateDisplayNameForMethod(Class testClass, Method testMethod) { 38 | return testMethod.getName(); 39 | } 40 | } 41 | } 42 | --------------------------------------------------------------------------------