├── hashtagmap-admin ├── src │ ├── test │ │ ├── resources │ │ │ └── empty.txt │ │ └── java │ │ │ └── com │ │ │ └── songpapeople │ │ │ └── hashtagmap │ │ │ ├── HashTagMapAdminApplicationTest.java │ │ │ ├── controller │ │ │ └── RouterControllerJavaTest.java │ │ │ └── docs │ │ │ ├── instagram │ │ │ └── InstagramApiDocumentation.java │ │ │ └── ApiDocumentUtils.java │ └── main │ │ ├── resources │ │ └── application.yml │ │ └── java │ │ └── com │ │ └── songpapeople │ │ └── hashtagmap │ │ ├── HashTagMapAdminApplication.java │ │ ├── member │ │ ├── service │ │ │ └── dto │ │ │ │ └── LoginRequest.java │ │ └── api │ │ │ └── MemberApiController.java │ │ ├── district │ │ └── service │ │ │ └── dto │ │ │ ├── DistrictSaveDto.java │ │ │ ├── ZoneDeleteDto.java │ │ │ ├── DistrictDeleteDto.java │ │ │ ├── DistrictUpdateDto.java │ │ │ ├── ZoneUpdateDto.java │ │ │ └── ZoneSaveDto.java │ │ ├── kakao │ │ └── service │ │ │ └── dto │ │ │ └── KakaoScheduleToggleDto.java │ │ ├── exception │ │ ├── AdminExceptionStatus.java │ │ └── AdminException.java │ │ ├── controller │ │ └── RouterController.java │ │ ├── taglevel │ │ └── service │ │ │ ├── dto │ │ │ └── TagLevelDto.java │ │ │ ├── TagLevelQueryService.java │ │ │ └── TagLevelCommandService.java │ │ ├── blacklist │ │ └── service │ │ │ └── dto │ │ │ ├── BlackListRequest.java │ │ │ ├── BlackListResponse.java │ │ │ └── AbnormalInstagramDto.java │ │ └── instagram │ │ ├── api │ │ └── InstagramSchedulerApiController.java │ │ └── repository │ │ └── InstagramAdminQueryRepository.java ├── .gitignore ├── front │ ├── babel.config.js │ ├── src │ │ ├── assets │ │ │ ├── chat.png │ │ │ ├── logo.png │ │ │ ├── bright-cow-image.png │ │ │ └── logo.svg │ │ ├── plugins │ │ │ └── vuetify.js │ │ ├── request │ │ │ └── api │ │ │ │ ├── member.js │ │ │ │ ├── tagLevel.js │ │ │ │ ├── blackList.js │ │ │ │ ├── zone.js │ │ │ │ ├── district.js │ │ │ │ └── kakao.js │ │ ├── main.js │ │ ├── views │ │ │ ├── Home.vue │ │ │ └── district │ │ │ │ └── DistrictContainer.vue │ │ ├── store │ │ │ ├── modules │ │ │ │ ├── snackbar.js │ │ │ │ ├── member.js │ │ │ │ ├── modal.js │ │ │ │ └── tagLevel.js │ │ │ └── index.js │ │ ├── utils │ │ │ ├── responseConverter.js │ │ │ └── validator.js │ │ ├── components │ │ │ └── CustomSnackBar.vue │ │ └── router │ │ │ └── index.js │ ├── .gitignore │ ├── vue.config.js │ └── public │ │ └── index.html └── README.md ├── hashtagmap-core ├── src │ ├── test │ │ ├── resources │ │ │ └── empty.txt │ │ └── java │ │ │ └── com │ │ │ └── songpapeople │ │ │ └── hashtagmap │ │ │ ├── HashtagmapCoreTestApplication.java │ │ │ ├── member │ │ │ └── model │ │ │ │ └── AdminMemberTest.java │ │ │ ├── place │ │ │ └── domain │ │ │ │ └── model │ │ │ │ ├── DistrictTest.java │ │ │ │ └── CategoryTest.java │ │ │ ├── blacklist │ │ │ └── domain │ │ │ │ └── model │ │ │ │ └── BlackListTest.java │ │ │ ├── kakao │ │ │ └── schedule │ │ │ │ └── model │ │ │ │ ├── PeriodHistoryTest.java │ │ │ │ └── ScheduleTest.java │ │ │ └── taglevel │ │ │ └── model │ │ │ └── TagLevelTest.java │ └── main │ │ ├── java │ │ └── com │ │ │ └── songpapeople │ │ │ └── hashtagmap │ │ │ ├── event │ │ │ ├── model │ │ │ │ ├── EventStatus.java │ │ │ │ └── EventHistory.java │ │ │ └── repository │ │ │ │ ├── KakaoEventHistoryRepository.java │ │ │ │ └── EventHistoryRepository.java │ │ │ ├── config │ │ │ ├── JpaConfiguration.java │ │ │ ├── vo │ │ │ │ └── Flag.java │ │ │ ├── QueryDslConfiguration.java │ │ │ └── entity │ │ │ │ └── BaseEntity.java │ │ │ ├── taglevel │ │ │ └── repository │ │ │ │ ├── TagLevelRepository.java │ │ │ │ └── TagLevelQueryRepository.java │ │ │ ├── place │ │ │ └── domain │ │ │ │ ├── repository │ │ │ │ ├── PlaceRepositoryCustom.java │ │ │ │ ├── ZoneRepository.java │ │ │ │ ├── PlaceRepository.java │ │ │ │ ├── DistrictRepository.java │ │ │ │ └── ZoneQueryRepository.java │ │ │ │ └── model │ │ │ │ ├── Category.java │ │ │ │ ├── District.java │ │ │ │ └── Location.java │ │ │ ├── exception │ │ │ ├── CoreException.java │ │ │ └── CoreExceptionStatus.java │ │ │ ├── instagram │ │ │ └── domain │ │ │ │ ├── repository │ │ │ │ ├── InstagramRepository.java │ │ │ │ ├── instagramPost │ │ │ │ │ └── InstagramPostRepository.java │ │ │ │ └── InstagramQueryRepository.java │ │ │ │ └── model │ │ │ │ ├── HashtagCounts.java │ │ │ │ └── InstagramPost.java │ │ │ ├── kakao │ │ │ └── schedule │ │ │ │ ├── repository │ │ │ │ ├── ScheduleRepository.java │ │ │ │ └── PeriodHistoryRepository.java │ │ │ │ └── model │ │ │ │ └── PeriodHistory.java │ │ │ ├── member │ │ │ ├── repository │ │ │ │ └── AdminMemberRepository.java │ │ │ └── model │ │ │ │ └── AdminMember.java │ │ │ └── blacklist │ │ │ └── domain │ │ │ └── repsitory │ │ │ └── BlackListRepository.java │ │ └── resources │ │ └── application.yml └── README.md ├── hashtagmap-instagram-crawler ├── src │ ├── main │ │ ├── resources │ │ │ └── empty.txt │ │ └── java │ │ │ └── com │ │ │ └── songpapeople │ │ │ └── hashtagmap │ │ │ ├── exception │ │ │ ├── CrawlerException.java │ │ │ └── CrawlerExceptionStatus.java │ │ │ ├── proxy │ │ │ ├── Proxies.java │ │ │ ├── Proxy.java │ │ │ └── ProxiesFactory.java │ │ │ ├── crawler │ │ │ ├── JsonExplorer.java │ │ │ ├── RegexPattern.java │ │ │ └── InstagramCrawler.java │ │ │ ├── dto │ │ │ ├── PostDto.java │ │ │ ├── CrawlingDto.java │ │ │ └── PostDtos.java │ │ │ └── util │ │ │ ├── PlaceNameParser.java │ │ │ └── PlaceNameType.java │ └── test │ │ └── java │ │ └── com │ │ └── songpapeople │ │ └── hashtagmap │ │ ├── MockDataFactoryTest.java │ │ ├── dto │ │ └── PostDtoTest.java │ │ ├── MockDataFactory.java │ │ ├── proxy │ │ ├── ProxiesFactoryTest.java │ │ ├── ProxyTest.java │ │ └── ProxiesTest.java │ │ ├── crawler │ │ ├── JsonExplorerTest.java │ │ └── InstaCrawlingResultTest.java │ │ └── util │ │ └── PlaceNameParserTest.java ├── build.gradle └── README.md ├── hashtagmap-kakao-scheduler ├── src │ ├── test │ │ ├── resources │ │ │ └── empty.txt │ │ └── java │ │ │ └── com │ │ │ └── songpapeople │ │ │ └── hashtagmap │ │ │ ├── KakaoSchedulerTestApplication.java │ │ │ └── scheduler │ │ │ └── domain │ │ │ └── CronPeriodTest.java │ └── main │ │ ├── resources │ │ └── application.yml │ │ └── java │ │ └── com │ │ └── songpapeople │ │ └── hashtagmap │ │ └── scheduler │ │ ├── exception │ │ ├── KakaoSchedulerException.java │ │ └── KakaoSchedulerExceptionStatus.java │ │ └── config │ │ └── KakaoSchedulerConfig.java └── README.md ├── lombok.config ├── hashtagmap-web ├── front │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── img │ │ │ └── icons │ │ │ │ ├── favicon-16x16.png │ │ │ │ ├── favicon-32x32.png │ │ │ │ ├── menu-bar-icon.png │ │ │ │ ├── apple-touch-icon.png │ │ │ │ ├── mstile-150x150.png │ │ │ │ ├── themiso-icon-192x192.png │ │ │ │ ├── themiso-icon-256x256.png │ │ │ │ ├── themiso-icon-512x512.png │ │ │ │ ├── android-chrome-192x192.png │ │ │ │ ├── android-chrome-512x512.png │ │ │ │ ├── apple-touch-icon-60x60.png │ │ │ │ ├── apple-touch-icon-76x76.png │ │ │ │ ├── apple-touch-icon-120x120.png │ │ │ │ ├── apple-touch-icon-152x152.png │ │ │ │ ├── apple-touch-icon-180x180.png │ │ │ │ ├── msapplication-icon-144x144.png │ │ │ │ ├── android-chrome-maskable-192x192.png │ │ │ │ ├── android-chrome-maskable-512x512.png │ │ │ │ └── safari-pinned-tab.svg │ │ └── themiso-manifest.json │ ├── babel.config.js │ ├── src │ │ ├── assets │ │ │ ├── dot.png │ │ │ ├── close.png │ │ │ ├── No-Image.png │ │ │ ├── NEXON-Gothic.woff │ │ │ ├── NEXON-Gothic-Bold.woff │ │ │ ├── mainpage │ │ │ │ ├── content1.jpeg │ │ │ │ ├── content2.jpeg │ │ │ │ ├── content3.jpeg │ │ │ │ ├── content4.jpeg │ │ │ │ ├── content5.jpeg │ │ │ │ ├── miso_circle.png │ │ │ │ ├── hot_place_ex1.jpeg │ │ │ │ ├── hot_place_ex2.jpeg │ │ │ │ └── hot_place_ex3.jpeg │ │ │ ├── markers │ │ │ │ ├── mapMarker1.png │ │ │ │ ├── mapMarker2.png │ │ │ │ ├── mapMarker3.png │ │ │ │ ├── mapMarker4.png │ │ │ │ └── mapMarker5.png │ │ │ └── css │ │ │ │ ├── app.css │ │ │ │ └── textBalloon.css │ │ ├── utils │ │ │ ├── hashtagCountConverter.js │ │ │ ├── markerImages.js │ │ │ └── constants.js │ │ ├── plugins │ │ │ └── vuetify.js │ │ ├── main.js │ │ ├── App.vue │ │ ├── router │ │ │ └── index.js │ │ ├── request │ │ │ └── kakao.js │ │ ├── store │ │ │ ├── index.js │ │ │ └── modules │ │ │ │ ├── place.js │ │ │ │ ├── category.js │ │ │ │ ├── kakaoMap.js │ │ │ │ └── detailModal.js │ │ ├── components │ │ │ ├── CurrentLocationButton.vue │ │ │ ├── tag-level │ │ │ │ └── TagLevelSelector.vue │ │ │ ├── place │ │ │ │ ├── PlaceCategoryFilter.vue │ │ │ │ ├── PlaceSearchInput.vue │ │ │ │ ├── PlaceCategories.vue │ │ │ │ └── PlaceSearchContainer.vue │ │ │ └── detail-modal │ │ │ │ └── Posts.vue │ │ ├── libs │ │ │ └── navigator │ │ │ │ └── navigator.js │ │ └── registerServiceWorker.js │ ├── .prettierrc │ ├── .gitignore │ ├── README.md │ └── vue.config.js ├── src │ ├── test │ │ ├── java │ │ │ └── com │ │ │ │ └── songpapeople │ │ │ │ └── hashtagmap │ │ │ │ ├── HashTagMapWebApplicationTest.java │ │ │ │ ├── controller │ │ │ │ └── WebRestControllerTest.java │ │ │ │ └── docs │ │ │ │ ├── ApiDocumentUtils.java │ │ │ │ └── ApiDocument.java │ │ └── resources │ │ │ └── org │ │ │ └── springframework │ │ │ └── restdocs │ │ │ └── templates │ │ │ └── request-fields.snippet │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── songpapeople │ │ │ │ └── hashtagmap │ │ │ │ ├── HashTagMapWebApplication.java │ │ │ │ ├── config │ │ │ │ └── StaticResourcePath.java │ │ │ │ ├── service │ │ │ │ ├── InstagramPostQueryService.java │ │ │ │ └── TagLevelQueryService.java │ │ │ │ ├── controller │ │ │ │ ├── RouterController.java │ │ │ │ └── WebRestController.java │ │ │ │ ├── api │ │ │ │ ├── TagLevelApiController.java │ │ │ │ ├── MapApiController.java │ │ │ │ └── InstagramApiController.java │ │ │ │ └── repository │ │ │ │ ├── InstagramPostWebQueryRepository.java │ │ │ │ └── InstagramWebQueryRepository.java │ │ └── resources │ │ │ └── application.yml │ └── docs │ │ └── asciidoc │ │ └── api-guide.adoc └── README.md ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE │ ├── DOCS_ISSUE_TEMPLATE.md │ ├── FEAT_ISSUE_TEMPLATE.md │ ├── REFACTOR_ISSUE_TEMPLATE.md │ └── BUGFIX_ISSUE_TEMPLATE.md ├── hashtagmap-instagram-service ├── src │ ├── main │ │ ├── resources │ │ │ └── application.yml │ │ └── java │ │ │ └── com │ │ │ └── songpapeople │ │ │ └── hashtagmap │ │ │ ├── exception │ │ │ ├── InstagramSchedulerException.java │ │ │ └── InstagramSchedulerExceptionStatus.java │ │ │ ├── service │ │ │ └── ProxySetter.java │ │ │ └── config │ │ │ └── InstagramSchedulerConfiguration.java │ └── test │ │ └── java │ │ └── com │ │ └── songpapeople │ │ └── hashtagmap │ │ ├── InstagramSchedulerApplicationTest.java │ │ ├── MockDataFactory.java │ │ └── service │ │ └── ProxySetterTest.java ├── README.md └── build.gradle ├── hashtagmap-common ├── build.gradle ├── README.md └── src │ └── main │ └── java │ └── com │ └── songpapeople │ └── hashtagmap │ └── exception │ ├── CommonExceptionStatus.java │ └── HashtagMapException.java ├── hashtagmap-batch ├── README.md ├── src │ ├── main │ │ ├── resources │ │ │ └── application.yml │ │ └── java │ │ │ └── com │ │ │ └── songpapeople │ │ │ └── hashtagmap │ │ │ ├── instagram │ │ │ ├── config │ │ │ │ └── BatchConfiguration.java │ │ │ ├── repository │ │ │ │ ├── dto │ │ │ │ │ └── InstagramBatchDto.java │ │ │ │ └── InstagramBatchQueryRepository.java │ │ │ └── processor │ │ │ │ └── InstagramBatchProcessor.java │ │ │ └── HashtagMapBatchApplication.java │ └── test │ │ └── java │ │ └── com │ │ └── songpapeople │ │ └── hashtagmap │ │ └── instagram │ │ └── config │ │ └── TestBatchConfig.java └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitmodules ├── Jenkinsfile ├── hashtagmap-slack ├── src │ └── main │ │ ├── resources │ │ └── application-slack.yml │ │ └── java │ │ └── com │ │ └── songpapeople │ │ └── hashtagmap │ │ └── web │ │ ├── util │ │ └── HttpUtils.java │ │ ├── config │ │ ├── LogPropertiesConfiguration.java │ │ └── LogbackContextConfiguration.java │ │ └── filter │ │ ├── MultiReadHttpServletRequestFilter.java │ │ └── LogbackMdcFilter.java └── build.gradle ├── hashtagmap-event ├── README.md ├── src │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── songpapeople │ │ │ └── hashtagmap │ │ │ └── event │ │ │ ├── message │ │ │ └── Event.java │ │ │ ├── service │ │ │ └── EventService.java │ │ │ ├── process │ │ │ ├── EventBroker.java │ │ │ └── EventBrokerGroup.java │ │ │ ├── util │ │ │ └── LogDecorator.java │ │ │ └── config │ │ │ └── EventConfiguration.java │ └── test │ │ └── java │ │ └── com │ │ └── songpapeople │ │ └── hashtagmap │ │ ├── HashtagmapEventTestApplication.java │ │ └── event │ │ └── util │ │ └── LogDecoratorTest.java └── build.gradle ├── hashtagmap-kakao-api ├── src │ ├── main │ │ ├── resources │ │ │ ├── application.yml │ │ │ └── application-kakao.yml │ │ └── java │ │ │ └── com │ │ │ └── songpapeople │ │ │ └── hashtagmap │ │ │ └── kakaoapi │ │ │ ├── domain │ │ │ ├── exception │ │ │ │ ├── KakaoException.java │ │ │ │ └── KakaoExceptionHandler.java │ │ │ ├── caller │ │ │ │ ├── KakaoSecurityProperties.java │ │ │ │ ├── KakaoProperties.java │ │ │ │ └── KakaoRestTemplateBuilder.java │ │ │ ├── dto │ │ │ │ ├── KakaoPlaceDto.java │ │ │ │ ├── RegionInfo.java │ │ │ │ └── Meta.java │ │ │ └── rect │ │ │ │ ├── location │ │ │ │ ├── Coordinate.java │ │ │ │ └── Latitude.java │ │ │ │ └── RectDivider.java │ │ │ └── exception │ │ │ ├── KakaoApiException.java │ │ │ └── KakaoApiExceptionStatus.java │ └── test │ │ └── java │ │ └── com │ │ └── songpapeople │ │ └── hashtagmap │ │ └── kakaoapi │ │ └── KakaoAPITest.java ├── .gitignore ├── README.md └── build.gradle ├── script ├── README.md ├── web │ ├── publish-over-ssh.sh │ └── web-bind.sh ├── admin │ ├── publish-over-ssh.sh │ └── admin-bind.sh └── nonstopweb │ ├── publish-over-ssh.sh │ └── web-bind.sh ├── settings.gradle └── README.md /hashtagmap-admin/src/test/resources/empty.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hashtagmap-core/src/test/resources/empty.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/main/resources/empty.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /hashtagmap-kakao-scheduler/src/test/resources/empty.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lombok.config: -------------------------------------------------------------------------------- 1 | lombok.addLombokGeneratedAnnotation = true -------------------------------------------------------------------------------- /hashtagmap-admin/.gitignore: -------------------------------------------------------------------------------- 1 | ./src/main/resources/static/** 2 | -------------------------------------------------------------------------------- /hashtagmap-web/front/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | PR 내용: 2 | 3 | 특이 사항 및 트러블 슈팅: 4 | 5 | 프로젝트 외부 설정: -------------------------------------------------------------------------------- /hashtagmap-admin/front/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/cli-plugin-babel/preset"] 3 | }; 4 | -------------------------------------------------------------------------------- /hashtagmap-instagram-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | include: > 4 | db -------------------------------------------------------------------------------- /hashtagmap-web/front/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@vue/cli-plugin-babel/preset"], 3 | }; 4 | -------------------------------------------------------------------------------- /hashtagmap-common/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar { enabled = false } 2 | jar { enabled = true } 3 | 4 | dependencies { 5 | } 6 | -------------------------------------------------------------------------------- /hashtagmap-kakao-scheduler/README.md: -------------------------------------------------------------------------------- 1 | # Kakao Scheduler 2 | 3 | ## 모듈 설명 4 | - Kakao Api를 통한 카카오맵 데이터 갱신 작업의 스케줄을 관리한다. 5 | 6 | -------------------------------------------------------------------------------- /hashtagmap-batch/README.md: -------------------------------------------------------------------------------- 1 | ## 모듈 설명 2 | - Batch Job을 처리하는 모듈 3 | - 인스타그램 해시태그 개수를 크롤링한다. 4 | 5 | ## 기술 스택 6 | - Spring Batch -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /hashtagmap-web/front/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/public/favicon.ico -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/src/assets/dot.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "hashtagmap-secret"] 2 | path = hashtagmap-secret 3 | url = https://github.com/songpa-people/hashtagmap-secret 4 | branch = master -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | node { 2 | stage ('clone') { 3 | checkout scm 4 | } 5 | stage('build') { 6 | sh './gradlew clean build' 7 | } 8 | } -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/assets/chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-admin/front/src/assets/chat.png -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-admin/front/src/assets/logo.png -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/src/assets/close.png -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/No-Image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/src/assets/No-Image.png -------------------------------------------------------------------------------- /hashtagmap-instagram-service/README.md: -------------------------------------------------------------------------------- 1 | ## 모듈 설명 2 | - Instagram 크롤러 모듈을 활용해 크롤링하는 모듈 3 | - 크롤링 할 때 대체 검색어, skip 여부를 설정한다. 4 | 5 | ## 기술 스택 6 | - spring-boot-starter -------------------------------------------------------------------------------- /hashtagmap-web/front/src/utils/hashtagCountConverter.js: -------------------------------------------------------------------------------- 1 | export const convertHashtagCount = hashtagCount => { 2 | return (hashtagCount / 1000).toFixed(1); 3 | }; 4 | -------------------------------------------------------------------------------- /hashtagmap-slack/src/main/resources/application-slack.yml: -------------------------------------------------------------------------------- 1 | log: 2 | level: ERROR 3 | slack: 4 | enabled: true 5 | webHookUrl: localhost:8080 6 | channel: admin -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/NEXON-Gothic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/src/assets/NEXON-Gothic.woff -------------------------------------------------------------------------------- /hashtagmap-web/front/src/plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuetify from "vuetify/lib"; 3 | 4 | Vue.use(Vuetify); 5 | 6 | export default new Vuetify({}); 7 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/assets/bright-cow-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-admin/front/src/assets/bright-cow-image.png -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/plugins/vuetify.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuetify from "vuetify/lib"; 3 | 4 | Vue.use(Vuetify); 5 | 6 | export default new Vuetify({}); 7 | -------------------------------------------------------------------------------- /hashtagmap-common/README.md: -------------------------------------------------------------------------------- 1 | # Common 2 | 3 | ## 모듈 설명 4 | 5 | - 모든 모듈에서 공통으로 사용하는 객체를 제공하는 모듈 6 | - Util 클래스, 커스텀 예외 객체 등 7 | - 외부 의존 관계, 시스템 내 의존 관계를 갖지 않는 순수 Java class 만 정의할 수 있다. -------------------------------------------------------------------------------- /hashtagmap-kakao-scheduler/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | include: > 4 | kakao, 5 | kakao-security, 6 | db 7 | active: local -------------------------------------------------------------------------------- /hashtagmap-web/front/public/img/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/public/img/icons/favicon-16x16.png -------------------------------------------------------------------------------- /hashtagmap-web/front/public/img/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/public/img/icons/favicon-32x32.png -------------------------------------------------------------------------------- /hashtagmap-web/front/public/img/icons/menu-bar-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/public/img/icons/menu-bar-icon.png -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/NEXON-Gothic-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/src/assets/NEXON-Gothic-Bold.woff -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/mainpage/content1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/src/assets/mainpage/content1.jpeg -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/mainpage/content2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/src/assets/mainpage/content2.jpeg -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/mainpage/content3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/src/assets/mainpage/content3.jpeg -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/mainpage/content4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/src/assets/mainpage/content4.jpeg -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/mainpage/content5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/src/assets/mainpage/content5.jpeg -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/markers/mapMarker1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/src/assets/markers/mapMarker1.png -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/markers/mapMarker2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/src/assets/markers/mapMarker2.png -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/markers/mapMarker3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/src/assets/markers/mapMarker3.png -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/markers/mapMarker4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/src/assets/markers/mapMarker4.png -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/markers/mapMarker5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/src/assets/markers/mapMarker5.png -------------------------------------------------------------------------------- /hashtagmap-web/front/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "doubleQuote": true, 3 | "semi": true, 4 | "useTabs": false, 5 | "tabWidth": 2, 6 | "trailingComma": "all", 7 | "printWidth": 80 8 | } -------------------------------------------------------------------------------- /hashtagmap-web/front/public/img/icons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/public/img/icons/apple-touch-icon.png -------------------------------------------------------------------------------- /hashtagmap-web/front/public/img/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/public/img/icons/mstile-150x150.png -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/mainpage/miso_circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/src/assets/mainpage/miso_circle.png -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/mainpage/hot_place_ex1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/src/assets/mainpage/hot_place_ex1.jpeg -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/mainpage/hot_place_ex2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/src/assets/mainpage/hot_place_ex2.jpeg -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/mainpage/hot_place_ex3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/src/assets/mainpage/hot_place_ex3.jpeg -------------------------------------------------------------------------------- /hashtagmap-event/README.md: -------------------------------------------------------------------------------- 1 | # EVENT 2 | 3 | ## 모듈 설명 4 | 5 | - Event를 Producer(생성) - Broker(보관) - Consumer(처리) 구조로 처리가 하기 위한 모듈 6 | - EventType에 새로운 Event를 추가하면 사용가능하다. 7 | - Core 모듈을 의존하여 사용하고 있다. -------------------------------------------------------------------------------- /hashtagmap-web/front/public/img/icons/themiso-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/public/img/icons/themiso-icon-192x192.png -------------------------------------------------------------------------------- /hashtagmap-web/front/public/img/icons/themiso-icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/public/img/icons/themiso-icon-256x256.png -------------------------------------------------------------------------------- /hashtagmap-web/front/public/img/icons/themiso-icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/public/img/icons/themiso-icon-512x512.png -------------------------------------------------------------------------------- /hashtagmap-kakao-api/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | jackson: 3 | property-naming-strategy: SNAKE_CASE 4 | profiles: 5 | include: > 6 | kakao-security, 7 | kakao -------------------------------------------------------------------------------- /hashtagmap-web/front/public/img/icons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/public/img/icons/android-chrome-192x192.png -------------------------------------------------------------------------------- /hashtagmap-web/front/public/img/icons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/public/img/icons/android-chrome-512x512.png -------------------------------------------------------------------------------- /hashtagmap-web/front/public/img/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/public/img/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /hashtagmap-web/front/public/img/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/public/img/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /script/README.md: -------------------------------------------------------------------------------- 1 | - 모든 스크립트는 root 프로젝트 기반으로 작성되어 있음 2 | - `*-bind.sh` 는 `./gradlew clean :module-name:build` 이후 실행되는 쉘 스크립트 3 | - `publish-over-ssh.sh` 는 build 이후 단계 publish over ssh 플러그인에서 사용할 쉘 스크립트 -------------------------------------------------------------------------------- /hashtagmap-web/front/public/img/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/public/img/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /hashtagmap-web/front/public/img/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/public/img/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /hashtagmap-web/front/public/img/icons/apple-touch-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/public/img/icons/apple-touch-icon-180x180.png -------------------------------------------------------------------------------- /hashtagmap-web/front/public/img/icons/msapplication-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/public/img/icons/msapplication-icon-144x144.png -------------------------------------------------------------------------------- /hashtagmap-web/front/public/img/icons/android-chrome-maskable-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/public/img/icons/android-chrome-maskable-192x192.png -------------------------------------------------------------------------------- /hashtagmap-web/front/public/img/icons/android-chrome-maskable-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woowacourse-teams/2020-songpa-people/HEAD/hashtagmap-web/front/public/img/icons/android-chrome-maskable-512x512.png -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/event/model/EventStatus.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.event.model; 2 | 3 | public enum EventStatus { 4 | READY, 5 | FAIL, 6 | SUCCESS 7 | } 8 | -------------------------------------------------------------------------------- /hashtagmap-kakao-api/.gitignore: -------------------------------------------------------------------------------- 1 | # End of https://www.toptal.com/developers/gitignore/api/gradle,java,intellij 2 | /src/main/resources/application-security.properties 3 | /src/main/resources/application-kakao-security.yml 4 | -------------------------------------------------------------------------------- /hashtagmap-event/src/main/java/com/songpapeople/hashtagmap/event/message/Event.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.event.message; 2 | 3 | public interface Event { 4 | void doService(); 5 | 6 | void doFailService(); 7 | } 8 | -------------------------------------------------------------------------------- /hashtagmap-kakao-api/src/main/resources/application-kakao.yml: -------------------------------------------------------------------------------- 1 | kakao: 2 | baseUrl: https://dapi.kakao.com 3 | categoryUrl: /v2/local/search/category.json 4 | categoryGroupCode: category_group_code 5 | maxDocumentCount: 15 6 | maxPageableCount: 45 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/DOCS_ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 문서 분석 Issue 템플릿 3 | about: 문서 분석 템플릿 4 | title: "[DOCS] 제목" 5 | labels: "\U0001F4DADocs" 6 | assignees: '' 7 | 8 | --- 9 | 10 | 분석 대상 : 11 | 12 | 예상 공수 기간 : 13 | 14 | 특이 사항(optional) : 15 | -------------------------------------------------------------------------------- /hashtagmap-core/README.md: -------------------------------------------------------------------------------- 1 | # Core 2 | 3 | ## 모듈 설명 4 | 5 | - DB와 연결되어 있는 도메인 모듈 6 | - 도메인 모델과 Repository가 위치해있다. 7 | 8 | ## 기술 스택 9 | 10 | - Spring Data JPA 11 | - Spring JDBC 12 | - batch update를 List로 넘겨서 효율적으로 처리하기 위함 13 | - QueryDsl 14 | - MariaDB -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEAT_ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 기능 구현 Issue 템플릿 3 | about: 기능 구현 템플릿 4 | title: "[FEAT] 제목" 5 | labels: "\U0001F4A1 Feature" 6 | assignees: '' 7 | 8 | --- 9 | 10 | 기능 구현 대상 : 11 | 12 | 예상 공수 기간 : 13 | 14 | 특이 사항(optional) : 15 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/request/api/member.js: -------------------------------------------------------------------------------- 1 | import { customWrapAxios } from "@/request"; 2 | 3 | const memberApi = { 4 | login(member) { 5 | return customWrapAxios().post("/admin-member/login", member); 6 | } 7 | }; 8 | 9 | export default memberApi; 10 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/REFACTOR_ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 리팩토링 Issue 템플릿 3 | about: 리팩토링 템플릿 4 | title: "[REFACTOR] 제목" 5 | labels: "\U0001F6E0 Refactoring" 6 | assignees: '' 7 | 8 | --- 9 | 10 | 리팩토링 대상 : 11 | 12 | 예상 공수 기간 : 13 | 14 | 특이 사항(optional) : 15 | -------------------------------------------------------------------------------- /hashtagmap-kakao-api/src/test/java/com/songpapeople/hashtagmap/kakaoapi/KakaoAPITest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakaoapi; 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication; 4 | 5 | @SpringBootApplication 6 | public class KakaoAPITest { 7 | } 8 | -------------------------------------------------------------------------------- /hashtagmap-web/front/public/img/icons/safari-pinned-tab.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUGFIX_ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 버그 발견 Issue 템플릿 3 | about: 버그 발견 템플릿 4 | title: "[BUG] 제목" 5 | labels: "\U0001F9E8 Bug" 6 | assignees: '' 7 | 8 | --- 9 | 10 | 버그 발견 일자 : 11 | 12 | 버그 내용 및 위치 : 13 | 14 | 버그 중요도 : 상/중/하 15 | 16 | 특이 사항(optional) : 17 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Jul 07 16:41:53 KST 2020 2 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-all.zip 3 | distributionBase=GRADLE_USER_HOME 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /hashtagmap-core/src/test/java/com/songpapeople/hashtagmap/HashtagmapCoreTestApplication.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap; 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication; 4 | 5 | @SpringBootApplication 6 | public class HashtagmapCoreTestApplication { 7 | } 8 | -------------------------------------------------------------------------------- /hashtagmap-event/src/test/java/com/songpapeople/hashtagmap/HashtagmapEventTestApplication.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap; 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication; 4 | 5 | @SpringBootApplication 6 | public class HashtagmapEventTestApplication { 7 | } 8 | -------------------------------------------------------------------------------- /hashtagmap-kakao-scheduler/src/test/java/com/songpapeople/hashtagmap/KakaoSchedulerTestApplication.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap; 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication; 4 | 5 | @SpringBootApplication 6 | public class KakaoSchedulerTestApplication { 7 | } 8 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/event/repository/KakaoEventHistoryRepository.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.event.repository; 2 | 3 | import com.songpapeople.hashtagmap.event.model.KakaoEventHistory; 4 | 5 | public interface KakaoEventHistoryRepository extends EventHistoryRepository { 6 | } 7 | -------------------------------------------------------------------------------- /hashtagmap-web/src/test/java/com/songpapeople/hashtagmap/HashTagMapWebApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | class HashTagMapWebApplicationTest { 6 | @Test 7 | void main() { 8 | HashTagMapWebApplication.main(new String[]{"--server.port=0"}); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/test/java/com/songpapeople/hashtagmap/HashTagMapAdminApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | class HashTagMapAdminApplicationTest { 6 | 7 | @Test 8 | void main() { 9 | HashTagMapAdminApplication.main(new String[]{"--server.port=0"}); 10 | } 11 | } -------------------------------------------------------------------------------- /hashtagmap-batch/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | include: > 4 | db 5 | jpa: 6 | open-in-view: false 7 | --- 8 | spring: 9 | profiles: local 10 | h2: 11 | console: 12 | enabled: true 13 | --- 14 | spring: 15 | profiles: test 16 | batch: 17 | chunk: 2 18 | --- 19 | batch: 20 | chunk: 50 21 | -------------------------------------------------------------------------------- /hashtagmap-instagram-service/src/test/java/com/songpapeople/hashtagmap/InstagramSchedulerApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap; 2 | 3 | import org.springframework.boot.autoconfigure.SpringBootApplication; 4 | 5 | @SpringBootApplication 6 | public class InstagramSchedulerApplicationTest { 7 | void contextLoads() { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | include: > 4 | kakao, 5 | kakao-security, 6 | db, 7 | slack-admin 8 | mvc: 9 | view: 10 | suffix: .html 11 | jpa: 12 | open-in-view: false 13 | --- 14 | spring: 15 | profiles: local 16 | h2: 17 | console: 18 | enabled: true 19 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/config/JpaConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing; 5 | 6 | @Configuration 7 | @EnableJpaAuditing 8 | public class JpaConfiguration { 9 | } 10 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/taglevel/repository/TagLevelRepository.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.taglevel.repository; 2 | 3 | import com.songpapeople.hashtagmap.taglevel.model.TagLevel; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface TagLevelRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/place/domain/repository/PlaceRepositoryCustom.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.place.domain.repository; 2 | 3 | import com.songpapeople.hashtagmap.place.domain.model.Place; 4 | 5 | import java.util.List; 6 | 7 | public interface PlaceRepositoryCustom { 8 | void updateAndInsert(List places); 9 | } 10 | -------------------------------------------------------------------------------- /hashtagmap-event/src/main/java/com/songpapeople/hashtagmap/event/service/EventService.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.event.service; 2 | 3 | import com.songpapeople.hashtagmap.event.message.Event; 4 | 5 | public interface EventService { 6 | 7 | void provide(E event); 8 | 9 | void fail(E event); 10 | 11 | void collect(E event); 12 | } 13 | -------------------------------------------------------------------------------- /hashtagmap-kakao-api/README.md: -------------------------------------------------------------------------------- 1 | # Kakao Api 2 | 3 | ## 모듈 설명 4 | - Kakao Map Api를 이용하여 카카오맵의 데이터를 제공받는다. 5 | 6 | ## 기술 스택 7 | - Rest Template 8 | 9 | 10 | **사용방법** 11 | 12 | findPlaces(가게 카테고리, 검색할 지역 좌표) 13 | ```java 14 | public List findPlaces(String category, Rect rect) { 15 | return findPlaces(category, rect, DEFAULT_OFFSET); 16 | } 17 | ``` 18 | -------------------------------------------------------------------------------- /hashtagmap-web/front/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | /src/secret/ 24 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/event/repository/EventHistoryRepository.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.event.repository; 2 | 3 | import com.songpapeople.hashtagmap.event.model.EventHistory; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface EventHistoryRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /hashtagmap-kakao-api/src/main/java/com/songpapeople/hashtagmap/kakaoapi/domain/exception/KakaoException.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakaoapi.domain.exception; 2 | 3 | import lombok.AllArgsConstructor; 4 | 5 | @AllArgsConstructor 6 | public class KakaoException extends RuntimeException { 7 | private final int statusCode; 8 | private final String messgae; 9 | 10 | 11 | } 12 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/exception/CoreException.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.exception; 2 | 3 | public class CoreException extends HashtagMapException { 4 | 5 | public CoreException(CoreExceptionStatus coreExceptionStatus, String detailMessage) { 6 | super(coreExceptionStatus.getMessage(), coreExceptionStatus.getCode(), detailMessage); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/instagram/domain/repository/InstagramRepository.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.instagram.domain.repository; 2 | 3 | import com.songpapeople.hashtagmap.instagram.domain.model.Instagram; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | public interface InstagramRepository extends JpaRepository { 7 | } 8 | -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/main/java/com/songpapeople/hashtagmap/exception/CrawlerException.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.exception; 2 | 3 | public class CrawlerException extends HashtagMapException { 4 | 5 | public CrawlerException(CrawlerExceptionStatus crawlerExceptionStatus) { 6 | super(crawlerExceptionStatus.getMessage(), crawlerExceptionStatus.getStatusCode()); 7 | } 8 | } -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./App.vue"; 3 | import router from "./router"; 4 | import store from "./store"; 5 | import vuetify from "./plugins/vuetify"; 6 | import axios from "axios"; 7 | 8 | Vue.config.productionTip = false; 9 | Vue.prototype.axios = axios; 10 | 11 | new Vue({ 12 | router, 13 | store, 14 | vuetify, 15 | render: h => h(App) 16 | }).$mount("#app"); 17 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/request/api/tagLevel.js: -------------------------------------------------------------------------------- 1 | import { customWrapAxios } from "@/request"; 2 | 3 | const tagLevelApi = { 4 | findAll() { 5 | return customWrapAxios().get("/tag-levels", { 6 | headers: { 7 | "Content-Type": "application/json" 8 | } 9 | }); 10 | }, 11 | update() { 12 | return customWrapAxios().put("/tag-levels"); 13 | } 14 | }; 15 | 16 | export default tagLevelApi; 17 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'hashtagmap' 2 | include 'hashtagmap-web' 3 | include 'hashtagmap-core' 4 | include 'hashtagmap-instagram-crawler' 5 | include 'hashtagmap-kakao-api' 6 | include 'hashtagmap-admin' 7 | include 'hashtagmap-kakao-scheduler' 8 | include 'hashtagmap-instagram-service' 9 | include 'hashtagmap-common' 10 | include 'hashtagmap-event' 11 | include 'hashtagmap-batch' 12 | include 'hashtagmap-slack' 13 | 14 | -------------------------------------------------------------------------------- /hashtagmap-slack/src/main/java/com/songpapeople/hashtagmap/web/util/HttpUtils.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.web.util; 2 | 3 | import ch.qos.logback.core.util.ContextUtil; 4 | 5 | public class HttpUtils { 6 | public static String getHostName() { 7 | try { 8 | return ContextUtil.getLocalHostName(); 9 | } catch (Exception e) { 10 | return "UNKNOWN"; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /hashtagmap-batch/build.gradle: -------------------------------------------------------------------------------- 1 | version = '2.0.0' 2 | 3 | dependencies { 4 | implementation project(path: ':hashtagmap-instagram-service', configuration: 'default') 5 | implementation 'org.springframework.boot:spring-boot-starter-batch' 6 | testImplementation 'org.springframework.batch:spring-batch-test' 7 | } 8 | 9 | sonarqube { 10 | properties { 11 | property "sonar.exclusions", "**/*Test*.*, **/Q*.java" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /hashtagmap-web/front/README.md: -------------------------------------------------------------------------------- 1 | # front 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /hashtagmap-slack/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar { enabled = false } 2 | jar { enabled = true } 3 | 4 | dependencies { 5 | implementation 'org.springframework.boot:spring-boot-starter-web' 6 | implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.6' 7 | implementation 'net.gpedro.integrations.slack:slack-webhook:1.4.0' 8 | 9 | compileOnly 'org.projectlombok:lombok' 10 | annotationProcessor 'org.projectlombok:lombok' 11 | } 12 | -------------------------------------------------------------------------------- /hashtagmap-web/src/main/java/com/songpapeople/hashtagmap/HashTagMapWebApplication.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class HashTagMapWebApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(HashTagMapWebApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/place/domain/repository/ZoneRepository.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.place.domain.repository; 2 | 3 | import com.songpapeople.hashtagmap.place.domain.model.Zone; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Collection; 7 | 8 | public interface ZoneRepository extends JpaRepository { 9 | void deleteAllByIdIn(Collection zoneIds); 10 | } 11 | -------------------------------------------------------------------------------- /hashtagmap-web/README.md: -------------------------------------------------------------------------------- 1 | # Web 2 | 3 | ## 모듈 설명 4 | 5 | - 사용자를 위한 기능을 제공하는 어플리케이션 모듈. 6 | - hashtagmap-core 모듈을 사용하여 데이터를 조회한다. 7 | - hashtagmap-common 모듈에서 api 응답 객체를 사용한다. 8 | 9 | ## 기술 스택 10 | 11 | - Vue.js 12 | - PWA를 이용해 모바일에서도 서비스 활용성 증가 13 | - spring-boot-starter-web 14 | - spring-webmvc 프레임워크를 활용해 Model-View-Controller 구조 설계 15 | - Spring REST Docs를 활용한 api 문서화 16 | - JUnit5 17 | - MockMvc를 활용한 Controller Layer 테스트 작성 18 | - REST-assured를 활용한 End-to-End 테스트 작성 -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/HashTagMapAdminApplication.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class HashTagMapAdminApplication { 8 | public static void main(String[] args) { 9 | SpringApplication.run(HashTagMapAdminApplication.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /hashtagmap-instagram-service/src/main/java/com/songpapeople/hashtagmap/exception/InstagramSchedulerException.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.exception; 2 | 3 | public class InstagramSchedulerException extends HashtagMapException { 4 | 5 | public InstagramSchedulerException(InstagramSchedulerExceptionStatus instagramSchedulerExceptionStatus) { 6 | super(instagramSchedulerExceptionStatus.getMessage(), instagramSchedulerExceptionStatus.getStatusCode()); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/kakao/schedule/repository/ScheduleRepository.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakao.schedule.repository; 2 | 3 | import com.songpapeople.hashtagmap.kakao.schedule.model.Schedule; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | public interface ScheduleRepository extends JpaRepository { 9 | Optional findByName(String name); 10 | } 11 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/member/repository/AdminMemberRepository.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.member.repository; 2 | 3 | import com.songpapeople.hashtagmap.member.model.AdminMember; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | public interface AdminMemberRepository extends JpaRepository { 9 | Optional findByNickName(String nickName); 10 | } 11 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/blacklist/domain/repsitory/BlackListRepository.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.blacklist.domain.repsitory; 2 | 3 | import com.songpapeople.hashtagmap.blacklist.domain.model.BlackList; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | public interface BlackListRepository extends JpaRepository { 9 | Optional findByKakaoId(String kakaoId); 10 | } 11 | -------------------------------------------------------------------------------- /hashtagmap-slack/src/main/java/com/songpapeople/hashtagmap/web/config/LogPropertiesConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.web.config; 2 | 3 | import com.songpapeople.hashtagmap.web.property.LogProperties; 4 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | @EnableConfigurationProperties(value = LogProperties.class) 9 | public class LogPropertiesConfiguration { 10 | } 11 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/kakao/schedule/repository/PeriodHistoryRepository.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakao.schedule.repository; 2 | 3 | import com.songpapeople.hashtagmap.kakao.schedule.model.PeriodHistory; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Optional; 7 | 8 | public interface PeriodHistoryRepository extends JpaRepository { 9 | Optional findTopByOrderByChangedDateDesc(); 10 | } -------------------------------------------------------------------------------- /script/web/publish-over-ssh.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | DEPLOY_USER=ubuntu 6 | echo "DEPLOY USER = $DEPLOY_USER" 7 | 8 | ZIP_DIR='/home/'$DEPLOY_USER'/app/zip' 9 | sudo mkdir -p $ZIP_DIR 10 | echo "ZIP_DIR = $ZIP_DIR" 11 | 12 | sudo mv '/home/'$DEPLOY_USER'/hashtagmap-web.tar' $ZIP_DIR/ 13 | echo "tar moved to $ZIP_DIR" 14 | 15 | sudo tar -xvf $ZIP_DIR/hashtagmap-web.tar -C $ZIP_DIR/ 16 | echo "> done untar" 17 | 18 | echo "> run deploy.sh" 19 | sudo chmod +x $ZIP_DIR/deploy.sh 20 | sudo sh $ZIP_DIR/deploy.sh -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 17 | 18 | 25 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/member/service/dto/LoginRequest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.member.service.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | 6 | @NoArgsConstructor 7 | @Getter 8 | public class LoginRequest { 9 | private String nickName; 10 | private String password; 11 | 12 | public LoginRequest(String nickName, String password) { 13 | this.nickName = nickName; 14 | this.password = password; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /hashtagmap-batch/src/main/java/com/songpapeople/hashtagmap/instagram/config/BatchConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.instagram.config; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Setter 9 | @Getter 10 | @ConfigurationProperties(prefix = "batch") 11 | @Configuration 12 | public class BatchConfiguration { 13 | private int chunk; 14 | } 15 | -------------------------------------------------------------------------------- /hashtagmap-kakao-api/src/main/java/com/songpapeople/hashtagmap/kakaoapi/exception/KakaoApiException.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakaoapi.exception; 2 | 3 | import com.songpapeople.hashtagmap.exception.HashtagMapException; 4 | 5 | public class KakaoApiException extends HashtagMapException { 6 | public KakaoApiException(KakaoApiExceptionStatus kakaoApiExceptionStatus, String detailMessgae) { 7 | super(kakaoApiExceptionStatus.getMessage(), kakaoApiExceptionStatus.getCode(), detailMessgae); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /script/admin/publish-over-ssh.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | DEPLOY_USER=ubuntu 6 | echo "DEPLOY USER = $DEPLOY_USER" 7 | 8 | ZIP_DIR='/home/'$DEPLOY_USER'/app/zip' 9 | sudo mkdir -p $ZIP_DIR 10 | echo "ZIP_DIR = $ZIP_DIR" 11 | 12 | sudo mv '/home/'$DEPLOY_USER'/hashtagmap-admin.tar' $ZIP_DIR/ 13 | echo "tar moved to $ZIP_DIR" 14 | 15 | sudo tar -xvf $ZIP_DIR/hashtagmap-admin.tar -C $ZIP_DIR/ 16 | echo "> done untar" 17 | 18 | echo "> run deploy.sh" 19 | sudo chmod +x $ZIP_DIR/deploy.sh 20 | sudo sh $ZIP_DIR/deploy.sh -------------------------------------------------------------------------------- /hashtagmap-batch/src/test/java/com/songpapeople/hashtagmap/instagram/config/TestBatchConfig.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.instagram.config; 2 | 3 | import org.springframework.batch.test.JobLauncherTestUtils; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | public class TestBatchConfig { 9 | @Bean 10 | public JobLauncherTestUtils myTestJobLauncher() { 11 | return new JobLauncherTestUtils(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/config/vo/Flag.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.config.vo; 2 | 3 | public enum Flag { 4 | Y(true), 5 | N(false); 6 | 7 | private final boolean flag; 8 | 9 | Flag(final boolean flag) { 10 | this.flag = flag; 11 | } 12 | 13 | public boolean isYes() { 14 | return flag; 15 | } 16 | 17 | public Flag toggle() { 18 | if (this == Y) { 19 | return N; 20 | } 21 | return Y; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import App from "./App.vue"; 3 | import "./registerServiceWorker"; 4 | import router from "./router"; 5 | import store from "./store"; 6 | import KakaoMap from "@/plugins/KakaoMap"; 7 | import vuetify from "./plugins/vuetify"; 8 | import axios from "axios"; 9 | 10 | Vue.config.productionTip = false; 11 | Vue.prototype.axios = axios; 12 | Vue.use(KakaoMap); 13 | 14 | new Vue({ 15 | router, 16 | store, 17 | vuetify, 18 | render: h => h(App), 19 | }).$mount("#app"); 20 | -------------------------------------------------------------------------------- /hashtagmap-web/src/main/java/com/songpapeople/hashtagmap/config/StaticResourcePath.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.config; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.RequiredArgsConstructor; 6 | 7 | @Getter 8 | @RequiredArgsConstructor(access = AccessLevel.PACKAGE) 9 | public enum StaticResourcePath { 10 | JS("/js/**", "js"), 11 | CSS("/css/**", "css"), 12 | IMG("/img/**", "img"); 13 | 14 | private final String path; 15 | private final String directory; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/main/java/com/songpapeople/hashtagmap/proxy/Proxies.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.proxy; 2 | 3 | import java.util.List; 4 | 5 | public class Proxies { 6 | private final List proxies; 7 | 8 | public Proxies(List proxies) { 9 | this.proxies = proxies; 10 | } 11 | 12 | public void setHostAndPort(int random) { 13 | proxies.get(random).setHostAndPort(); 14 | } 15 | 16 | public int size() { 17 | return proxies.size(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/main/java/com/songpapeople/hashtagmap/proxy/Proxy.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.proxy; 2 | 3 | public class Proxy { 4 | private final String host; 5 | private final String port; 6 | 7 | public Proxy(String host, String port) { 8 | this.host = host; 9 | this.port = port; 10 | } 11 | 12 | public void setHostAndPort() { 13 | System.setProperty("http.proxyHost", this.host); 14 | System.setProperty("http.proxyPort", this.port); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /hashtagmap-kakao-api/src/main/java/com/songpapeople/hashtagmap/kakaoapi/domain/caller/KakaoSecurityProperties.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakaoapi.domain.caller; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Getter 9 | @Setter 10 | @ConfigurationProperties(prefix = "security") 11 | @Component 12 | public class KakaoSecurityProperties { 13 | private String key; 14 | } 15 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/views/district/DistrictContainer.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /hashtagmap-kakao-scheduler/src/main/java/com/songpapeople/hashtagmap/scheduler/exception/KakaoSchedulerException.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.scheduler.exception; 2 | 3 | import com.songpapeople.hashtagmap.exception.HashtagMapException; 4 | 5 | public class KakaoSchedulerException extends HashtagMapException { 6 | public KakaoSchedulerException(final KakaoSchedulerExceptionStatus kakaoSchedulerExceptionStatus) { 7 | super(kakaoSchedulerExceptionStatus.getMessage(), kakaoSchedulerExceptionStatus.getCode()); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueRouter from "vue-router"; 3 | import KakaoMap from "@/views/KakaoMap.vue"; 4 | 5 | Vue.use(VueRouter); 6 | 7 | const routes = [ 8 | { 9 | path: "/", 10 | name: "KakaoMap", 11 | component: KakaoMap, 12 | children: [ 13 | { 14 | path: "detail", 15 | }, 16 | ], 17 | }, 18 | ]; 19 | 20 | const router = new VueRouter({ 21 | mode: "history", 22 | base: process.env.BASE_URL, 23 | routes, 24 | }); 25 | 26 | export default router; 27 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/place/domain/repository/PlaceRepository.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.place.domain.repository; 2 | 3 | import com.songpapeople.hashtagmap.place.domain.model.Place; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.List; 7 | 8 | public interface PlaceRepository extends JpaRepository, PlaceRepositoryCustom { 9 | List findByPlaceName(String name); 10 | 11 | List findAllByKakaoIdIn(List kakaoIds); 12 | } 13 | -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/main/java/com/songpapeople/hashtagmap/crawler/JsonExplorer.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.crawler; 2 | 3 | import com.google.gson.JsonElement; 4 | 5 | class JsonExplorer { 6 | public static final String PARENT_KEY = "node"; 7 | 8 | private JsonExplorer() { 9 | } 10 | 11 | public static String findByKey(JsonElement edge, String key) { 12 | return edge.getAsJsonObject() 13 | .get(PARENT_KEY).getAsJsonObject() 14 | .get(key).getAsString(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /hashtagmap-admin/README.md: -------------------------------------------------------------------------------- 1 | # ADMIN 2 | 3 | ## 모듈 설명 4 | 5 | - 어플리케이션의 관리 페이지 API역할을 하는 모듈 6 | - front 패키지에서 node run build를 하면 필요한 정적 파일이 static으로 이동한다. 7 | - 데이터 수집 (지역구, 좌표, 가게, 인스타그램 해시태그) 에 대한 관리가 가능하다. 8 | - 인증된 사람(관리자)만 접근 할 수 있다. 9 | - core, kakao-scheduler, instagram-service, common 모듈을 의존하여 사용하고있다. 10 | 11 | ## 기술 스택 12 | 13 | - Spring-boot-starter-web 14 | - Spring-webmvc 프레임워크를 활용해 Model-View-Controller 구조 설계 15 | - Spring REST Docs를 활용한 api 문서화 16 | - MockMvc를 활용한 Controller Layer 테스트 작성 17 | - REST-assured를 활용한 End-to-End 테스트 작성 18 | - Vue.js 19 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | Artboard 46 2 | -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/main/java/com/songpapeople/hashtagmap/dto/PostDto.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.dto; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class PostDto { 7 | public static final String POST_URL_FORMAT = "https://www.instagram.com/p/%s"; 8 | 9 | private final String postUrl; 10 | private final String imageUrl; 11 | 12 | public PostDto(String postUrlKey, String imageUrl) { 13 | this.postUrl = String.format(POST_URL_FORMAT, postUrlKey); 14 | this.imageUrl = imageUrl; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/test/java/com/songpapeople/hashtagmap/MockDataFactoryTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.io.IOException; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | 10 | class MockDataFactoryTest { 11 | 12 | @DisplayName("2020-9월 기준 insta 크롤링 성공 mock 데이터") 13 | @Test 14 | void createBody() throws IOException { 15 | assertThat(MockDataFactory.createBody()).isNotBlank(); 16 | } 17 | } -------------------------------------------------------------------------------- /hashtagmap-batch/src/main/java/com/songpapeople/hashtagmap/instagram/repository/dto/InstagramBatchDto.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.instagram.repository.dto; 2 | 3 | import com.querydsl.core.annotations.QueryProjection; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | public class InstagramBatchDto { 8 | private final Long instagramId; 9 | private final Long placeId; 10 | 11 | @QueryProjection 12 | public InstagramBatchDto(Long instagramId, Long placeId) { 13 | this.instagramId = instagramId; 14 | this.placeId = placeId; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/store/modules/snackbar.js: -------------------------------------------------------------------------------- 1 | export default { 2 | namespaced: true, 3 | 4 | state: { 5 | snackbar: { show: false, type: "", message: "", code: "" } 6 | }, 7 | 8 | getters: { 9 | getSnackbar(state) { 10 | return state.snackbar; 11 | } 12 | }, 13 | 14 | mutations: { 15 | SHOW_SNACKBAR(state, input) { 16 | state.snackbar.show = true; 17 | state.snackbar.type = input.type ? input.type : "info"; 18 | state.snackbar.message = input.message; 19 | state.snackbar.code = input.code; 20 | } 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/place/domain/repository/DistrictRepository.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.place.domain.repository; 2 | 3 | import com.songpapeople.hashtagmap.place.domain.model.District; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.Collection; 7 | import java.util.Optional; 8 | 9 | public interface DistrictRepository extends JpaRepository { 10 | Optional findByDistrictName(final String districtName); 11 | 12 | void deleteAllByIdIn(Collection ids); 13 | } 14 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/district/service/dto/DistrictSaveDto.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.district.service.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.validation.constraints.NotBlank; 8 | 9 | @Getter 10 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 11 | public class DistrictSaveDto { 12 | @NotBlank 13 | private String districtName; 14 | 15 | public DistrictSaveDto(final String districtName) { 16 | this.districtName = districtName; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/test/java/com/songpapeople/hashtagmap/controller/RouterControllerJavaTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.controller; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | class RouterControllerJavaTest { 9 | 10 | @DisplayName("errorPath 반환") 11 | @Test 12 | void getErrorPathTest() { 13 | RouterController routerController = new RouterController(); 14 | 15 | assertThat(routerController.getErrorPath()).isEqualTo("/error"); 16 | } 17 | } -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/district/service/dto/ZoneDeleteDto.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.district.service.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.validation.constraints.NotEmpty; 8 | import java.util.List; 9 | 10 | @Getter 11 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 12 | public class ZoneDeleteDto { 13 | @NotEmpty 14 | private List zoneIds; 15 | 16 | public ZoneDeleteDto(final List zoneIds) { 17 | this.zoneIds = zoneIds; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /hashtagmap-instagram-service/src/main/java/com/songpapeople/hashtagmap/service/ProxySetter.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.service; 2 | 3 | import com.songpapeople.hashtagmap.proxy.Proxies; 4 | 5 | import java.util.Random; 6 | 7 | public class ProxySetter { 8 | private final Proxies proxies; 9 | private final Random random = new Random(); 10 | 11 | public ProxySetter(Proxies proxies) { 12 | this.proxies = proxies; 13 | } 14 | 15 | public void setProxy() { 16 | int size = proxies.size(); 17 | proxies.setHostAndPort(random.nextInt(size)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /hashtagmap-core/src/test/java/com/songpapeople/hashtagmap/member/model/AdminMemberTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.member.model; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 7 | 8 | class AdminMemberTest { 9 | 10 | @DisplayName("패스워드가 일치하지 않는 경우") 11 | @Test 12 | void isNotMatchPassword() { 13 | AdminMember adminMember = new AdminMember("nickName", "password"); 14 | 15 | assertThat(adminMember.isNotMatchPassword("fail_password")).isTrue(); 16 | } 17 | } -------------------------------------------------------------------------------- /hashtagmap-instagram-service/src/main/java/com/songpapeople/hashtagmap/config/InstagramSchedulerConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.config; 2 | 3 | import com.songpapeople.hashtagmap.crawler.Crawler; 4 | import com.songpapeople.hashtagmap.crawler.InstagramCrawler; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | public class InstagramSchedulerConfiguration { 10 | @Bean 11 | public InstagramCrawler instagramCrawler() { 12 | return new InstagramCrawler(new Crawler()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /hashtagmap-web/src/test/resources/org/springframework/restdocs/templates/request-fields.snippet: -------------------------------------------------------------------------------- 1 | =====Request Fields 2 | |=== 3 | |필드명|타입|필수값여부|양식|설명 4 | 5 | 6 | {{#fields}} 7 | |{{#tableCellContent}}`+{{path}}+`{{/tableCellContent}} 8 | |{{#tableCellContent}}`+{{type}}+`{{/tableCellContent}} 9 | |{{#tableCellContent}}{{^optional}}true{{/optional}}{{/tableCellContent}} 10 | |{{#tableCellContent}}{{#format}}{{.}}{{/format}}{{/tableCellContent}} 11 | |{{#tableCellContent}}{{description}}{{/tableCellContent}} 12 | 13 | {{/fields}} 14 | 15 | |=== -------------------------------------------------------------------------------- /hashtagmap-kakao-api/src/main/java/com/songpapeople/hashtagmap/kakaoapi/exception/KakaoApiExceptionStatus.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakaoapi.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum KakaoApiExceptionStatus { 7 | INVALID_LATITUDE("KAKAO_API_1001", "부정확한 위도 값입니다."), 8 | INVALID_LONGITUDE("KAKAO_API_1002", "부정확한 경도 값입니다."); 9 | 10 | private final String code; 11 | private final String message; 12 | 13 | KakaoApiExceptionStatus(final String code, final String message) { 14 | this.code = code; 15 | this.message = message; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /hashtagmap-event/src/main/java/com/songpapeople/hashtagmap/event/process/EventBroker.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.event.process; 2 | 3 | import com.songpapeople.hashtagmap.event.message.Event; 4 | 5 | import java.util.concurrent.BlockingQueue; 6 | import java.util.concurrent.LinkedBlockingQueue; 7 | 8 | public class EventBroker { 9 | private final BlockingQueue eventQueue = new LinkedBlockingQueue<>(); 10 | 11 | public void push(E event) { 12 | eventQueue.add(event); 13 | } 14 | 15 | public E poll() { 16 | return eventQueue.poll(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const port = 3000; 3 | const proxyPort = 8080; 4 | 5 | module.exports = { 6 | outputDir: path.resolve("__dirname", "../../src/main/resources/static"), 7 | transpileDependencies: ["vuetify"], 8 | configureWebpack: { 9 | resolve: { 10 | alias: { 11 | "@": path.join(__dirname, "src/") 12 | } 13 | } 14 | }, 15 | devServer: { 16 | port: port, 17 | proxy: { 18 | "/": { 19 | target: `http://localhost:${proxyPort}`, 20 | ws: true, 21 | changeOrigin: true 22 | } 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /hashtagmap-batch/src/main/java/com/songpapeople/hashtagmap/HashtagMapBatchApplication.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap; 2 | 3 | import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | 7 | @EnableBatchProcessing 8 | @SpringBootApplication 9 | public class HashtagMapBatchApplication { 10 | 11 | public static void main(String[] args) { 12 | System.exit(SpringApplication.exit(SpringApplication.run(HashtagMapBatchApplication.class, args))); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/exception/CoreExceptionStatus.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum CoreExceptionStatus { 7 | INVALID_LATITUDE("CORE_1001", "부정확한 위도 값입니다."), 8 | INVALID_LONGITUDE("CORE_1002", "부정확한 경도 값입니다."), 9 | INVALID_TAG_LEVEL("CORE_1003", "해시 태그 값이 잘못되었습니다."); 10 | 11 | private final String code; 12 | private final String message; 13 | 14 | CoreExceptionStatus(final String code, final String message) { 15 | this.code = code; 16 | this.message = message; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /hashtagmap-web/front/vue.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const port = 8080; 3 | const proxyPort = 9000; 4 | 5 | module.exports = { 6 | outputDir: path.resolve("__dirname", "../../src/main/resources/static"), 7 | transpileDependencies: ["vuetify"], 8 | devServer: { 9 | port: port, 10 | proxy: { 11 | "/": { 12 | target: `http://localhost:${proxyPort}`, 13 | ws: true, 14 | changeOrigin: true, 15 | }, 16 | }, 17 | }, 18 | configureWebpack: { 19 | resolve: { 20 | alias: { 21 | "@": path.join(__dirname, "src/"), 22 | }, 23 | }, 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/district/service/dto/DistrictDeleteDto.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.district.service.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.validation.constraints.NotEmpty; 8 | import java.util.List; 9 | 10 | @Getter 11 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 12 | public class DistrictDeleteDto { 13 | @NotEmpty 14 | private List districtIds; 15 | 16 | public DistrictDeleteDto(final List districtIds) { 17 | this.districtIds = districtIds; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/kakao/service/dto/KakaoScheduleToggleDto.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakao.service.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.validation.constraints.NotBlank; 8 | import javax.validation.constraints.NotNull; 9 | 10 | @Getter 11 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 12 | public class KakaoScheduleToggleDto { 13 | @NotBlank 14 | private String name; 15 | 16 | public KakaoScheduleToggleDto(@NotNull final String name) { 17 | this.name = name; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/request/kakao.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { KAKAO_REST_KEY } from "@/secret"; 3 | 4 | const kakaoSearch = { 5 | getKeywordSearch(keyword) { 6 | return axios.get("https://dapi.kakao.com/v2/local/search/keyword.json", { 7 | headers: { 8 | Authorization: "KakaoAK " + KAKAO_REST_KEY, 9 | }, 10 | params: { 11 | query: keyword, 12 | x: 126.99094, 13 | y: 37.550929, 14 | rect: 15 | "126.75578831035362,37.41847533960485,127.2251487382762,37.70625487247741", 16 | }, 17 | }); 18 | }, 19 | }; 20 | 21 | export default kakaoSearch; 22 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/exception/AdminExceptionStatus.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum AdminExceptionStatus { 7 | NOT_FOUND_SCHEDULER("ADMIN_1000", "스케쥴러가 존재하지 않습니다."), 8 | NOT_FOUNT_NICK_NAME("ADMIN_2000", "존재하지 않는 아이디입니다."), 9 | WRONG_PASSWORD("ADMIN_2001", "비밀번호가 일치하지 않습니다."); 10 | 11 | private final String code; 12 | private final String message; 13 | 14 | AdminExceptionStatus(final String code, final String message) { 15 | this.code = code; 16 | this.message = message; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /hashtagmap-web/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | mvc: 3 | view: 4 | suffix: .html 5 | profiles: 6 | include: > 7 | db, 8 | slack-web 9 | resources: 10 | chain: 11 | strategy: 12 | content: 13 | enabled: true 14 | server: 15 | port: 9000 16 | compression: 17 | enabled: true 18 | mime-types: text/html, text/css, text/javascript, application/javascript, application/json, image/png, image/jpeg 19 | min-response-size: 2048 20 | --- 21 | spring.profiles: set1 22 | 23 | server: 24 | port: 8081 25 | --- 26 | spring.profiles: set2 27 | 28 | server: 29 | port: 8082 30 | -------------------------------------------------------------------------------- /hashtagmap-core/src/test/java/com/songpapeople/hashtagmap/place/domain/model/DistrictTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.place.domain.model; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 7 | 8 | class DistrictTest { 9 | 10 | @DisplayName("District 변경 테스트") 11 | @Test 12 | void update() { 13 | District district = new District("서울시 송파구"); 14 | String replace = "서울시 강남구"; 15 | 16 | district.update(replace); 17 | 18 | assertThat(district.getDistrictName()).isEqualTo(replace); 19 | } 20 | } -------------------------------------------------------------------------------- /hashtagmap-kakao-api/src/main/java/com/songpapeople/hashtagmap/kakaoapi/domain/caller/KakaoProperties.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakaoapi.domain.caller; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Getter 9 | @Setter 10 | @ConfigurationProperties(prefix = "kakao") 11 | @Component 12 | public class KakaoProperties { 13 | private String baseUrl; 14 | private String categoryUrl; 15 | private String categoryGroupCode; 16 | private int maxDocumentCount; 17 | private int maxPageableCount; 18 | } 19 | -------------------------------------------------------------------------------- /hashtagmap-web/front/public/themiso-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "당신이 몰랐던 그 곳, 미소", 3 | "short_name": "미소", 4 | "theme_color": "#C485AD", 5 | "background_color": "#FFFFFF", 6 | "display": "fullscreen", 7 | "icons": [ 8 | { 9 | "src": "./img/icons/themiso-icon-192x192.png", 10 | "sizes": "192x192", 11 | "type": "image/png" 12 | }, 13 | { 14 | "src": "./img/icons/themiso-icon-256x256.png", 15 | "sizes": "256x256", 16 | "type": "image/png" 17 | }, 18 | { 19 | "src": "./img/icons/themiso-icon-512x512.png", 20 | "sizes": "512x512", 21 | "type": "image/png" 22 | } 23 | ], 24 | "start_url": "." 25 | } 26 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/config/QueryDslConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.config; 2 | 3 | import com.querydsl.jpa.impl.JPAQueryFactory; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | import javax.persistence.EntityManager; 8 | import javax.persistence.PersistenceContext; 9 | 10 | @Configuration 11 | public class QueryDslConfiguration { 12 | @PersistenceContext 13 | private EntityManager entityManager; 14 | 15 | @Bean 16 | public JPAQueryFactory jpaQueryFactory() { 17 | return new JPAQueryFactory(entityManager); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/instagram/domain/model/HashtagCounts.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.instagram.domain.model; 2 | 3 | import java.util.List; 4 | 5 | public class HashtagCounts { 6 | private final List hashtagCounts; 7 | 8 | public HashtagCounts(List hashtagCounts) { 9 | this.hashtagCounts = hashtagCounts; 10 | } 11 | 12 | public Long get(int index) { 13 | return hashtagCounts.get(index); 14 | } 15 | 16 | public int getSize() { 17 | return hashtagCounts.size(); 18 | } 19 | 20 | public int getLastIndex() { 21 | return hashtagCounts.size() - 1; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/instagram/domain/repository/instagramPost/InstagramPostRepository.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.instagram.domain.repository.instagramPost; 2 | 3 | import com.songpapeople.hashtagmap.instagram.domain.model.InstagramPost; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.transaction.annotation.Transactional; 6 | 7 | import java.util.List; 8 | 9 | public interface InstagramPostRepository extends JpaRepository { 10 | @Transactional 11 | void deleteByInstagramId(Long instagramId); 12 | 13 | List findAllByInstagramId(Long id); 14 | } 15 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | import kakaoMap from "./modules/kakaoMap"; 4 | import detailModal from "./modules/detailModal"; 5 | import place from "./modules/place"; 6 | import tagLevel from "./modules/tagLevel"; 7 | import category from "./modules/category"; 8 | import mapOverlay from "./modules/mapOverlay"; 9 | 10 | Vue.use(Vuex); 11 | 12 | export default new Vuex.Store({ 13 | state: {}, 14 | 15 | getters: {}, 16 | 17 | mutations: {}, 18 | 19 | actions: {}, 20 | 21 | modules: { 22 | kakaoMap, 23 | detailModal, 24 | place, 25 | tagLevel, 26 | category, 27 | mapOverlay, 28 | }, 29 | }); 30 | -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/main/java/com/songpapeople/hashtagmap/util/PlaceNameParser.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.util; 2 | 3 | import java.util.Optional; 4 | 5 | public class PlaceNameParser { 6 | 7 | private PlaceNameParser() { 8 | } 9 | 10 | public static String parsePlaceName(String placeName) { 11 | String parsedPlaceName = placeName.replaceAll(" ", ""); 12 | Optional optionalPlaceNameType = PlaceNameType.find(parsedPlaceName); 13 | return optionalPlaceNameType 14 | .map(placeNameType -> placeNameType.parsePlaceName(parsedPlaceName)) 15 | .orElseGet(() -> parsedPlaceName); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/components/CurrentLocationButton.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 25 | 26 | 32 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/libs/navigator/navigator.js: -------------------------------------------------------------------------------- 1 | const navigatorUtils = { 2 | getCurrentPosition() { 3 | return new Promise((resolve, reject) => { 4 | navigator.geolocation.getCurrentPosition(resolve, reject, { 5 | enableHighAccuracy: false, 6 | maximumAge: 60000, 7 | timeout: 10000, 8 | }); 9 | }); 10 | }, 11 | extractGeolocationPosition(position) { 12 | return { 13 | longitude: position.coords.longitude, 14 | latitude: position.coords.latitude, 15 | }; 16 | }, 17 | convertToLatLon(x, y) { 18 | return { 19 | longitude: x, 20 | latitude: y, 21 | }; 22 | }, 23 | }; 24 | 25 | export default navigatorUtils; 26 | -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/test/java/com/songpapeople/hashtagmap/dto/PostDtoTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.dto; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.assertj.core.api.Assertions.assertThat; 7 | 8 | class PostDtoTest { 9 | @DisplayName("PostDto 생성시 받아온 url key값으로 postUrl을 정상적으로 생성하는지 테스트") 10 | @Test 11 | void create() { 12 | String shortCode = "code"; 13 | String imageUrl = "http://www.address"; 14 | 15 | PostDto postDto = new PostDto(shortCode, imageUrl); 16 | 17 | assertThat(postDto.getPostUrl()).isEqualTo("https://www.instagram.com/p/code"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/utils/responseConverter.js: -------------------------------------------------------------------------------- 1 | import { MESSAGE, SNACK_BAR_TEMPLATE } from "@/utils/constants"; 2 | 3 | export const convert = { 4 | toSnackBarContent: function(response) { 5 | if (!response) { 6 | return SNACK_BAR_TEMPLATE.INFO(MESSAGE.NO_CONTENT); 7 | } 8 | switch (response.status) { 9 | case 200: 10 | return SNACK_BAR_TEMPLATE.SUCCESS(); 11 | case 201: 12 | return SNACK_BAR_TEMPLATE.SUCCESS(); 13 | case 204: 14 | return SNACK_BAR_TEMPLATE.SUCCESS(); 15 | case 400: 16 | return SNACK_BAR_TEMPLATE.ERROR(response); 17 | case 404: 18 | return SNACK_BAR_TEMPLATE.ERROR(response); 19 | } 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Vuex from "vuex"; 3 | import kakao from "@/store/modules/kakao"; 4 | import snackbar from "@/store/modules/snackbar"; 5 | import member from "@/store/modules/member"; 6 | import district from "@/store/modules/district"; 7 | import zone from "@/store/modules/zone"; 8 | import tagLevel from "@/store/modules/tagLevel"; 9 | import modal from "./modules/modal"; 10 | import blackList from "@/store/modules/blackList"; 11 | 12 | Vue.use(Vuex); 13 | export default new Vuex.Store({ 14 | modules: { 15 | snackbar, 16 | member, 17 | kakao, 18 | district, 19 | zone, 20 | tagLevel, 21 | modal, 22 | blackList 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/css/app.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: Nexon-gothic-bold; 3 | src: url("../NEXON-Gothic-Bold.woff") format("woff"); 4 | font-weight: normal; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: Nexon-gothic; 10 | src: url("../NEXON-Gothic.woff") format("woff"); 11 | font-weight: normal; 12 | font-style: normal; 13 | } 14 | 15 | #app { 16 | font-family: Nexon-gothic-bold, sans-serif; 17 | -webkit-font-smoothing: antialiased; 18 | -moz-osx-font-smoothing: grayscale; 19 | text-align: center; 20 | color: #2c3e50; 21 | } 22 | 23 | #current-location-button { 24 | z-index: 10; 25 | position: fixed; 26 | bottom: 35px; 27 | left: 35px; 28 | } 29 | -------------------------------------------------------------------------------- /hashtagmap-instagram-service/src/main/java/com/songpapeople/hashtagmap/exception/InstagramSchedulerExceptionStatus.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.exception; 2 | 3 | import com.songpapeople.hashtagmap.service.CrawlingResult; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | public enum InstagramSchedulerExceptionStatus { 8 | NOT_ENOUGH_HASHTAG_COUNT(String.format("해시태그 검색 결과가 %d개 미만입니다.", CrawlingResult.MIN_HASHTAG_COUNT), "INSTAGRAM_SCHEDULER_1000"); 9 | 10 | private final String message; 11 | private final String statusCode; 12 | 13 | InstagramSchedulerExceptionStatus(String message, String statusCode) { 14 | this.message = message; 15 | this.statusCode = statusCode; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/district/service/dto/DistrictUpdateDto.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.district.service.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.validation.constraints.NotNull; 8 | 9 | @Getter 10 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 11 | public class DistrictUpdateDto { 12 | @NotNull 13 | private Long districtId; 14 | @NotNull 15 | private String districtName; 16 | 17 | public DistrictUpdateDto(@NotNull final Long districtId, @NotNull final String districtName) { 18 | this.districtId = districtId; 19 | this.districtName = districtName; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /script/web/web-bind.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | BASE=`pwd` 4 | WEB_JAR=./hashtagmap-web/build/libs/*.jar 5 | WEB_DEPLOY_SCRIPT=./script/deploy.sh 6 | WEB_BASE=./script/hashtagmap-web 7 | WEB_TARGET=./script/hashtagmap-web/items 8 | 9 | echo "> WEB DIR 생성" 10 | mkdir -p $WEB_BASE 11 | mkdir -p $WEB_TARGET 12 | 13 | echo "> WEB 배포 스크립트 이동" 14 | cp $WEB_DEPLOY_SCRIPT $WEB_TARGET/ 15 | 16 | echo "> WEB jar 이동" 17 | cp $WEB_JAR $WEB_TARGET/ 18 | 19 | echo "> WEB 배포 스크립트, jar 압축" 20 | cd $WEB_TARGET 21 | tar -cvf hashtagmap-web.tar * 22 | 23 | echo "> 압축 이후 .jar, deploy.sh 삭제" 24 | rm *.jar 25 | rm deploy.sh 26 | 27 | echo "> tar 파일 이동" 28 | mv hashtagmap-web.tar $BASE/$WEB_BASE/ 29 | 30 | echo "> PROJECT_ROOT/script/hashtagmap-web/hashtagmap-web.tar 생성" 31 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/utils/markerImages.js: -------------------------------------------------------------------------------- 1 | import mapMarker1 from "../assets/markers/mapMarker1.png"; 2 | import mapMarker2 from "../assets/markers/mapMarker2.png"; 3 | import mapMarker3 from "../assets/markers/mapMarker3.png"; 4 | import mapMarker4 from "../assets/markers/mapMarker4.png"; 5 | import mapMarker5 from "../assets/markers/mapMarker5.png"; 6 | 7 | const markerImages = new Map(); 8 | markerImages.set(1, mapMarker1); 9 | markerImages.set(2, mapMarker2); 10 | markerImages.set(3, mapMarker3); 11 | markerImages.set(4, mapMarker4); 12 | markerImages.set(5, mapMarker5); 13 | 14 | export const getMarkerImage = tagLevel => { 15 | return markerImages.get(tagLevel); 16 | }; 17 | 18 | export const SIZE = { 19 | width: 40, 20 | height: 40, 21 | }; 22 | -------------------------------------------------------------------------------- /hashtagmap-common/src/main/java/com/songpapeople/hashtagmap/exception/CommonExceptionStatus.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum CommonExceptionStatus { 7 | UNEXPECTED("COMMON_0000", "요청을 처리하지 못했습니다."), 8 | REQUEST_NOT_ALLOWED("COMMON_0100", "처리할 수 없는 요청 URI입니다."), 9 | WRONG_ARGUMENT("COMMON_1000", "전달받은 매개변수가 올바르지 않습니다."), 10 | ALREADY_PERSIST("COMMON_2000", "이미 등록되었습니다."), 11 | NOT_PERSIST("COMMON_2100", "등록되어있지 않습니다."); 12 | 13 | private final String code; 14 | private final String message; 15 | 16 | CommonExceptionStatus(final String code, final String message) { 17 | this.code = code; 18 | this.message = message; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/test/java/com/songpapeople/hashtagmap/MockDataFactory.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap; 2 | 3 | import java.io.File; 4 | import java.io.FileReader; 5 | import java.io.IOException; 6 | 7 | public class MockDataFactory { 8 | 9 | public static String createBody() throws IOException { 10 | StringBuilder builder = new StringBuilder(); 11 | File file = new File("src/test/resources/crawling-mock-data.txt"); 12 | try (FileReader fileReader = new FileReader(file)) { 13 | int fileData; 14 | while ((fileData = fileReader.read()) != -1) { 15 | builder.append((char) fileData); 16 | } 17 | } 18 | return builder.toString(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /hashtagmap-kakao-scheduler/src/main/java/com/songpapeople/hashtagmap/scheduler/exception/KakaoSchedulerExceptionStatus.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.scheduler.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public enum KakaoSchedulerExceptionStatus { 7 | SCHEDULE_ALREADY_RUNNING("KAKAO_SCHEDULE_1000", "스케쥴러가 이미 실행중입니다."), 8 | INVALID_PERIOD_EXPRESSION("KAKAO_SCHEDULER_1001", "기간(정규식)이 잘못되었습니다."), 9 | SCHEDULE_ALREADY_STOPPED("KAKAO_SCHEDULE_1002", "스케쥴러가 이미 정지되었습니다."); 10 | 11 | private final String code; 12 | private final String message; 13 | 14 | 15 | KakaoSchedulerExceptionStatus(final String code, final String message) { 16 | this.code = code; 17 | this.message = message; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /hashtagmap-web/src/main/java/com/songpapeople/hashtagmap/service/InstagramPostQueryService.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.service; 2 | 3 | import com.songpapeople.hashtagmap.dto.InstagramPostResponse; 4 | import com.songpapeople.hashtagmap.repository.InstagramPostWebQueryRepository; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.List; 9 | 10 | @RequiredArgsConstructor 11 | @Service 12 | public class InstagramPostQueryService { 13 | private final InstagramPostWebQueryRepository instagramPostWebQueryRepository; 14 | 15 | public List findAllByInstagramId(Long id) { 16 | return instagramPostWebQueryRepository.findAllByInstagramId(id); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/request/api/blackList.js: -------------------------------------------------------------------------------- 1 | import { customWrapAxios } from "@/request"; 2 | 3 | const blackListApi = { 4 | getSubBlackList() { 5 | return customWrapAxios().get("/blacklist/abnormal-instagram"); 6 | }, 7 | updateInstagramAfterAddBlackList(blackList) { 8 | return customWrapAxios().put("/blacklist/instagram", blackList, { 9 | headers: { 10 | "Content-Type": "application/json" 11 | } 12 | }); 13 | }, 14 | deleteInstagramAfterAddBlackList(deleteBlackListData) { 15 | return customWrapAxios().delete("/blacklist/instagram", { 16 | headers: { 17 | "Content-Type": "application/json" 18 | }, 19 | data: deleteBlackListData 20 | }); 21 | } 22 | }; 23 | 24 | export default blackListApi; 25 | -------------------------------------------------------------------------------- /hashtagmap-web/src/main/java/com/songpapeople/hashtagmap/controller/RouterController.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.controller; 2 | 3 | import org.springframework.boot.web.servlet.error.ErrorController; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.ResponseStatus; 8 | 9 | @Controller 10 | public class RouterController implements ErrorController { 11 | @GetMapping({"/", "/error"}) 12 | @ResponseStatus(HttpStatus.OK) 13 | public String index() { 14 | return "index"; 15 | } 16 | 17 | @Override 18 | public String getErrorPath() { 19 | return "/error"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /script/admin/admin-bind.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | BASE=`pwd` 6 | WEB_JAR=./hashtagmap-admin/build/libs/*.jar 7 | WEB_DEPLOY_SCRIPT=./script/deploy.sh 8 | WEB_BASE=./script/hashtagmap-admin 9 | WEB_TARGET=./script/hashtagmap-admin/items 10 | 11 | echo "> WEB DIR 생성" 12 | mkdir -p $WEB_BASE 13 | mkdir -p $WEB_TARGET 14 | 15 | echo "> WEB 배포 스크립트 이동" 16 | cp $WEB_DEPLOY_SCRIPT $WEB_TARGET/ 17 | 18 | echo "> WEB jar 이동" 19 | cp $WEB_JAR $WEB_TARGET/ 20 | 21 | echo "> WEB 배포 스크립트, jar 압축" 22 | cd $WEB_TARGET 23 | tar -cvf hashtagmap-admin.tar * 24 | 25 | echo "> 압축 이후 .jar, deploy.sh 삭제" 26 | rm *.jar 27 | rm deploy.sh 28 | 29 | echo "> tar 파일 이동" 30 | mv hashtagmap-admin.tar $BASE/$WEB_BASE/ 31 | 32 | echo "> PROJECT_ROOT/script/hashtagmap-admin/hashtagmap-admin.tar 생성" 33 | -------------------------------------------------------------------------------- /hashtagmap-core/src/test/java/com/songpapeople/hashtagmap/blacklist/domain/model/BlackListTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.blacklist.domain.model; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 7 | 8 | public class BlackListTest { 9 | 10 | @DisplayName("blackList 업데이트 기능 테스트") 11 | @Test 12 | void updateBlackList() { 13 | BlackList target = new BlackList("1", "오아시스"); 14 | BlackList replace = new BlackList("1", "오아시스강남", true); 15 | 16 | target.updateBlackList(replace); 17 | 18 | assertThat(target.getReplaceName()).isEqualTo("오아시스강남"); 19 | assertThat(target.getIsSkipPlace()).isTrue(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 당신이 모르는 그곳, 미소 2 | 3 | https://themiso.kr 4 | 5 | ![image](https://user-images.githubusercontent.com/39546083/94126614-cca0ec00-fe92-11ea-8804-b97649415bf1.png) 6 | 7 | ## 서비스 소개 8 | 9 | 친구들, 연인과 함께할 장소를 어렵게 찾아보지 마세요. 10 | 11 | 인스타그램 속 🔥핫플레이스🔥를 한 눈에 확인하세요. 12 | 13 | 14 | 15 | 16 | ## 송파구 사람들 17 | 18 | ![image](https://user-images.githubusercontent.com/39546083/94125555-73848880-fe91-11ea-9687-cbe2fbb2e769.png) 19 | 20 | ## 기술 스택 21 | 22 | - JAVA 8 23 | - Spring Boot 2.3.1 24 | - Spring Data JPA 25 | - Spring Rest Docs 26 | - Spring Batch 27 | - Gradle 28 | - MariaDB 29 | - QueryDSL 30 | - Nginx 31 | - Jenkins 32 | - SonarQube 33 | - Vue.js 34 | - PWA -------------------------------------------------------------------------------- /hashtagmap-core/src/test/java/com/songpapeople/hashtagmap/kakao/schedule/model/PeriodHistoryTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakao.schedule.model; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 7 | import static org.junit.jupiter.api.Assertions.assertAll; 8 | 9 | class PeriodHistoryTest { 10 | 11 | @DisplayName("PeriodHistory 생성자 테스트") 12 | @Test 13 | void create() { 14 | PeriodHistory periodHistory = new PeriodHistory("0 0 * * * ?"); 15 | assertAll(() -> { 16 | assertThat(periodHistory.getMember()).isNull(); 17 | assertThat(periodHistory.getExpression()).isEqualTo("0 0 * * * ?"); 18 | }); 19 | } 20 | } -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/main/java/com/songpapeople/hashtagmap/dto/CrawlingDto.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | 7 | import java.util.List; 8 | 9 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 10 | @Getter 11 | public class CrawlingDto { 12 | private final String hashtagName; 13 | private final Long hashtagCount; 14 | private final PostDtos postDtos; 15 | 16 | public static CrawlingDto of(String hashtagName, String hashtagCount, PostDtos postDtos) { 17 | return new CrawlingDto(hashtagName, Long.valueOf(hashtagCount), postDtos); 18 | } 19 | 20 | public List getPostDtoList() { 21 | return postDtos.getPostDtos(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/store/modules/member.js: -------------------------------------------------------------------------------- 1 | import { CONST } from "@/utils/constants"; 2 | import memberApi from "@/request/api/member"; 3 | 4 | export default { 5 | namespaced: true, 6 | 7 | state: {}, 8 | 9 | mutations: {}, 10 | 11 | getters: { 12 | isLogin: () => { 13 | return localStorage.getItem(CONST.ADMIN_LOGIN_KEY); 14 | } 15 | }, 16 | 17 | actions: { 18 | loginRequest: async (commit, member) => { 19 | try { 20 | await memberApi.login(member); 21 | localStorage.setItem(CONST.ADMIN_LOGIN_KEY, true); 22 | location.href = "/"; 23 | } catch (e) { 24 | return e; 25 | } 26 | }, 27 | logout: () => { 28 | localStorage.removeItem(CONST.ADMIN_LOGIN_KEY); 29 | location.href = "/"; 30 | } 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /hashtagmap-web/src/main/java/com/songpapeople/hashtagmap/controller/WebRestController.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.controller; 2 | 3 | import lombok.AllArgsConstructor; 4 | import org.springframework.core.env.Environment; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import java.util.Arrays; 9 | 10 | @RestController 11 | @AllArgsConstructor 12 | public class WebRestController { 13 | private final Environment environment; 14 | 15 | @GetMapping("/profile") 16 | public String getProfile() { 17 | return Arrays.stream(environment.getActiveProfiles()) 18 | .filter(profile -> profile.contains("set")) 19 | .findFirst() 20 | .orElse(""); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/components/tag-level/TagLevelSelector.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 27 | 28 | 33 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/utils/validator.js: -------------------------------------------------------------------------------- 1 | const validator = { 2 | district: { 3 | name: [ 4 | v => !!v || "자치구 이름이 필요합니다.", 5 | v => v.trim() !== "" || "공백은 입력할 수 없습니다." 6 | ] 7 | }, 8 | zone: { 9 | latitude: [ 10 | v => 11 | (Number(v) >= 33 && Number(v) <= 44) || 12 | "올바른 위도 범위가 아닙니다. 33 ~ 43.x" 13 | ], 14 | longitude: [ 15 | v => 16 | (Number(v) >= 124 && Number(v) <= 133) || 17 | "올바른 경도 범위가 아닙니다. 124 ~ 132.x" 18 | ] 19 | }, 20 | kakao: { 21 | cron: [ 22 | v => !!v || "공백은 입력할 수 없습니다.", 23 | v => v.trim() !== "" || "공백은 입력할 수 없습니다.", 24 | v => v.trim() !== "* * * * * *" || "주기가 너무 빠릅니다.", 25 | v => v.trim() !== "* * * * * ?" || "주기가 너무 빠릅니다." 26 | ] 27 | } 28 | }; 29 | 30 | export default validator; 31 | -------------------------------------------------------------------------------- /hashtagmap-event/src/main/java/com/songpapeople/hashtagmap/event/util/LogDecorator.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.event.util; 2 | 3 | import org.slf4j.MDC; 4 | import org.springframework.core.task.TaskDecorator; 5 | 6 | import java.util.Map; 7 | 8 | public class LogDecorator implements TaskDecorator { 9 | private final Map contextMap; 10 | 11 | public LogDecorator(final Map contextMap) { 12 | this.contextMap = contextMap; 13 | } 14 | 15 | @Override 16 | public Runnable decorate(final Runnable runnable) { 17 | return () -> { 18 | try { 19 | MDC.setContextMap(contextMap); 20 | runnable.run(); 21 | } finally { 22 | MDC.clear(); 23 | } 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /hashtagmap-kakao-api/src/main/java/com/songpapeople/hashtagmap/kakaoapi/domain/dto/KakaoPlaceDto.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakaoapi.domain.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * meta: Response에 대한 Meta 데이터 12 | * documents: 검색한 지역에 포함된 가게의 정보 13 | */ 14 | 15 | @Getter 16 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 17 | @AllArgsConstructor 18 | public class KakaoPlaceDto { 19 | private Meta meta; 20 | private List documents; 21 | 22 | public int getTotalCount() { 23 | return this.meta.getTotalCount(); 24 | } 25 | 26 | public int getPageableCount() { 27 | return this.meta.getPageableCount(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /hashtagmap-event/src/main/java/com/songpapeople/hashtagmap/event/config/EventConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.event.config; 2 | 3 | import com.songpapeople.hashtagmap.event.process.EventBrokerGroup; 4 | import com.songpapeople.hashtagmap.event.process.EventConsumer; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | 9 | @Configuration 10 | @RequiredArgsConstructor 11 | public class EventConfiguration { 12 | 13 | @Bean 14 | public EventConsumer eventConsumer(EventBrokerGroup eventBrokerGroup) { 15 | return new EventConsumer(eventBrokerGroup); 16 | } 17 | 18 | @Bean 19 | public EventBrokerGroup eventBrokers() { 20 | return new EventBrokerGroup(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /hashtagmap-instagram-service/src/test/java/com/songpapeople/hashtagmap/MockDataFactory.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap; 2 | 3 | import com.songpapeople.hashtagmap.dto.PostDto; 4 | import com.songpapeople.hashtagmap.dto.PostDtos; 5 | 6 | import java.util.Arrays; 7 | 8 | public class MockDataFactory { 9 | 10 | public static PostDtos createPostDtos() { 11 | return new PostDtos(Arrays.asList( 12 | new PostDto("1", "1"), 13 | new PostDto("2", "2"), 14 | new PostDto("3", "3"), 15 | new PostDto("4", "4"), 16 | new PostDto("5", "5"), 17 | new PostDto("6", "6"), 18 | new PostDto("7", "7"), 19 | new PostDto("8", "8"), 20 | new PostDto("9", "9") 21 | )); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/controller/RouterController.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.controller; 2 | 3 | import org.springframework.boot.web.servlet.error.ErrorController; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | 7 | @Controller 8 | public class RouterController implements ErrorController { 9 | 10 | @GetMapping(value = {"/", 11 | "/kakao-scheduler", 12 | "/instagram-scheduler", 13 | "/district-manage", 14 | "/tag-level", 15 | "/blacklist-manager", 16 | "/error"}) 17 | public String index() { 18 | return "index"; 19 | } 20 | 21 | @Override 22 | public String getErrorPath() { 23 | return "/error"; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/taglevel/service/dto/TagLevelDto.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.taglevel.service.dto; 2 | 3 | import com.songpapeople.hashtagmap.taglevel.model.TagLevel; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | public class TagLevelDto { 8 | private Long tagLevel; 9 | private Long minHashtagCount; 10 | private Long maxHashtagCount; 11 | 12 | public TagLevelDto(Long tagLevel, Long minHashtagCount, Long maxHashtagCount) { 13 | this.tagLevel = tagLevel; 14 | this.minHashtagCount = minHashtagCount; 15 | this.maxHashtagCount = maxHashtagCount; 16 | } 17 | 18 | public static TagLevelDto from(TagLevel tagLevel) { 19 | return new TagLevelDto(tagLevel.getId(), tagLevel.getMinHashtagCount(), tagLevel.getMaxHashtagCount()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /hashtagmap-core/src/test/java/com/songpapeople/hashtagmap/kakao/schedule/model/ScheduleTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakao.schedule.model; 2 | 3 | import com.songpapeople.hashtagmap.config.vo.Flag; 4 | import org.junit.jupiter.api.DisplayName; 5 | import org.junit.jupiter.params.ParameterizedTest; 6 | import org.junit.jupiter.params.provider.CsvSource; 7 | 8 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 9 | 10 | class ScheduleTest { 11 | 12 | @DisplayName("flag toggle 기능 테스트") 13 | @ParameterizedTest 14 | @CsvSource(value = {"Y,false", "N,true"}) 15 | void toggle(Flag flag, boolean expect) { 16 | Schedule schedule = new Schedule("KAKAO", "name", flag); 17 | 18 | schedule.toggle(); 19 | 20 | assertThat(schedule.isActive()).isEqualTo(expect); 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /hashtagmap-web/front/src/store/modules/place.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export default { 4 | state: { 5 | places: [ 6 | // { 7 | // hashtagCount: "", 8 | // instagramId: "", 9 | // kakaoId: "", 10 | // latitude: "", 11 | // longitude: "", 12 | // placeName: "", 13 | // tagLevel: "", 14 | // category: "", 15 | // }, 16 | ], 17 | }, 18 | 19 | getters: { 20 | getPlaces: state => { 21 | return state.places; 22 | }, 23 | }, 24 | 25 | mutations: { 26 | SET_PLACES(state, places) { 27 | state.places = places; 28 | }, 29 | }, 30 | 31 | actions: { 32 | async setPlaces({ commit }) { 33 | const places = await axios.get("/markers"); 34 | commit("SET_PLACES", places.data.data); 35 | }, 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/taglevel/service/TagLevelQueryService.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.taglevel.service; 2 | 3 | import com.songpapeople.hashtagmap.taglevel.repository.TagLevelRepository; 4 | import com.songpapeople.hashtagmap.taglevel.service.dto.TagLevelDto; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | 11 | @RequiredArgsConstructor 12 | @Service 13 | public class TagLevelQueryService { 14 | private final TagLevelRepository tagLevelRepository; 15 | 16 | public List findAll() { 17 | return tagLevelRepository.findAll() 18 | .stream() 19 | .map(TagLevelDto::from) 20 | .collect(Collectors.toList()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/place/domain/repository/ZoneQueryRepository.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.place.domain.repository; 2 | 3 | import com.querydsl.jpa.impl.JPAQueryFactory; 4 | import com.songpapeople.hashtagmap.place.domain.model.QZone; 5 | import com.songpapeople.hashtagmap.place.domain.model.Zone; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.List; 10 | 11 | @RequiredArgsConstructor 12 | @Repository 13 | public class ZoneQueryRepository { 14 | private final JPAQueryFactory jpaQueryFactory; 15 | 16 | public List findByActivated() { 17 | QZone zone = QZone.zone; 18 | return jpaQueryFactory.selectFrom(zone) 19 | .where(zone.isActivated.isTrue()) 20 | .fetch(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/components/CustomSnackBar.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 19 | 30 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/request/api/zone.js: -------------------------------------------------------------------------------- 1 | import customAxios, { customWrapAxios } from "@/request"; 2 | 3 | const zoneApi = { 4 | save(zoneInput) { 5 | return customWrapAxios().post("/districts/zones", zoneInput, { 6 | headers: { 7 | "Content-Type": "application/json" 8 | } 9 | }); 10 | }, 11 | update(updateDto) { 12 | return customWrapAxios().patch("/districts/zones", updateDto, { 13 | headers: { 14 | "Content-Type": "application/json" 15 | } 16 | }); 17 | }, 18 | findAll() { 19 | return customWrapAxios().get("/districts/zones"); 20 | }, 21 | delete(zoneIds) { 22 | return customAxios().delete("/districts/zones", { 23 | headers: { 24 | "Content-Type": "application/json" 25 | }, 26 | data: zoneIds 27 | }); 28 | } 29 | }; 30 | 31 | export default zoneApi; 32 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/test/java/com/songpapeople/hashtagmap/docs/instagram/InstagramApiDocumentation.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.docs.instagram; 2 | 3 | import com.songpapeople.hashtagmap.docs.ApiDocument; 4 | import org.springframework.restdocs.mockmvc.RestDocumentationResultHandler; 5 | 6 | import static com.songpapeople.hashtagmap.docs.ApiDocumentUtils.getDocumentRequest; 7 | import static com.songpapeople.hashtagmap.docs.ApiDocumentUtils.getDocumentResponse; 8 | import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; 9 | 10 | public class InstagramApiDocumentation extends ApiDocument { 11 | protected RestDocumentationResultHandler getDocumentByUpdate() { 12 | return document("instagram/scheduler/update", 13 | getDocumentRequest(), 14 | getDocumentResponse()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /hashtagmap-web/src/main/java/com/songpapeople/hashtagmap/service/TagLevelQueryService.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.service; 2 | 3 | import com.songpapeople.hashtagmap.dto.TagLevelResponse; 4 | import com.songpapeople.hashtagmap.taglevel.model.TagLevels; 5 | import com.songpapeople.hashtagmap.taglevel.repository.TagLevelQueryRepository; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.List; 10 | 11 | @RequiredArgsConstructor 12 | @Service 13 | public class TagLevelQueryService { 14 | private final TagLevelQueryRepository tagLevelQueryRepository; 15 | 16 | public List findTagLevels() { 17 | TagLevels tagLevels = new TagLevels(tagLevelQueryRepository.findFiveByModifiedDateOrderById()); 18 | return TagLevelResponse.of(tagLevels); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /hashtagmap-event/src/test/java/com/songpapeople/hashtagmap/event/util/LogDecoratorTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.event.util; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.slf4j.MDC; 5 | 6 | import java.util.concurrent.CountDownLatch; 7 | 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | 10 | class LogDecoratorTest { 11 | 12 | @Test 13 | void decorate() throws InterruptedException { 14 | //given 15 | MDC.put("test", "test"); 16 | LogDecorator logDecorator = new LogDecorator(MDC.getCopyOfContextMap()); 17 | 18 | CountDownLatch countDownLatch = new CountDownLatch(1); 19 | logDecorator.decorate(() -> { 20 | assertThat(MDC.get("test")).isEqualTo("test"); 21 | countDownLatch.countDown(); 22 | }).run(); 23 | 24 | countDownLatch.await(); 25 | } 26 | } -------------------------------------------------------------------------------- /hashtagmap-kakao-api/src/main/java/com/songpapeople/hashtagmap/kakaoapi/domain/dto/RegionInfo.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakaoapi.domain.dto; 2 | 3 | import com.fasterxml.jackson.databind.PropertyNamingStrategy; 4 | import com.fasterxml.jackson.databind.annotation.JsonNaming; 5 | import lombok.AccessLevel; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | /** 10 | * region: 질의어에서 인식된 지역의 리스트 11 | * ex) '중앙로 맛집' 에서 중앙로에 해당하는 지역 리스트 12 | * keyword: 질의어에서 지역 정보를 제외한 키워드 13 | * ex) '중앙로 맛집' 에서 '맛집' 14 | * selectedRegion: 인식된 지역 리스트 중, 현재 검색에 사용된 지역 정보 15 | */ 16 | 17 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 18 | @Getter 19 | @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) 20 | public class RegionInfo { 21 | private String[] region; 22 | private String keyword; 23 | private String selectedRegion; 24 | } 25 | -------------------------------------------------------------------------------- /hashtagmap-core/src/test/java/com/songpapeople/hashtagmap/taglevel/model/TagLevelTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.taglevel.model; 2 | 3 | import com.songpapeople.hashtagmap.exception.CoreException; 4 | import com.songpapeople.hashtagmap.exception.CoreExceptionStatus; 5 | import org.junit.jupiter.api.DisplayName; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertThrows; 10 | 11 | class TagLevelTest { 12 | 13 | @DisplayName("해시태그 최소/최대 개수를 검증한다.") 14 | @Test 15 | public void validateHashtagCountTest() { 16 | CoreException exception = assertThrows(CoreException.class, 17 | () -> new TagLevel(0L, 10L, 1L)); 18 | assertEquals(exception.getErrorCode(), CoreExceptionStatus.INVALID_TAG_LEVEL.getCode()); 19 | } 20 | } -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/main/java/com/songpapeople/hashtagmap/dto/PostDtos.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.dto; 2 | 3 | import com.songpapeople.hashtagmap.exception.CrawlerException; 4 | import com.songpapeople.hashtagmap.exception.CrawlerExceptionStatus; 5 | import lombok.Getter; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | @Getter 11 | public class PostDtos { 12 | public static final int POPULAR_POST_SIZE = 9; 13 | 14 | private final List postDtos; 15 | 16 | public PostDtos(List postDtos) { 17 | if (postDtos.size() != POPULAR_POST_SIZE) { 18 | throw new CrawlerException(CrawlerExceptionStatus.NOT_ENOUGH_POPULAR_POST); 19 | } 20 | this.postDtos = new ArrayList<>(postDtos); 21 | } 22 | 23 | public int size() { 24 | return this.postDtos.size(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /hashtagmap-batch/src/main/java/com/songpapeople/hashtagmap/instagram/processor/InstagramBatchProcessor.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.instagram.processor; 2 | 3 | import com.songpapeople.hashtagmap.place.domain.model.Place; 4 | import com.songpapeople.hashtagmap.service.CrawlingResult; 5 | import com.songpapeople.hashtagmap.service.InstagramCrawlingService; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.batch.item.ItemProcessor; 8 | import org.springframework.stereotype.Component; 9 | 10 | @RequiredArgsConstructor 11 | @Component 12 | public class InstagramBatchProcessor implements ItemProcessor { 13 | private final InstagramCrawlingService instagramCrawlingService; 14 | 15 | @Override 16 | public CrawlingResult process(Place place) { 17 | return instagramCrawlingService.createCrawlingResult(place).orElse(null); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/exception/AdminException.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.exception; 2 | 3 | public class AdminException extends HashtagMapException { 4 | public AdminException(final String errorMessage, final String errorCode) { 5 | super(errorMessage, errorCode); 6 | } 7 | 8 | public AdminException(AdminExceptionStatus adminExceptionStatus) { 9 | super(adminExceptionStatus.getMessage(), adminExceptionStatus.getCode()); 10 | } 11 | 12 | public AdminException(AdminExceptionStatus adminExceptionStatus, String detailMessage) { 13 | super(adminExceptionStatus.getMessage(), adminExceptionStatus.getCode(), detailMessage); 14 | } 15 | 16 | public AdminException(final CommonExceptionStatus commonExceptionStatus, final String detailMessage) { 17 | super(commonExceptionStatus, detailMessage); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/blacklist/service/dto/BlackListRequest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.blacklist.service.dto; 2 | 3 | import com.songpapeople.hashtagmap.blacklist.domain.model.BlackList; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | import javax.validation.constraints.NotNull; 8 | 9 | @NoArgsConstructor 10 | @Getter 11 | public class BlackListRequest { 12 | @NotNull 13 | private String kakaoId; 14 | 15 | @NotNull 16 | private String replaceName; 17 | 18 | public static BlackList toSkipBlackList(BlackListRequest blackListRequest) { 19 | return new BlackList(blackListRequest.getKakaoId(), blackListRequest.getReplaceName(), true); 20 | } 21 | 22 | public BlackListRequest(String kakaoId, String replaceName) { 23 | this.kakaoId = kakaoId; 24 | this.replaceName = replaceName; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/place/domain/model/Category.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.place.domain.model; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.Arrays; 6 | 7 | @Getter 8 | public enum Category { 9 | CAFE("CE7", "카페"), 10 | RESTAURANT("FD6", "음식점"); 11 | 12 | private String categoryGroupCode; 13 | private String categoryGroupName; 14 | 15 | Category(String categoryGroupCode, String categoryGroupName) { 16 | this.categoryGroupCode = categoryGroupCode; 17 | this.categoryGroupName = categoryGroupName; 18 | } 19 | 20 | public static Category fromCategoryGroupCode(String code) { 21 | return Arrays.stream(Category.values()) 22 | .filter(category -> category.categoryGroupCode.equals(code)) 23 | .findFirst() 24 | .orElseThrow(() -> new IllegalArgumentException("존재하는 않는 카테고리 코드입니다.")); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /hashtagmap-instagram-service/src/test/java/com/songpapeople/hashtagmap/service/ProxySetterTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.service; 2 | 3 | import com.songpapeople.hashtagmap.proxy.Proxies; 4 | import com.songpapeople.hashtagmap.proxy.Proxy; 5 | import org.junit.jupiter.api.DisplayName; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.Arrays; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | class ProxySetterTest { 13 | @DisplayName("프록시 set테스트") 14 | @Test 15 | void init() { 16 | Proxies proxies = new Proxies(Arrays.asList( 17 | new Proxy("1", "1"), 18 | new Proxy("2", "2"), 19 | new Proxy("3", "3")) 20 | ); 21 | ProxySetter proxySetter = new ProxySetter(proxies); 22 | 23 | proxySetter.setProxy(); 24 | 25 | assertThat(System.getProperty("http.proxyHost")).isBetween("1", "3"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/blacklist/service/dto/BlackListResponse.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.blacklist.service.dto; 2 | 3 | import com.songpapeople.hashtagmap.instagram.domain.model.Instagram; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @NoArgsConstructor 8 | @Getter 9 | public class BlackListResponse { 10 | private String kakaoId; 11 | private String replaceName; 12 | private Long hashtagCount; 13 | 14 | public static BlackListResponse of(Instagram instagram) { 15 | return new BlackListResponse(instagram.getKakaoId(), 16 | instagram.getHashtagName(), 17 | instagram.getHashtagCount()); 18 | } 19 | 20 | private BlackListResponse(String kakaoId, String replaceName, Long hashtagCount) { 21 | this.kakaoId = kakaoId; 22 | this.replaceName = replaceName; 23 | this.hashtagCount = hashtagCount; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/store/modules/category.js: -------------------------------------------------------------------------------- 1 | import { CATEGORY } from "../../utils/constants"; 2 | 3 | export default { 4 | state: { 5 | categories: [ 6 | { 7 | name: CATEGORY.CAFE, 8 | active: true, 9 | }, 10 | { 11 | name: CATEGORY.RESTAURANT, 12 | active: true, 13 | }, 14 | ], 15 | }, 16 | 17 | getters: { 18 | getCategories: state => { 19 | return state.categories; 20 | }, 21 | getActiveCategories: state => { 22 | return state.categories 23 | .filter(category => category.active) 24 | .map(category => category.name); 25 | }, 26 | }, 27 | 28 | mutations: { 29 | SET_CATEGORY(state, category) { 30 | state.categories = state.categories.map(c => 31 | c.name === category.name 32 | ? { ...c, active: !category.active } 33 | : { ...c, active: true }, 34 | ); 35 | }, 36 | }, 37 | 38 | actions: {}, 39 | }; 40 | -------------------------------------------------------------------------------- /script/nonstopweb/publish-over-ssh.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | DEPLOY_USER=ubuntu 6 | echo "DEPLOY USER = $DEPLOY_USER" 7 | 8 | ZIP_DIR='/home/'$DEPLOY_USER'/app/zip' 9 | sudo mkdir -p $ZIP_DIR 10 | echo "ZIP_DIR = $ZIP_DIR" 11 | 12 | JAR_DIR='/home/'$DEPLOY_USER'/app' 13 | sudo mkdir -p $JAR_DIR 14 | echo "JAR_DIR = $JAR_DIR" 15 | 16 | 17 | JAR_COUNT=$(ls -al $JAR_DIR | grep jar | wc -l) 18 | if [ "$JAR_COUNT" -eq 1 ] 19 | then 20 | sudo mv ~/app/*.jar ~/backup-app/ 21 | echo "> 이전 버전 jar파일 백업" 22 | else 23 | echo "> 백업할 파일이 존재하지 않습니다" 24 | fi 25 | 26 | sudo mv '/home/'$DEPLOY_USER'/hashtagmap-web.tar' $JAR_DIR/ 27 | echo "tar moved to $JAR_DIR" 28 | 29 | sudo tar -xvf $JAR_DIR/hashtagmap-web.tar -C $JAR_DIR/ 30 | echo "> done untar" 31 | 32 | sudo mv $JAR_DIR/hashtagmap-web.tar $ZIP_DIR/ 33 | echo "tar moved to $ZIP_DIR" 34 | 35 | echo "> run nonstop-deploy.sh" 36 | sudo chmod +x $JAR_DIR/nonstop-deploy.sh 37 | sudo sh $JAR_DIR/nonstop-deploy.sh 38 | -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar { enabled = false } 2 | jar { enabled = true } 3 | 4 | dependencies { 5 | implementation project(path:':hashtagmap-common', configuration: 'default') 6 | implementation 'org.jsoup:jsoup:1.13.1' 7 | implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.6' 8 | testImplementation group: 'org.mock-server', name: 'mockserver-netty', version: '5.11.1' 9 | } 10 | 11 | jacocoTestCoverageVerification { 12 | violationRules { 13 | rule { 14 | enabled = true 15 | element = 'BUNDLE' 16 | 17 | limit { 18 | counter = 'INSTRUCTION' 19 | value = 'COVEREDRATIO' 20 | minimum = 0.90 21 | } 22 | excludes = [] 23 | } 24 | } 25 | } 26 | 27 | sonarqube { 28 | properties { 29 | property "sonar.exclusions", "**/*Test*.*, **/Q*.java, **/*Doc*.java, **/Mock*" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/components/place/PlaceCategoryFilter.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 31 | 32 | 45 | -------------------------------------------------------------------------------- /hashtagmap-core/src/test/java/com/songpapeople/hashtagmap/place/domain/model/CategoryTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.place.domain.model; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 7 | import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; 8 | 9 | class CategoryTest { 10 | 11 | @DisplayName("GroupCode로 Category 찾아오기 테스트") 12 | @Test 13 | void fromCategoryGroupCode() { 14 | assertThat(Category.fromCategoryGroupCode("CE7")).isEqualTo(Category.CAFE); 15 | } 16 | 17 | @DisplayName("존재하지 않는 GroupCode 찾을 시 예외 발생 테스트") 18 | @Test 19 | void fromCategoryGroupCodeException() { 20 | assertThatThrownBy(() -> Category.fromCategoryGroupCode("HAHA")) 21 | .isInstanceOf(IllegalArgumentException.class) 22 | .hasMessageContaining("존재하는 않는 카테고리 코드입니다."); 23 | } 24 | } -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/main/java/com/songpapeople/hashtagmap/exception/CrawlerExceptionStatus.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.exception; 2 | 3 | import com.songpapeople.hashtagmap.dto.PostDtos; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | public enum CrawlerExceptionStatus { 8 | URL_NOT_CONNECT("연결할 수 없는 URL 입니다.", "CRAWLER_1001"), 9 | NOT_ENOUGH_POPULAR_POST(String.format("인기게시물이 %d개 미만입니다.", PostDtos.POPULAR_POST_SIZE), "CRAWLER_1002"), 10 | NOT_FOUND_MATCH_REGEX("크롤링 데이터에서 원하는 내용을 찾지 못했습니다.", "CRAWLER_1003"), 11 | ILLEGAL_PROXY("잘못된 프록시 설정입니다.", "CRAWLER_1004"), 12 | NOT_FOUND_URL("해당 URL을 찾을 수 없습니다.", "CRAWLER_1005"), 13 | TOO_MANY_REQUEST("너무 많은 요청으로 오류가 발생했습니다.", "CRAWLER_1006"); 14 | 15 | private final String message; 16 | private final String statusCode; 17 | 18 | CrawlerExceptionStatus(String message, String statusCode) { 19 | this.message = message; 20 | this.statusCode = statusCode; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/test/java/com/songpapeople/hashtagmap/proxy/ProxiesFactoryTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.proxy; 2 | 3 | import org.assertj.core.api.Assertions; 4 | import org.junit.jupiter.api.DisplayName; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.lang.reflect.Constructor; 8 | 9 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 10 | 11 | class ProxiesFactoryTest { 12 | 13 | @DisplayName("프록시 사이트에 프록시 주소를 크롤링하여 정상적으로 Proxies를 만드는지 테스트") 14 | @Test 15 | void create() { 16 | assertThat(ProxiesFactory.create().size()).isGreaterThan(200); 17 | } 18 | 19 | @DisplayName("유틸성 클래스라 private 생성자를 가지고 있다.") 20 | @Test 21 | void constructorTest() throws NoSuchMethodException { 22 | Constructor declaredConstructor = ProxiesFactory.class.getDeclaredConstructor((Class[]) null); 23 | Assertions.assertThat(declaredConstructor.isAccessible()).isFalse(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /hashtagmap-kakao-api/src/main/java/com/songpapeople/hashtagmap/kakaoapi/domain/caller/KakaoRestTemplateBuilder.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakaoapi.domain.caller; 2 | 3 | import com.songpapeople.hashtagmap.kakaoapi.domain.exception.KakaoExceptionHandler; 4 | import org.springframework.boot.web.client.RestTemplateBuilder; 5 | 6 | import java.time.Duration; 7 | 8 | public class KakaoRestTemplateBuilder { 9 | public static RestTemplateBuilder get(KakaoSecurityProperties kakaoSecurityProperties, 10 | KakaoProperties kakaoProperties) { 11 | return new RestTemplateBuilder() 12 | .rootUri(kakaoProperties.getBaseUrl()) 13 | .errorHandler(new KakaoExceptionHandler()) 14 | .defaultHeader("Authorization", "KakaoAK " + kakaoSecurityProperties.getKey()) 15 | .setConnectTimeout(Duration.ofSeconds(10)) 16 | .setReadTimeout(Duration.ofSeconds(10)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/assets/css/textBalloon.css: -------------------------------------------------------------------------------- 1 | .text-balloon-box { 2 | bottom: 77px; 3 | border-radius: 30px / 30px; 4 | box-shadow: 0 4px 15px 0 rgba(45, 54, 65, 0.5); 5 | background-color: #ffffff !important; 6 | min-width: 150px; 7 | padding: 6px 12px; 8 | font-size: 14px; 9 | } 10 | 11 | .close { 12 | width: 18px; 13 | height: 18px; 14 | background-image: url("../close.png"); 15 | position: absolute; 16 | top: -0.4rem; 17 | right: 0.2rem; 18 | opacity: 0.9; 19 | } 20 | 21 | .text-balloon-box::after { 22 | border-left: 11px solid transparent; 23 | border-top: 13px solid white; 24 | border-right: 11px solid transparent; 25 | content: ""; 26 | position: absolute; 27 | bottom: -13px; 28 | right: 63px; 29 | } 30 | 31 | .text-balloon-box:hover { 32 | box-shadow: 0 4px 15px 0 rgba(45, 54, 65, 0.75); 33 | } 34 | 35 | .text-balloon-title { 36 | width: auto; 37 | right: 90%; 38 | } 39 | 40 | .text-balloon-text { 41 | font-weight: bold; 42 | } 43 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/kakao/schedule/model/PeriodHistory.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakao.schedule.model; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import org.hibernate.annotations.CreationTimestamp; 7 | 8 | import javax.persistence.Entity; 9 | import javax.persistence.GeneratedValue; 10 | import javax.persistence.GenerationType; 11 | import javax.persistence.Id; 12 | import java.util.Date; 13 | 14 | @Getter 15 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 16 | @Entity 17 | public class PeriodHistory { 18 | @Id 19 | @GeneratedValue(strategy = GenerationType.IDENTITY) 20 | private Long id; 21 | 22 | private String expression; 23 | private String member; 24 | 25 | @CreationTimestamp 26 | private Date changedDate; 27 | 28 | public PeriodHistory(String expression) { 29 | this.expression = expression; 30 | this.member = null; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/README.md: -------------------------------------------------------------------------------- 1 | # Instagram Crawler 2 | 3 | ## 모듈 설명 4 | 5 | - 인스타그램에서 가게 이름에 대한 해시태그 수, 이미지 등을 크롤링하는 기능을 제공하는 모듈. 6 | - 크롤링 기능을 외부 라이브러리로 제공할 수 있다. 7 | - hashtagmap-common 모듈을 이용해 커스텀 예외 객체를 사용한다. 8 | 9 | ## 기술 스택 10 | 11 | - Jsoup 12 | 13 | ```Java 14 | public static Document crawling(String url) { 15 | try { 16 | return Jsoup.connect(url) 17 | //위장할 브라우저, 디바이스 정보 18 | .userAgent(USER_AGENT) 19 | //최대 연결 시도 시간 20 | .timeout(HOLDING_TIME) 21 | .get(); 22 | } catch (IOException e) { 23 | throw new CrawlingUrlException(); 24 | } 25 | } 26 | ``` 27 | 28 | **사용방법** 29 | ```InstagramCrawler.createHashtagDto(검색할 이름)``` 30 | 31 | ```java 32 | public CrawlingDto createHashtagDto(String placeName) { 33 | 34 | ... 35 | 36 | return CrawlingDto.of(placeName, hashtagCount, postDtos); 37 | } 38 | ``` 39 | 40 | **CrawlingDto에 포함된 정보** 41 | - 가게이름 42 | - 해시태크 게시물 수 43 | - 인기게시물 9개의 postUrl 과 imageUrl 44 | -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/test/java/com/songpapeople/hashtagmap/proxy/ProxyTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.proxy; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; 7 | import static org.junit.jupiter.api.Assertions.assertAll; 8 | 9 | public class ProxyTest { 10 | @DisplayName("프록시 설정 테스트") 11 | @Test 12 | void setHostAndPort() { 13 | String inputHost = "123.23.12.15"; 14 | String inputPort = "8080"; 15 | Proxy proxy = new Proxy(inputHost, inputPort); 16 | proxy.setHostAndPort(); 17 | 18 | String actualHost = System.getProperty("http.proxyHost"); 19 | String actualPort = System.getProperty("http.proxyPort"); 20 | 21 | assertAll( 22 | () -> assertThat(actualHost).isEqualTo(inputHost), 23 | () -> assertThat(actualPort).isEqualTo(inputPort) 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/components/place/PlaceSearchInput.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 33 | 34 | 43 | -------------------------------------------------------------------------------- /script/nonstopweb/web-bind.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | BASE=`pwd` 4 | WEB_JAR=./hashtagmap-web/build/libs/*.jar 5 | WEB_DEPLOY_SCRIPT=./script/nonstop-deploy.sh 6 | WEB_BASE=./script/hashtagmap-web 7 | WEB_TARGET=./script/hashtagmap-web/items 8 | ITEM_NAME=nonstop_test 9 | 10 | echo "> workspace clear! exclude $ITEM_NAME" 11 | cd /var/lib/jenkins/workspace 12 | sudo rm -rf `ls /var/lib/jenkins/workspace | grep -v $ITEM_NAME` 13 | 14 | echo "> move to web workspace" 15 | cd $ITEM_NAME 16 | 17 | echo "> WEB DIR 생성" 18 | mkdir -p $WEB_BASE 19 | mkdir -p $WEB_TARGET 20 | 21 | echo "> WEB 배포 스크립트 이동" 22 | cp $WEB_DEPLOY_SCRIPT $WEB_TARGET/ 23 | 24 | echo "> WEB jar 이동" 25 | cp $WEB_JAR $WEB_TARGET/ 26 | 27 | echo "> WEB 배포 스크립트, jar 압축" 28 | cd $WEB_TARGET 29 | tar -cvf hashtagmap-web.tar * 30 | 31 | echo "> 압축 이후 .jar, deploy.sh 삭제" 32 | rm *.jar 33 | rm nonstop-deploy.sh 34 | 35 | echo "> tar 파일 이동" 36 | mv hashtagmap-web.tar $BASE/$WEB_BASE/ 37 | 38 | echo "> PROJECT_ROOT/script/hashtagmap-web/hashtagmap-web.tar 생성" 39 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/config/entity/BaseEntity.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.config.entity; 2 | 3 | import lombok.Getter; 4 | import org.springframework.data.annotation.CreatedDate; 5 | import org.springframework.data.annotation.LastModifiedDate; 6 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 7 | 8 | import javax.persistence.EntityListeners; 9 | import javax.persistence.GeneratedValue; 10 | import javax.persistence.GenerationType; 11 | import javax.persistence.Id; 12 | import javax.persistence.MappedSuperclass; 13 | import java.time.LocalDateTime; 14 | 15 | @Getter 16 | @EntityListeners(AuditingEntityListener.class) 17 | @MappedSuperclass 18 | public abstract class BaseEntity { 19 | @Id 20 | @GeneratedValue(strategy = GenerationType.IDENTITY) 21 | protected Long id; 22 | 23 | @CreatedDate 24 | protected LocalDateTime createdDate; 25 | 26 | @LastModifiedDate 27 | protected LocalDateTime modifiedDate; 28 | } 29 | -------------------------------------------------------------------------------- /hashtagmap-kakao-api/src/main/java/com/songpapeople/hashtagmap/kakaoapi/domain/rect/location/Coordinate.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakaoapi.domain.rect.location; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public abstract class Coordinate { 6 | protected final BigDecimal value; 7 | 8 | public Coordinate(BigDecimal value) { 9 | this.value = value; 10 | } 11 | 12 | protected static boolean isBetween(BigDecimal value, BigDecimal min, BigDecimal max) { 13 | return value.compareTo(max) <= 0 14 | && value.compareTo(min) >= 0; 15 | } 16 | 17 | public boolean isGreater(Coordinate compare) { 18 | return this.getValue() > compare.getValue(); 19 | } 20 | 21 | public boolean isLess(Coordinate compare) { 22 | return this.getValue() < compare.getValue(); 23 | } 24 | 25 | public double getValue() { 26 | return value.doubleValue(); 27 | } 28 | 29 | public abstract Coordinate forward(BigDecimal offset); 30 | } 31 | -------------------------------------------------------------------------------- /hashtagmap-event/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar { enabled = false } 2 | jar { enabled = true } 3 | 4 | dependencies { 5 | compile project(path:':hashtagmap-core', configuration: 'default') 6 | } 7 | 8 | jacocoTestCoverageVerification { 9 | def Qdomains = [] 10 | for (qPattern in "*.QA".."*.QZ") { 11 | Qdomains.add(qPattern + "*") 12 | } 13 | 14 | violationRules { 15 | rule { 16 | enabled = true 17 | element = 'CLASS' 18 | 19 | limit { 20 | counter = 'INSTRUCTION' 21 | value = 'COVEREDRATIO' 22 | minimum = 0.85 23 | } 24 | 25 | limit { 26 | counter = 'BRANCH' 27 | value = 'COVEREDRATIO' 28 | minimum = 0.85 29 | } 30 | 31 | excludes = [] + Qdomains 32 | } 33 | } 34 | } 35 | 36 | sonarqube { 37 | properties { 38 | property "sonar.exclusions", "**/*Test*.*, **/Q*.java, **/*Doc*.java" 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/request/api/district.js: -------------------------------------------------------------------------------- 1 | import { customWrapAxios } from "@/request"; 2 | 3 | const districtApi = { 4 | save(districtName) { 5 | const saveDto = { 6 | districtName: districtName 7 | }; 8 | return customWrapAxios().post("/districts", saveDto, { 9 | headers: { 10 | "Content-Type": "application/json" 11 | } 12 | }); 13 | }, 14 | findAll() { 15 | return customWrapAxios().get("/districts"); 16 | }, 17 | findAllDistrictName() { 18 | return customWrapAxios().get("/districts/names"); 19 | }, 20 | updateDistrictName(editDistrict) { 21 | return customWrapAxios().patch("/districts", editDistrict, { 22 | headers: { 23 | "Content-Type": "application/json" 24 | } 25 | }); 26 | }, 27 | delete(districtIds) { 28 | return customWrapAxios().delete("/districts", { 29 | headers: { 30 | "Content-Type": "application/json" 31 | }, 32 | data: districtIds 33 | }); 34 | } 35 | }; 36 | 37 | export default districtApi; 38 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/member/model/AdminMember.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.member.model; 2 | 3 | 4 | import com.songpapeople.hashtagmap.config.entity.BaseEntity; 5 | import lombok.AccessLevel; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.persistence.AttributeOverride; 10 | import javax.persistence.Column; 11 | import javax.persistence.Entity; 12 | import java.util.Objects; 13 | 14 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 15 | @Getter 16 | @AttributeOverride(name = "id", column = @Column(name = "ADMIN_MEMBER_ID")) 17 | @Entity 18 | public class AdminMember extends BaseEntity { 19 | private String nickName; 20 | private String password; 21 | 22 | public AdminMember(String nickName, String password) { 23 | this.nickName = nickName; 24 | this.password = password; 25 | } 26 | 27 | public boolean isNotMatchPassword(String password) { 28 | return !Objects.equals(this.password, password); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /hashtagmap-web/src/main/java/com/songpapeople/hashtagmap/api/TagLevelApiController.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.api; 2 | 3 | import com.songpapeople.hashtagmap.dto.TagLevelResponse; 4 | import com.songpapeople.hashtagmap.response.CustomResponse; 5 | import com.songpapeople.hashtagmap.service.TagLevelQueryService; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.ResponseStatus; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import java.util.List; 13 | 14 | @RequiredArgsConstructor 15 | @RestController 16 | public class TagLevelApiController { 17 | private final TagLevelQueryService tagLevelQueryService; 18 | 19 | @GetMapping("/tag-levels") 20 | @ResponseStatus(HttpStatus.OK) 21 | public CustomResponse> findTagLevels() { 22 | return CustomResponse.of(tagLevelQueryService.findTagLevels()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 13 | 17 | 18 | 19 | 26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /hashtagmap-batch/src/main/java/com/songpapeople/hashtagmap/instagram/repository/InstagramBatchQueryRepository.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.instagram.repository; 2 | 3 | import com.querydsl.jpa.impl.JPAQueryFactory; 4 | import com.songpapeople.hashtagmap.instagram.repository.dto.InstagramBatchDto; 5 | import com.songpapeople.hashtagmap.instagram.repository.dto.QInstagramBatchDto; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.List; 10 | 11 | import static com.songpapeople.hashtagmap.instagram.domain.model.QInstagram.instagram; 12 | 13 | @RequiredArgsConstructor 14 | @Repository 15 | public class InstagramBatchQueryRepository { 16 | private final JPAQueryFactory jpaQueryFactory; 17 | 18 | public List findAll() { 19 | return jpaQueryFactory.select(new QInstagramBatchDto( 20 | instagram.id, 21 | instagram.place.id 22 | )) 23 | .from(instagram) 24 | .fetch(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /hashtagmap-web/src/docs/asciidoc/api-guide.adoc: -------------------------------------------------------------------------------- 1 | ifndef::snippets[] 2 | :snippets: ../../../build/generated-snippets 3 | endif::[] 4 | :doctype: book 5 | :icons: font 6 | :source-highlighter: highlightjs 7 | :toc: left 8 | :toclevels: 4 9 | :sectlinks: 10 | :operation-http-request-title: 요청 예시 11 | :operation-http-response-title: 응답 예시 12 | :operation-response-fields-title: 응답 필드 상세설명 13 | 14 | [[resources]] 15 | = HashtagMap API Guide 16 | 17 | [[resources-maps]] 18 | == Map 19 | 20 | [[resources-maps-find-all-markers]] 21 | === 마커 전부 조회 22 | 23 | operation::maps/find-all-markers[snippets='http-request,http-response,response-fields'] 24 | 25 | [[resources-instagram]] 26 | == Instagram 27 | 28 | [[resources-instagram-get-post]] 29 | === Instagram Post 정보 요청 30 | 31 | operation::instagram/get-post[snippets='http-request,http-response,response-fields'] 32 | 33 | [[resources-tag-levels]] 34 | == TagLevel 35 | 36 | [[resources-tag-levels-findAll]] 37 | === 태그레벨 정보 조회 38 | 39 | operation::tag-levels/find-all[snippets='http-request,http-response,response-fields'] 40 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/taglevel/repository/TagLevelQueryRepository.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.taglevel.repository; 2 | 3 | import com.querydsl.jpa.impl.JPAQueryFactory; 4 | import com.songpapeople.hashtagmap.taglevel.model.QTagLevel; 5 | import com.songpapeople.hashtagmap.taglevel.model.TagLevel; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | @RequiredArgsConstructor 13 | @Repository 14 | public class TagLevelQueryRepository { 15 | private final JPAQueryFactory jpaQueryFactory; 16 | 17 | public List findFiveByModifiedDateOrderById() { 18 | QTagLevel tagLevel = QTagLevel.tagLevel; 19 | List recentTagLevels = jpaQueryFactory.selectFrom(tagLevel) 20 | .orderBy(tagLevel.id.desc()) 21 | .limit(5) 22 | .fetch(); 23 | Collections.reverse(recentTagLevels); 24 | return recentTagLevels; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /hashtagmap-kakao-api/src/main/java/com/songpapeople/hashtagmap/kakaoapi/domain/rect/RectDivider.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakaoapi.domain.rect; 2 | 3 | import com.songpapeople.hashtagmap.kakaoapi.domain.rect.location.Coordinate; 4 | 5 | import java.math.BigDecimal; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class RectDivider { 10 | public static List divide(Rect rect, BigDecimal offset) { 11 | Coordinate minLatitude = rect.getMinLatitude(); 12 | Coordinate maxLatitude = rect.getMaxLatitude(); 13 | Coordinate minLongitude = rect.getMinLongitude(); 14 | Coordinate maxLongitude = rect.getMaxLongitude(); 15 | List rects = new ArrayList<>(); 16 | 17 | for (Coordinate y = maxLongitude; y.isGreater(minLongitude); y = y.forward(offset)) { 18 | for (Coordinate x = minLatitude; x.isLess(maxLatitude); x = x.forward(offset)) { 19 | rects.add(Rect.byOffset(x, y, offset)); 20 | } 21 | } 22 | return rects; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/main/java/com/songpapeople/hashtagmap/crawler/RegexPattern.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.crawler; 2 | 3 | import com.songpapeople.hashtagmap.exception.CrawlerException; 4 | import com.songpapeople.hashtagmap.exception.CrawlerExceptionStatus; 5 | import lombok.Getter; 6 | 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | @Getter 11 | enum RegexPattern { 12 | HASH_TAG_COUNT(Pattern.compile("(\"edge_hashtag_to_media\":\\{\"count\"):([0-9]+)")), 13 | HASHTAG_POPULAR_POSTS_INFO(Pattern.compile("(\"edge_hashtag_to_top_posts\":)(.*)(,\"edge_hashtag_to_content_advisory\")")); 14 | 15 | private final Pattern pattern; 16 | 17 | RegexPattern(Pattern pattern) { 18 | this.pattern = pattern; 19 | } 20 | 21 | public String extract(String body) { 22 | Matcher matcher = this.pattern.matcher(body); 23 | if (matcher.find()) { 24 | return matcher.group(2); 25 | } 26 | throw new CrawlerException(CrawlerExceptionStatus.NOT_FOUND_MATCH_REGEX); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /hashtagmap-common/src/main/java/com/songpapeople/hashtagmap/exception/HashtagMapException.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.exception; 2 | 3 | import lombok.Getter; 4 | 5 | @Getter 6 | public class HashtagMapException extends RuntimeException { 7 | private final String errorCode; 8 | private final String errorMessage; 9 | 10 | public HashtagMapException(final String errorMessage, final String errorCode) { 11 | super(errorMessage); 12 | this.errorCode = errorCode; 13 | this.errorMessage = errorMessage; 14 | } 15 | 16 | public HashtagMapException(final String errorMessage, final String errorCode, final String detailMessage) { 17 | super(detailMessage); 18 | this.errorCode = errorCode; 19 | this.errorMessage = errorMessage; 20 | } 21 | 22 | public HashtagMapException(CommonExceptionStatus commonExceptionStatus, final String detailMessage) { 23 | super(detailMessage); 24 | this.errorCode = commonExceptionStatus.getCode(); 25 | this.errorMessage = commonExceptionStatus.getMessage(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/utils/constants.js: -------------------------------------------------------------------------------- 1 | export const KAKAO_MAP = { 2 | API_SRC_ADDRESS: "//dapi.kakao.com/v2/maps/sdk.js?autoload=false&appkey=", 3 | API_EXTENSION_LIBRARY: "&libraries=clusterer", 4 | JAMSIL_STATION_8_EXIT: { 5 | latitude: 37.513898, 6 | longitude: 127.101463, 7 | }, 8 | USER_MAKER_SIZE: 17, 9 | }; 10 | 11 | export const EVENT_TYPE = { 12 | CLICK: "click", 13 | DRAG_END: "dragend", 14 | ZOOM_CHANGED: "zoom_changed", 15 | CENTER_CHANGED: "center_changed", 16 | }; 17 | 18 | export const TAG_LEVEL = { 19 | LEVEL_FIVE: { 20 | LEVEL: 5, 21 | RGB: "rgb(65,12,162)", 22 | }, 23 | LEVEL_FOUR: { 24 | LEVEL: 4, 25 | RGB: "rgb(116,22,227)", 26 | }, 27 | LEVEL_THREE: { 28 | LEVEL: 3, 29 | RGB: "rgb(158,76,237)", 30 | }, 31 | LEVEL_TWO: { 32 | LEVEL: 2, 33 | RGB: "rgb(185,112,243)", 34 | }, 35 | LEVEL_ONE: { 36 | LEVEL: 1, 37 | RGB: "rgb(216,160,250)", 38 | }, 39 | }; 40 | 41 | export const CATEGORY = { 42 | CAFE: "카페", 43 | RESTAURANT: "음식점", 44 | }; 45 | 46 | export const VISIT_KEY = "hashtagmapvisitkey"; 47 | -------------------------------------------------------------------------------- /hashtagmap-instagram-service/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar { enabled = false } 2 | jar { enabled = true } 3 | 4 | dependencies { 5 | compile project(path:':hashtagmap-core', configuration: 'default') 6 | implementation project(path:':hashtagmap-common', configuration: 'default') 7 | compile project(path:':hashtagmap-instagram-crawler', configuration: 'default') 8 | implementation 'org.springframework.boot:spring-boot-starter' 9 | } 10 | 11 | jacocoTestCoverageVerification { 12 | def Qdomains = [] 13 | for (qPattern in "*.QA".."*.QZ") { 14 | Qdomains.add(qPattern + "*") 15 | } 16 | 17 | violationRules { 18 | rule { 19 | enabled = true 20 | element = 'BUNDLE' 21 | 22 | limit { 23 | counter = 'INSTRUCTION' 24 | value = 'COVEREDRATIO' 25 | minimum = 0.90 26 | } 27 | excludes = [] + Qdomains 28 | } 29 | } 30 | } 31 | 32 | sonarqube { 33 | properties { 34 | property "sonar.exclusions", "**/*Test*.*, **/Q*.java, **/*Data*.java" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import { register } from "register-service-worker"; 4 | 5 | if (process.env.NODE_ENV === "production") { 6 | register(`${process.env.BASE_URL}service-worker.js`, { 7 | ready() { 8 | console.log( 9 | "App is being served from cache by a service worker.\n" + 10 | "For more details, visit https://goo.gl/AFskqB", 11 | ); 12 | }, 13 | registered() { 14 | console.log("Service worker has been registered."); 15 | }, 16 | cached() { 17 | console.log("Content has been cached for offline use."); 18 | }, 19 | updatefound() { 20 | console.log("New content is downloading."); 21 | }, 22 | updated() { 23 | console.log("New content is available; please refresh."); 24 | }, 25 | offline() { 26 | console.log( 27 | "No internet connection found. App is running in offline mode.", 28 | ); 29 | }, 30 | error(error) { 31 | console.error("Error during service worker registration:", error); 32 | }, 33 | }); 34 | } 35 | -------------------------------------------------------------------------------- /hashtagmap-kakao-api/src/main/java/com/songpapeople/hashtagmap/kakaoapi/domain/exception/KakaoExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakaoapi.domain.exception; 2 | 3 | import org.springframework.http.client.ClientHttpResponse; 4 | import org.springframework.web.client.ResponseErrorHandler; 5 | 6 | import java.io.IOException; 7 | 8 | import static org.springframework.http.HttpStatus.Series.CLIENT_ERROR; 9 | import static org.springframework.http.HttpStatus.Series.SERVER_ERROR; 10 | 11 | public class KakaoExceptionHandler implements ResponseErrorHandler { 12 | @Override 13 | public boolean hasError(ClientHttpResponse httpResponse) throws IOException { 14 | return httpResponse.getStatusCode().series() == CLIENT_ERROR 15 | || httpResponse.getStatusCode().series() == SERVER_ERROR; 16 | } 17 | 18 | @Override 19 | public void handleError(ClientHttpResponse httpResponse) throws IOException { 20 | int statusCode = httpResponse.getStatusCode().value(); 21 | throw new KakaoException(statusCode, KakaoExceptionStatus.of(statusCode)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /hashtagmap-kakao-api/build.gradle: -------------------------------------------------------------------------------- 1 | bootJar { enabled = false } 2 | jar { enabled = true } 3 | 4 | dependencies { 5 | implementation project(path:':hashtagmap-common', configuration: 'default') 6 | compile 'org.springframework.boot:spring-boot-starter-web' 7 | compile 'com.fasterxml.jackson.core:jackson-databind:2.11.1' 8 | } 9 | 10 | task copyApiKey(type: Copy) { 11 | description = "Copy kakao api key from hashtagmap-secret" 12 | from '../hashtagmap-secret/kakao/application-kakao-security.yml' 13 | into 'src/main/resources/' 14 | } 15 | 16 | processResources.dependsOn 'copyApiKey' 17 | 18 | jacocoTestCoverageVerification { 19 | violationRules { 20 | rule { 21 | enabled = true 22 | element = 'BUNDLE' 23 | 24 | limit { 25 | counter = 'INSTRUCTION' 26 | value = 'COVEREDRATIO' 27 | minimum = 0.95 28 | } 29 | } 30 | } 31 | } 32 | 33 | sonarqube { 34 | properties { 35 | property "sonar.exclusions", "**/*Test*.*, **/Q*.java, **/*Doc*.java, **/*Fixture*.java" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /hashtagmap-web/src/main/java/com/songpapeople/hashtagmap/api/MapApiController.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.api; 2 | 3 | import com.songpapeople.hashtagmap.dto.MarkerResponse; 4 | import com.songpapeople.hashtagmap.response.CustomResponse; 5 | import com.songpapeople.hashtagmap.service.InstagramQueryService; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.ResponseStatus; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import java.util.List; 12 | 13 | @RestController 14 | public class MapApiController { 15 | private final InstagramQueryService instagramQueryService; 16 | 17 | public MapApiController(InstagramQueryService instagramQueryService) { 18 | this.instagramQueryService = instagramQueryService; 19 | } 20 | 21 | @GetMapping("/markers") 22 | @ResponseStatus(HttpStatus.OK) 23 | public CustomResponse> findAllMarkers() { 24 | return CustomResponse.of(instagramQueryService.findAllMarkers()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /hashtagmap-web/src/test/java/com/songpapeople/hashtagmap/controller/WebRestControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.controller; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.web.client.TestRestTemplate; 7 | import org.springframework.mock.env.MockEnvironment; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | public class WebRestControllerTest { 12 | 13 | @Autowired 14 | private TestRestTemplate restTemplate; 15 | 16 | @DisplayName("profile을 확인하는 api Test") 17 | @Test 18 | public void checkProfile () { 19 | String expectedProfile = "set1"; 20 | MockEnvironment env = new MockEnvironment(); 21 | env.addActiveProfile("db"); 22 | env.addActiveProfile(expectedProfile); 23 | 24 | WebRestController controller = new WebRestController(env); 25 | 26 | String profile = controller.getProfile(); 27 | 28 | assertThat(profile).isEqualTo(expectedProfile); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /hashtagmap-kakao-scheduler/src/main/java/com/songpapeople/hashtagmap/scheduler/config/KakaoSchedulerConfig.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.scheduler.config; 2 | 3 | import com.songpapeople.hashtagmap.scheduler.domain.CronPeriod; 4 | import com.songpapeople.hashtagmap.scheduler.domain.KakaoScheduler; 5 | import com.songpapeople.hashtagmap.scheduler.domain.KakaoSchedulerTask; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.scheduling.annotation.EnableScheduling; 9 | 10 | @EnableScheduling 11 | @Configuration 12 | public class KakaoSchedulerConfig { 13 | private static final String EXPRESSION = "0 0 2 1 * ?"; 14 | 15 | private final KakaoSchedulerTask kakaoSchedulerTask; 16 | 17 | public KakaoSchedulerConfig(KakaoSchedulerTask kakaoSchedulerTask) { 18 | this.kakaoSchedulerTask = kakaoSchedulerTask; 19 | } 20 | 21 | @Bean 22 | public KakaoScheduler kakaoPlaceScheduler() { 23 | return new KakaoScheduler(kakaoSchedulerTask::sourceEvent, new CronPeriod(EXPRESSION)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/store/modules/kakaoMap.js: -------------------------------------------------------------------------------- 1 | import { EVENT_TYPE } from "@/utils/constants"; 2 | 3 | export default { 4 | state: { 5 | kakaoMap: "", 6 | kakaoMapApi: "", 7 | mapBounds: {}, 8 | }, 9 | 10 | getters: { 11 | getKakaoMap: state => { 12 | return state.kakaoMap; 13 | }, 14 | getKakaoMapApi: state => { 15 | return state.kakaoMapApi; 16 | }, 17 | getBounds: state => { 18 | return state.mapBounds; 19 | }, 20 | }, 21 | 22 | mutations: { 23 | SET_KAKAO_MAP_API(state, kakaoMapApi) { 24 | state.kakaoMapApi = kakaoMapApi; 25 | }, 26 | SET_KAKAO_MAP(state, kakaoMap) { 27 | state.kakaoMap = kakaoMap; 28 | }, 29 | SET_BOUNDS_EVENT(state) { 30 | [ 31 | EVENT_TYPE.DRAG_END, 32 | EVENT_TYPE.ZOOM_CHANGED, 33 | EVENT_TYPE.CENTER_CHANGED, 34 | ].forEach(eventType => { 35 | state.kakaoMapApi.event.addListener(state.kakaoMap, eventType, () => { 36 | state.mapBounds = state.kakaoMap.getBounds(); 37 | }); 38 | }); 39 | }, 40 | }, 41 | 42 | actions: {}, 43 | }; 44 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | include: > 4 | db 5 | active: local 6 | datasource: 7 | hikari: 8 | pool-name: hashtagmap-pool 9 | minimum-idle: 5 10 | maximum-pool-size: 50 11 | connection-timeout: 3000 12 | max-lifetime: 1800000 13 | transaction-isolation: TRANSACTION_REPEATABLE_READ 14 | jpa: 15 | open-in-view: false 16 | 17 | --- 18 | spring: 19 | profiles: local 20 | datasource: 21 | hikari: 22 | driver-class-name: org.h2.Driver 23 | jdbc-url: jdbc:h2:mem:hashtagmap;mode=MYSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE; 24 | username: sa 25 | password: 26 | jpa: 27 | hibernate: 28 | ddl-auto: create-drop 29 | properties: 30 | hibernate: 31 | dialect: org.hibernate.dialect.MySQL5InnoDBDialect 32 | format_sql: true 33 | show-sql: true 34 | generate-ddl: true 35 | logging: 36 | level: 37 | org: 38 | hibernate: 39 | SQL: DEBUG 40 | type: 41 | descriptor: 42 | sql: 43 | BasicBinder: TRACE -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/test/java/com/songpapeople/hashtagmap/proxy/ProxiesTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.proxy; 2 | 3 | import org.junit.jupiter.api.DisplayName; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.Arrays; 7 | 8 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 9 | import static org.junit.jupiter.api.Assertions.assertAll; 10 | 11 | class ProxiesTest { 12 | @DisplayName("인자로 넘긴 인덱스에 해당하는 프록시로 잘 설정되는지 테스트") 13 | @Test 14 | void setHostAndPort() { 15 | Proxies proxies = new Proxies(Arrays.asList( 16 | new Proxy("123.123.123.1", "11"), 17 | new Proxy("123.123.123.2", "12"), 18 | new Proxy("123.123.123.3", "13") 19 | )); 20 | 21 | proxies.setHostAndPort(1); 22 | String actualHost = System.getProperty("http.proxyHost"); 23 | String actualPort = System.getProperty("http.proxyPort"); 24 | 25 | assertAll( 26 | () -> assertThat(actualHost).isEqualTo("123.123.123.2"), 27 | () -> assertThat(actualPort).isEqualTo("12") 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/member/api/MemberApiController.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.member.api; 2 | 3 | import com.songpapeople.hashtagmap.member.service.AdminMemberService; 4 | import com.songpapeople.hashtagmap.member.service.dto.LoginRequest; 5 | import com.songpapeople.hashtagmap.response.CustomResponse; 6 | import org.springframework.web.bind.annotation.PostMapping; 7 | import org.springframework.web.bind.annotation.RequestBody; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RestController 12 | @RequestMapping("/admin-member") 13 | public class MemberApiController { 14 | private final AdminMemberService adminMemberService; 15 | 16 | public MemberApiController(AdminMemberService adminMemberService) { 17 | this.adminMemberService = adminMemberService; 18 | } 19 | 20 | @PostMapping("/login") 21 | public CustomResponse adminLogin(@RequestBody LoginRequest loginRequest) { 22 | adminMemberService.validateMember(loginRequest); 23 | return CustomResponse.empty(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/request/api/kakao.js: -------------------------------------------------------------------------------- 1 | import { customWrapAxios } from "@/request"; 2 | 3 | const kakaoApi = { 4 | startScheduler() { 5 | return customWrapAxios().post("/kakao/scheduler/start"); 6 | }, 7 | stopScheduler() { 8 | return customWrapAxios().post("/kakao/scheduler/stop"); 9 | }, 10 | toggleSchedulerAutoRunnable(schedulerName) { 11 | const toggleDto = { name: schedulerName }; 12 | return customWrapAxios().patch("/kakao/scheduler/auto/toggle", toggleDto); 13 | }, 14 | getSchedulerAutoRunnableStatus(schedulerName) { 15 | return customWrapAxios().get("/kakao/scheduler/auto", { 16 | params: { 17 | name: schedulerName 18 | } 19 | }); 20 | }, 21 | changePeriodPeriod(expression) { 22 | return customWrapAxios().put("/kakao/scheduler/period", expression, { 23 | headers: { 24 | "Content-Type": "application/json" 25 | } 26 | }); 27 | }, 28 | getPeriodHistory() { 29 | return customWrapAxios().get("/kakao/scheduler/period"); 30 | }, 31 | getSchedulerStatus() { 32 | return customWrapAxios().get("/kakao/scheduler"); 33 | } 34 | }; 35 | 36 | export default kakaoApi; 37 | -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/test/java/com/songpapeople/hashtagmap/crawler/JsonExplorerTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.crawler; 2 | 3 | import com.google.gson.JsonElement; 4 | import com.google.gson.JsonParser; 5 | import org.junit.jupiter.api.DisplayName; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.lang.reflect.Constructor; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | class JsonExplorerTest { 13 | @DisplayName("json 데이터에서 key값으로 value값을 가져오는 기능 테스트") 14 | @Test 15 | void findByKey() { 16 | String json = "{\"node\":{\"name\":\"hs\",\"password\":\"1234\"},\"type\":\"string\"}"; 17 | JsonElement jsonElement = JsonParser.parseString(json); 18 | 19 | assertThat(JsonExplorer.findByKey(jsonElement, "name")).isEqualTo("hs"); 20 | } 21 | 22 | @DisplayName("유틸성 클래스라 private 생성자를 가지고 있다.") 23 | @Test 24 | void constructorTest() throws NoSuchMethodException { 25 | Constructor declaredConstructor = JsonExplorer.class.getDeclaredConstructor((Class[]) null); 26 | assertThat(declaredConstructor.isAccessible()).isFalse(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/store/modules/detailModal.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | export default { 4 | state: { 5 | detailModal: { 6 | isShow: false, 7 | placeName: "", 8 | placeUrl: "", 9 | hashtagCount: "", 10 | hashtagName: "", 11 | posts: [], 12 | }, 13 | }, 14 | 15 | getters: { 16 | getDetailModal: state => { 17 | return state.detailModal; 18 | }, 19 | }, 20 | 21 | mutations: { 22 | SET_DETAIL_MODAL(state, detailModal) { 23 | state.detailModal = detailModal; 24 | }, 25 | SET_DETAIL_MODAL_CLOSE(state) { 26 | state.detailModal.isShow = false; 27 | }, 28 | }, 29 | 30 | actions: { 31 | async setDetailModal({ commit }, place) { 32 | const posts = await axios.get(`/instagram/${place.instagramId}/post`); 33 | const detailModal = { 34 | isShow: true, 35 | placeName: place.placeName, 36 | placeUrl: place.placeUrl, 37 | hashtagCount: place.hashtagCount, 38 | hashtagName: place.hashtagName, 39 | posts: posts.data.data, 40 | }; 41 | commit("SET_DETAIL_MODAL", detailModal); 42 | }, 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /hashtagmap-web/src/main/java/com/songpapeople/hashtagmap/api/InstagramApiController.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.api; 2 | 3 | import com.songpapeople.hashtagmap.dto.InstagramPostResponse; 4 | import com.songpapeople.hashtagmap.response.CustomResponse; 5 | import com.songpapeople.hashtagmap.service.InstagramPostQueryService; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.web.bind.annotation.*; 8 | 9 | import java.util.List; 10 | 11 | @RestController 12 | @RequestMapping("/instagram") 13 | public class InstagramApiController { 14 | private final InstagramPostQueryService instagramPostQueryService; 15 | 16 | public InstagramApiController(InstagramPostQueryService instagramPostQueryService) { 17 | this.instagramPostQueryService = instagramPostQueryService; 18 | } 19 | 20 | @GetMapping("/{id}/post") 21 | @ResponseStatus(HttpStatus.OK) 22 | public CustomResponse> getInstagramPost(@PathVariable Long id) { 23 | List instagramPostResponses = instagramPostQueryService.findAllByInstagramId(id); 24 | return CustomResponse.of(instagramPostResponses); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /hashtagmap-slack/src/main/java/com/songpapeople/hashtagmap/web/filter/MultiReadHttpServletRequestFilter.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.web.filter; 2 | 3 | import com.songpapeople.hashtagmap.web.web.MultiReadHttpServletRequest; 4 | 5 | import javax.servlet.Filter; 6 | import javax.servlet.FilterChain; 7 | import javax.servlet.FilterConfig; 8 | import javax.servlet.ServletException; 9 | import javax.servlet.ServletRequest; 10 | import javax.servlet.ServletResponse; 11 | import javax.servlet.http.HttpServletRequest; 12 | import java.io.IOException; 13 | 14 | public class MultiReadHttpServletRequestFilter implements Filter { 15 | 16 | @Override 17 | public void init(final FilterConfig filterConfig) { 18 | } 19 | 20 | @Override 21 | public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { 22 | MultiReadHttpServletRequest multiReadHttpServletRequest = new MultiReadHttpServletRequest((HttpServletRequest) request, MultiReadHttpServletRequest.DEFAULT_ENCODING); 23 | chain.doFilter(multiReadHttpServletRequest, response); 24 | } 25 | 26 | public void destroy() { 27 | } 28 | } -------------------------------------------------------------------------------- /hashtagmap-admin/src/test/java/com/songpapeople/hashtagmap/docs/ApiDocumentUtils.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.docs; 2 | 3 | import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor; 4 | import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor; 5 | 6 | import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyUris; 7 | import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; 8 | import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; 9 | import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; 10 | 11 | public interface ApiDocumentUtils { 12 | 13 | static OperationRequestPreprocessor getDocumentRequest() { 14 | return preprocessRequest( 15 | modifyUris() 16 | .scheme("https") 17 | .host("admin.themiso.kr") 18 | .removePort(), 19 | prettyPrint()); 20 | } 21 | 22 | static OperationResponsePreprocessor getDocumentResponse() { 23 | return preprocessResponse(prettyPrint()); 24 | } 25 | } -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/main/java/com/songpapeople/hashtagmap/proxy/ProxiesFactory.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.proxy; 2 | 3 | import com.songpapeople.hashtagmap.crawler.Crawler; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | public class ProxiesFactory { 11 | private static final String FREE_PROXY_LIST_URL = "https://free-proxy-list.net/"; 12 | private static final String PROXY_REGEX = "(\\d+\\.\\d+\\.\\d+\\.\\d+):(\\d+)"; 13 | private static final int HOST_INDEX = 1; 14 | private static final int PORT_INDEX = 2; 15 | 16 | private ProxiesFactory() { 17 | } 18 | 19 | public static Proxies create() { 20 | Crawler crawler = new Crawler(); 21 | String body = crawler.crawl(FREE_PROXY_LIST_URL); 22 | Pattern pattern = Pattern.compile(PROXY_REGEX); 23 | Matcher matcher = pattern.matcher(body); 24 | List proxies = new ArrayList<>(); 25 | while (matcher.find()) { 26 | proxies.add(new Proxy(matcher.group(HOST_INDEX), matcher.group(PORT_INDEX))); 27 | } 28 | return new Proxies(proxies); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /hashtagmap-slack/src/main/java/com/songpapeople/hashtagmap/web/filter/LogbackMdcFilter.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.web.filter; 2 | 3 | import com.songpapeople.hashtagmap.web.model.MDCLogField; 4 | import org.slf4j.MDC; 5 | 6 | import javax.servlet.Filter; 7 | import javax.servlet.FilterChain; 8 | import javax.servlet.FilterConfig; 9 | import javax.servlet.ServletException; 10 | import javax.servlet.ServletRequest; 11 | import javax.servlet.ServletResponse; 12 | import javax.servlet.http.HttpServletRequest; 13 | import java.io.IOException; 14 | 15 | public class LogbackMdcFilter implements Filter { 16 | @Override 17 | public void init(FilterConfig filterConfig) { 18 | } 19 | 20 | @Override 21 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 22 | HttpServletRequest httpServletRequest = (HttpServletRequest) request; 23 | MDCLogField.putMDCLogFields(httpServletRequest); 24 | 25 | try { 26 | chain.doFilter(request, response); 27 | } finally { 28 | MDC.clear(); 29 | } 30 | } 31 | 32 | @Override 33 | public void destroy() { 34 | } 35 | } -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/taglevel/service/TagLevelCommandService.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.taglevel.service; 2 | 3 | import com.songpapeople.hashtagmap.instagram.domain.model.HashtagCounts; 4 | import com.songpapeople.hashtagmap.instagram.domain.repository.InstagramQueryRepository; 5 | import com.songpapeople.hashtagmap.taglevel.model.TagLevels; 6 | import com.songpapeople.hashtagmap.taglevel.repository.TagLevelRepository; 7 | import lombok.RequiredArgsConstructor; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.transaction.annotation.Transactional; 11 | 12 | @Slf4j 13 | @RequiredArgsConstructor 14 | @Service 15 | public class TagLevelCommandService { 16 | private final InstagramQueryRepository instagramQueryRepository; 17 | private final TagLevelRepository tagLevelRepository; 18 | 19 | @Transactional 20 | public void update() { 21 | TagLevels tagLevels = new TagLevels(tagLevelRepository.findAll()); 22 | HashtagCounts hashtagCounts = new HashtagCounts(instagramQueryRepository.findAllHashtagCountByOrderAsc()); 23 | tagLevels.update(hashtagCounts); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /hashtagmap-web/src/test/java/com/songpapeople/hashtagmap/docs/ApiDocumentUtils.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.docs; 2 | 3 | import org.springframework.restdocs.operation.preprocess.OperationRequestPreprocessor; 4 | import org.springframework.restdocs.operation.preprocess.OperationResponsePreprocessor; 5 | 6 | import static org.springframework.restdocs.operation.preprocess.Preprocessors.modifyUris; 7 | import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; 8 | import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; 9 | import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; 10 | 11 | public interface ApiDocumentUtils { 12 | 13 | static OperationRequestPreprocessor getDocumentRequest() { 14 | return preprocessRequest( 15 | modifyUris() 16 | .scheme("https") 17 | .host("admin.themiso.kr") 18 | .removePort(), 19 | prettyPrint()); 20 | } 21 | 22 | static OperationResponsePreprocessor getDocumentResponse() { 23 | return preprocessResponse(prettyPrint()); 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /hashtagmap-kakao-api/src/main/java/com/songpapeople/hashtagmap/kakaoapi/domain/dto/Meta.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakaoapi.domain.dto; 2 | 3 | import com.fasterxml.jackson.databind.PropertyNamingStrategy; 4 | import com.fasterxml.jackson.databind.annotation.JsonNaming; 5 | import lombok.AccessLevel; 6 | import lombok.Builder; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | 10 | /** 11 | * totalCount: 검색어에 검색된 문서 수 12 | * pageableCount: total_count 중 노출 가능 문서 수, 최대 45 13 | * isEnd: 현재 페이지가 마지막 페이지인지 여부 14 | * 값이 false면 page를 증가시켜 다음 페이지를 요청할 수 있음 15 | * sameName: 질의어의 지역 및 키워드 분석 정보 16 | */ 17 | 18 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 19 | @Getter 20 | @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class) 21 | public class Meta { 22 | private Integer totalCount; 23 | private Integer pageableCount; 24 | private Boolean isEnd; 25 | private RegionInfo sameName; 26 | 27 | @Builder 28 | public Meta(Integer totalCount, Integer pageableCount, Boolean isEnd, RegionInfo sameName) { 29 | this.totalCount = totalCount; 30 | this.pageableCount = pageableCount; 31 | this.isEnd = isEnd; 32 | this.sameName = sameName; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hashtagmap-web/src/main/java/com/songpapeople/hashtagmap/repository/InstagramPostWebQueryRepository.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.repository; 2 | 3 | import com.querydsl.jpa.impl.JPAQueryFactory; 4 | import com.songpapeople.hashtagmap.dto.InstagramPostResponse; 5 | import com.songpapeople.hashtagmap.dto.QInstagramPostResponse; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.List; 10 | 11 | import static com.songpapeople.hashtagmap.instagram.domain.model.QInstagram.instagram; 12 | import static com.songpapeople.hashtagmap.instagram.domain.model.QInstagramPost.instagramPost; 13 | 14 | @RequiredArgsConstructor 15 | @Repository 16 | public class InstagramPostWebQueryRepository { 17 | private final JPAQueryFactory jpaQueryFactory; 18 | 19 | public List findAllByInstagramId(Long id) { 20 | return jpaQueryFactory.select(new QInstagramPostResponse( 21 | instagramPost.id, 22 | instagramPost.imageUrl, 23 | instagramPost.postUrl 24 | )) 25 | .from(instagramPost) 26 | .where(instagram.id.eq(id)) 27 | .fetch(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/store/modules/modal.js: -------------------------------------------------------------------------------- 1 | export default { 2 | namespaced: true, 3 | 4 | state: { 5 | status: [ 6 | { 7 | modalName: "", 8 | active: false 9 | } 10 | ], 11 | objectData: { 12 | type: Object 13 | }, 14 | arrayData: { 15 | type: Array 16 | } 17 | }, 18 | 19 | getters: { 20 | isActive: state => modalName => { 21 | const status = state.status.find( 22 | status => status.modalName === modalName 23 | ); 24 | if (!status) { 25 | return false; 26 | } 27 | return status.active; 28 | } 29 | }, 30 | 31 | mutations: { 32 | ACTIVATE_MODAL: (state, modalName) => { 33 | let status = state.status.find(status => status.modalName === modalName); 34 | if (!status) { 35 | status = { modalName: modalName, active: false }; 36 | state.status.push(status); 37 | } 38 | status.active = true; 39 | }, 40 | DEACTIVATE_MODAL: (state, modalName) => { 41 | const status = state.status.find( 42 | status => status.modalName === modalName 43 | ); 44 | if (!status) { 45 | return; 46 | } 47 | status.active = false; 48 | } 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/district/service/dto/ZoneUpdateDto.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.district.service.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.validation.constraints.NotNull; 9 | 10 | @Getter 11 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 12 | public class ZoneUpdateDto { 13 | @NotNull 14 | private Long zoneId; 15 | private String districtName; 16 | private String topLeftLatitude; 17 | private String topLeftLongitude; 18 | private String bottomRightLatitude; 19 | private String bottomRightLongitude; 20 | 21 | @Builder 22 | public ZoneUpdateDto(@NotNull final Long zoneId, final String districtName, final String topLeftLatitude, final String topLeftLongitude, final String bottomRightLatitude, final String bottomRightLongitude) { 23 | this.zoneId = zoneId; 24 | this.districtName = districtName; 25 | this.topLeftLatitude = topLeftLatitude; 26 | this.topLeftLongitude = topLeftLongitude; 27 | this.bottomRightLatitude = bottomRightLatitude; 28 | this.bottomRightLongitude = bottomRightLongitude; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/instagram/api/InstagramSchedulerApiController.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.instagram.api; 2 | 3 | import com.songpapeople.hashtagmap.response.CustomResponse; 4 | import com.songpapeople.hashtagmap.service.InstagramService; 5 | import com.songpapeople.hashtagmap.taglevel.service.TagLevelCommandService; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.web.bind.annotation.PutMapping; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.ResponseStatus; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | @RequiredArgsConstructor 14 | @RestController 15 | @RequestMapping("/instagram-scheduler") 16 | public class InstagramSchedulerApiController { 17 | private final InstagramService instagramService; 18 | private final TagLevelCommandService tagLevelCommandService; 19 | 20 | @PutMapping 21 | @ResponseStatus(HttpStatus.OK) 22 | public CustomResponse update() { 23 | instagramService.update(); 24 | tagLevelCommandService.update(); 25 | return CustomResponse.empty(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/test/java/com/songpapeople/hashtagmap/util/PlaceNameParserTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.util; 2 | 3 | import org.assertj.core.api.Assertions; 4 | import org.junit.jupiter.api.DisplayName; 5 | import org.junit.jupiter.api.Test; 6 | import org.junit.jupiter.params.ParameterizedTest; 7 | import org.junit.jupiter.params.provider.CsvSource; 8 | 9 | import java.lang.reflect.Constructor; 10 | 11 | import static org.assertj.core.api.AssertionsForClassTypes.assertThat; 12 | 13 | class PlaceNameParserTest { 14 | @DisplayName("가게 이름 데이터 가공하기") 15 | @ParameterizedTest 16 | @CsvSource({"스타벅스 강남역점,스타벅스강남역", "피자나라 치킨공주,피자나라치킨공주", "스타벅스,스타벅스", 17 | "홍콩반점,홍콩반점", "스타벅스 본점,스타벅스", "스타벅스 직영점,스타벅스"}) 18 | void parsePlaceName(String placeName, String expected) { 19 | assertThat(PlaceNameParser.parsePlaceName(placeName)).isEqualTo(expected); 20 | } 21 | 22 | @DisplayName("유틸성 클래스라 private 생성자를 가지고 있다.") 23 | @Test 24 | public void constructorTest() throws NoSuchMethodException { 25 | Constructor declaredConstructor = PlaceNameParser.class.getDeclaredConstructor((Class[]) null); 26 | Assertions.assertThat(declaredConstructor.isAccessible()).isFalse(); 27 | } 28 | } -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/place/domain/model/District.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.place.domain.model; 2 | 3 | import com.songpapeople.hashtagmap.config.entity.BaseEntity; 4 | import lombok.AccessLevel; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import org.springframework.util.StringUtils; 8 | 9 | import javax.persistence.AttributeOverride; 10 | import javax.persistence.Column; 11 | import javax.persistence.Entity; 12 | import javax.persistence.Table; 13 | import javax.persistence.UniqueConstraint; 14 | 15 | @Getter 16 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 17 | @AttributeOverride(name = "id", column = @Column(name = "DISTRICT_ID")) 18 | @Table(uniqueConstraints = @UniqueConstraint(name = "UK_DISTRICT_NAME", columnNames = "DISTRICT_NAME")) 19 | @Entity 20 | public class District extends BaseEntity { 21 | @Column(name = "DISTRICT_NAME") 22 | private String districtName; 23 | 24 | public District(final String districtName) { 25 | this.districtName = districtName.trim(); 26 | } 27 | 28 | public void update(final String districtName) { 29 | if (!StringUtils.isEmpty(districtName)) { 30 | this.districtName = districtName; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /hashtagmap-web/src/main/java/com/songpapeople/hashtagmap/repository/InstagramWebQueryRepository.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.repository; 2 | 3 | import com.querydsl.jpa.impl.JPAQueryFactory; 4 | import com.songpapeople.hashtagmap.dto.InstagramForMarker; 5 | import com.songpapeople.hashtagmap.dto.QInstagramForMarker; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.List; 10 | 11 | import static com.songpapeople.hashtagmap.instagram.domain.model.QInstagram.instagram; 12 | 13 | @RequiredArgsConstructor 14 | @Repository 15 | public class InstagramWebQueryRepository { 16 | private final JPAQueryFactory jpaQueryFactory; 17 | 18 | public List findAllFetch() { 19 | return jpaQueryFactory.select(new QInstagramForMarker( 20 | instagram.id, 21 | instagram.hashtagCount, 22 | instagram.hashtagName, 23 | instagram.place.placeName, 24 | instagram.place.placeUrl, 25 | instagram.place.kakaoId, 26 | instagram.place.location, 27 | instagram.place.category 28 | )) 29 | .from(instagram) 30 | .fetch(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /hashtagmap-web/src/test/java/com/songpapeople/hashtagmap/docs/ApiDocument.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.docs; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 6 | import org.springframework.restdocs.RestDocumentationContextProvider; 7 | import org.springframework.restdocs.RestDocumentationExtension; 8 | import org.springframework.test.web.servlet.MockMvc; 9 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 10 | import org.springframework.web.context.WebApplicationContext; 11 | 12 | import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration; 13 | 14 | @ExtendWith(RestDocumentationExtension.class) 15 | @AutoConfigureMockMvc 16 | public class ApiDocument { 17 | protected MockMvc mockMvc; 18 | 19 | @BeforeEach 20 | public void setUp(WebApplicationContext webApplicationContext, 21 | RestDocumentationContextProvider restDocumentation) { 22 | this.mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext) 23 | .apply(documentationConfiguration(restDocumentation)) 24 | .build(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /hashtagmap-event/src/main/java/com/songpapeople/hashtagmap/event/process/EventBrokerGroup.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.event.process; 2 | 3 | import com.songpapeople.hashtagmap.event.message.Event; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.Optional; 8 | import java.util.Set; 9 | 10 | public class EventBrokerGroup { 11 | private final Map, EventBroker> brokers = new HashMap<>(); 12 | 13 | public EventBrokerGroup() { 14 | for (Class eventType : EventType.getTypes()) { 15 | brokers.put(eventType, new EventBroker<>()); 16 | } 17 | } 18 | 19 | @SuppressWarnings("unchecked") 20 | public void push(E event) { 21 | EventBroker eventBroker = (EventBroker) brokers.get(event.getClass()); 22 | eventBroker.push(event); 23 | } 24 | 25 | @SuppressWarnings("unchecked") 26 | public Optional poll(Class type) { 27 | EventBroker eventBroker = (EventBroker) brokers.get(type); 28 | return Optional.ofNullable(eventBroker.poll()); 29 | } 30 | 31 | public Set> keySet() { 32 | return this.brokers.keySet(); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/instagram/domain/model/InstagramPost.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.instagram.domain.model; 2 | 3 | import com.songpapeople.hashtagmap.config.entity.BaseEntity; 4 | import lombok.AccessLevel; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.persistence.AttributeOverride; 10 | import javax.persistence.Column; 11 | import javax.persistence.Entity; 12 | import javax.persistence.FetchType; 13 | import javax.persistence.JoinColumn; 14 | import javax.persistence.ManyToOne; 15 | 16 | @Getter 17 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 18 | @AttributeOverride(name = "id", column = @Column(name = "INSTAGRAM_POST_ID")) 19 | @Entity 20 | public class InstagramPost extends BaseEntity { 21 | @ManyToOne(fetch = FetchType.LAZY) 22 | @JoinColumn(name = "INSTAGRAM_ID") 23 | private Instagram instagram; 24 | 25 | private String postUrl; 26 | 27 | @Column(columnDefinition = "TEXT") 28 | private String imageUrl; 29 | 30 | @Builder 31 | public InstagramPost(Instagram instagram, String postUrl, String imageUrl) { 32 | this.instagram = instagram; 33 | this.postUrl = postUrl; 34 | this.imageUrl = imageUrl; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/place/domain/model/Location.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.place.domain.model; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Generated; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | import javax.persistence.Embeddable; 10 | import javax.persistence.Embedded; 11 | import java.util.Objects; 12 | 13 | @Getter 14 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 15 | @AllArgsConstructor 16 | @Embeddable 17 | public class Location { 18 | @Embedded 19 | private Point point; 20 | private String roadAddressName; 21 | 22 | @Generated 23 | @Override 24 | public boolean equals(Object o) { 25 | if (this == o) { 26 | return true; 27 | } 28 | if (o == null || getClass() != o.getClass()) { 29 | return false; 30 | } 31 | Location location = (Location) o; 32 | return Objects.equals(this.getPoint(), location.getPoint()) 33 | && Objects.equals(this.getRoadAddressName(), location.getRoadAddressName()); 34 | } 35 | 36 | @Generated 37 | @Override 38 | public int hashCode() { 39 | return Objects.hash(this.getPoint(), this.getRoadAddressName()); 40 | } 41 | } -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/main/java/com/songpapeople/hashtagmap/util/PlaceNameType.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.util; 2 | 3 | import java.util.Arrays; 4 | import java.util.Optional; 5 | import java.util.function.Function; 6 | 7 | public enum PlaceNameType { 8 | ORIGINAL_BRANCH("본점", 9 | placeName -> placeName.substring(0, placeName.length() - 2)), 10 | DIRECT_BRANCH("직영점", 11 | placeName -> placeName.substring(0, placeName.length() - 3)), 12 | CHINESE_BRANCH("반점", 13 | placeName -> placeName), 14 | BRANCH("점", 15 | placeName -> placeName.substring(0, placeName.length() - 1)); 16 | 17 | private String suffix; 18 | private Function expression; 19 | 20 | PlaceNameType(String suffix, Function expression) { 21 | this.suffix = suffix; 22 | this.expression = expression; 23 | } 24 | 25 | public static Optional find(String placeName) { 26 | return Arrays.stream(PlaceNameType.values()) 27 | .filter(placeNameType -> placeName.endsWith(placeNameType.suffix)) 28 | .findFirst(); 29 | } 30 | 31 | public String parsePlaceName(String placeName) { 32 | return expression.apply(placeName); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/components/detail-modal/Posts.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 37 | 38 | 54 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/components/place/PlaceCategories.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 27 | 28 | 57 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/blacklist/service/dto/AbnormalInstagramDto.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.blacklist.service.dto; 2 | 3 | import com.songpapeople.hashtagmap.instagram.domain.model.Instagram; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | 7 | @NoArgsConstructor 8 | @Getter 9 | public class AbnormalInstagramDto { 10 | private String kakaoId; 11 | private String placeName; 12 | private String hashtagName; 13 | private String roadAddressName; 14 | private Long hashtagCount; 15 | 16 | private AbnormalInstagramDto(String kakaoId, String placeName, String hashtagName, String roadAddressName, Long hashtagCount) { 17 | this.kakaoId = kakaoId; 18 | this.placeName = placeName; 19 | this.hashtagName = hashtagName; 20 | this.roadAddressName = roadAddressName; 21 | this.hashtagCount = hashtagCount; 22 | } 23 | 24 | public static AbnormalInstagramDto of(Instagram instagram) { 25 | return new AbnormalInstagramDto( 26 | instagram.getKakaoId(), 27 | instagram.getPlaceName(), 28 | instagram.getHashtagName(), 29 | instagram.getRoadAddressName(), 30 | instagram.getHashtagCount() 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/store/modules/tagLevel.js: -------------------------------------------------------------------------------- 1 | import tagLevelApi from "@/request/api/tagLevel.js"; 2 | 3 | export default { 4 | namespaced: true, 5 | state: { 6 | tagLevels: [ 7 | { 8 | tagLevel: "", 9 | minHashtagCount: "", 10 | maxHashtagCount: "" 11 | } 12 | ] 13 | }, 14 | 15 | getters: { 16 | getTagLevels: state => { 17 | return state.tagLevels; 18 | } 19 | }, 20 | 21 | mutations: { 22 | ADD_TAG_LEVEL: (state, tagLevel) => { 23 | state.tagLevels.push(tagLevel); 24 | }, 25 | CLEAR_TAG_LEVELS: state => { 26 | state.tagLevels = []; 27 | } 28 | }, 29 | 30 | actions: { 31 | updateTagLevel: async ({ dispatch }) => { 32 | try { 33 | const response = await tagLevelApi.update(); 34 | dispatch("fetchTagLevels"); 35 | return response; 36 | } catch (error) { 37 | return error; 38 | } 39 | }, 40 | 41 | fetchTagLevels: async ({ commit }) => { 42 | try { 43 | const response = await tagLevelApi.findAll(); 44 | commit("CLEAR_TAG_LEVELS"); 45 | response.body.data.map(tagLevel => commit("ADD_TAG_LEVEL", tagLevel)); 46 | return response; 47 | } catch (error) { 48 | return error; 49 | } 50 | } 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/district/service/dto/ZoneSaveDto.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.district.service.dto; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.validation.constraints.NotBlank; 9 | import javax.validation.constraints.NotNull; 10 | 11 | @Getter 12 | @NoArgsConstructor(access = AccessLevel.PRIVATE) 13 | public class ZoneSaveDto { 14 | @NotNull 15 | private String districtName; 16 | @NotBlank 17 | private String topLeftLatitude; 18 | @NotBlank 19 | private String topLeftLongitude; 20 | @NotBlank 21 | private String bottomRightLatitude; 22 | @NotBlank 23 | private String bottomRightLongitude; 24 | 25 | @Builder 26 | public ZoneSaveDto(@NotNull final String districtName, @NotBlank final String topLeftLatitude, @NotBlank final String topLeftLongitude, @NotBlank final String bottomRightLatitude, @NotBlank final String bottomRightLongitude) { 27 | this.districtName = districtName; 28 | this.topLeftLatitude = topLeftLatitude; 29 | this.topLeftLongitude = topLeftLongitude; 30 | this.bottomRightLatitude = bottomRightLatitude; 31 | this.bottomRightLongitude = bottomRightLongitude; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/main/java/com/songpapeople/hashtagmap/crawler/InstagramCrawler.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.crawler; 2 | 3 | import com.songpapeople.hashtagmap.dto.CrawlingDto; 4 | import com.songpapeople.hashtagmap.dto.PostDtos; 5 | import com.songpapeople.hashtagmap.util.PlaceNameParser; 6 | 7 | public class InstagramCrawler { 8 | private static final String INSTAGRAM_URL_FORMAT = "https://www.instagram.com/explore/tags/%s/?hl=ko"; 9 | 10 | private final Crawler crawler; 11 | 12 | public InstagramCrawler(Crawler crawler) { 13 | this.crawler = crawler; 14 | } 15 | 16 | private CrawlingDto createCrawlingDto(String hashtagName, String body) { 17 | InstaCrawlingResult instaCrawlingResult = new InstaCrawlingResult(body); 18 | String hashTagCount = instaCrawlingResult.findHashTagCount(); 19 | PostDtos postDtos = instaCrawlingResult.findPostDtos(); 20 | return CrawlingDto.of(hashtagName, hashTagCount, postDtos); 21 | } 22 | 23 | public CrawlingDto crawler(String crawlingName) { 24 | String parsedHashtagName = PlaceNameParser.parsePlaceName(crawlingName); 25 | String body = crawler.crawl(String.format(INSTAGRAM_URL_FORMAT, parsedHashtagName)); 26 | return createCrawlingDto(parsedHashtagName, body); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /hashtagmap-slack/src/main/java/com/songpapeople/hashtagmap/web/config/LogbackContextConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.web.config; 2 | 3 | import ch.qos.logback.classic.LoggerContext; 4 | import com.songpapeople.hashtagmap.web.appender.LogbackAppender; 5 | import com.songpapeople.hashtagmap.web.property.LogProperties; 6 | import lombok.RequiredArgsConstructor; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.beans.factory.InitializingBean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.core.env.Environment; 11 | 12 | @Configuration 13 | @RequiredArgsConstructor 14 | public class LogbackContextConfiguration implements InitializingBean { 15 | private final LogProperties logProperties; 16 | private final Environment env; 17 | 18 | @Override 19 | public void afterPropertiesSet() { 20 | LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); 21 | 22 | LogbackAppender logBackAppender = new LogbackAppender(logProperties, String.join(",", env.getActiveProfiles())); 23 | 24 | logBackAppender.setContext(loggerContext); 25 | logBackAppender.setName("logbackAppender"); 26 | logBackAppender.start(); 27 | loggerContext.getLogger("ROOT").addAppender(logBackAppender); 28 | } 29 | } -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/event/model/EventHistory.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.event.model; 2 | 3 | import com.songpapeople.hashtagmap.config.entity.BaseEntity; 4 | import lombok.AccessLevel; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import javax.persistence.AttributeOverride; 9 | import javax.persistence.Column; 10 | import javax.persistence.DiscriminatorColumn; 11 | import javax.persistence.Entity; 12 | import javax.persistence.EnumType; 13 | import javax.persistence.Enumerated; 14 | import javax.persistence.Inheritance; 15 | import javax.persistence.InheritanceType; 16 | 17 | @Getter 18 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 19 | @Entity 20 | @Inheritance(strategy = InheritanceType.JOINED) 21 | @DiscriminatorColumn 22 | @AttributeOverride(name = "id", column = @Column(name = "EVENT_ID")) 23 | public abstract class EventHistory extends BaseEntity { 24 | @Enumerated(EnumType.STRING) 25 | protected EventStatus eventStatus; 26 | 27 | public EventHistory(final EventStatus eventStatus) { 28 | this.eventStatus = eventStatus; 29 | } 30 | 31 | public void fail() { 32 | this.eventStatus = EventStatus.FAIL; 33 | } 34 | 35 | public void success() { 36 | this.eventStatus = EventStatus.SUCCESS; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /hashtagmap-core/src/main/java/com/songpapeople/hashtagmap/instagram/domain/repository/InstagramQueryRepository.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.instagram.domain.repository; 2 | 3 | import com.querydsl.jpa.impl.JPAQueryFactory; 4 | import com.songpapeople.hashtagmap.instagram.domain.model.Instagram; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import java.util.List; 9 | 10 | import static com.songpapeople.hashtagmap.instagram.domain.model.QInstagram.instagram; 11 | import static com.songpapeople.hashtagmap.place.domain.model.QPlace.place; 12 | 13 | @RequiredArgsConstructor 14 | @Repository 15 | public class InstagramQueryRepository { 16 | private final JPAQueryFactory jpaQueryFactory; 17 | 18 | public List findAllHashtagCountByOrderAsc() { 19 | return jpaQueryFactory.select(instagram.hashtagCount) 20 | .from(instagram) 21 | .orderBy(instagram.hashtagCount.asc()) 22 | .fetch(); 23 | } 24 | 25 | public Instagram findByKakaoId(String kakaoId) { 26 | return jpaQueryFactory.selectFrom(instagram) 27 | .innerJoin(instagram.place, place) 28 | .fetchJoin() 29 | .where(place.kakaoId.eq(kakaoId)) 30 | .fetchFirst(); 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /hashtagmap-kakao-scheduler/src/test/java/com/songpapeople/hashtagmap/scheduler/domain/CronPeriodTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.scheduler.domain; 2 | 3 | import com.songpapeople.hashtagmap.scheduler.exception.KakaoSchedulerException; 4 | import com.songpapeople.hashtagmap.scheduler.exception.KakaoSchedulerExceptionStatus; 5 | import org.junit.jupiter.api.DisplayName; 6 | import org.junit.jupiter.params.ParameterizedTest; 7 | import org.junit.jupiter.params.provider.ValueSource; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | import static org.junit.jupiter.api.Assertions.assertThrows; 11 | 12 | class CronPeriodTest { 13 | 14 | @DisplayName("(예외) 잘못된 정규화식을 입력했을 때") 15 | @ParameterizedTest 16 | @ValueSource(strings = {"", "* * * * * * /"}) 17 | public void CronPeriodTest(String wronExpression) { 18 | KakaoSchedulerException excpetion = assertThrows(KakaoSchedulerException.class, 19 | () -> new CronPeriod(wronExpression)); 20 | 21 | KakaoSchedulerExceptionStatus kakaoSchedulerExceptionStatus = KakaoSchedulerExceptionStatus.INVALID_PERIOD_EXPRESSION; 22 | assertThat(excpetion.getErrorCode()).isEqualTo(kakaoSchedulerExceptionStatus.getCode()); 23 | assertThat(excpetion.getMessage()).isEqualTo(kakaoSchedulerExceptionStatus.getMessage()); 24 | } 25 | } -------------------------------------------------------------------------------- /hashtagmap-admin/front/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueRouter from "vue-router"; 3 | import KakaoScheduler from "../views/KakaoScheduler"; 4 | import InstagramScheduler from "../views/InstagramScheduler"; 5 | import TagLevel from "../views/TagLevel"; 6 | import Home from "../views/Home"; 7 | import DistrictContainer from "../views/district/DistrictContainer"; 8 | import BlackListManager from "@/views/BlackListManager"; 9 | 10 | Vue.use(VueRouter); 11 | 12 | const routes = [ 13 | { 14 | path: "/", 15 | name: "Home", 16 | component: Home 17 | }, 18 | { 19 | path: "/kakao-scheduler", 20 | name: "KakaoScheduler", 21 | component: KakaoScheduler 22 | }, 23 | { 24 | path: "/instagram-scheduler", 25 | name: "InstagramScheduler", 26 | component: InstagramScheduler 27 | }, 28 | { 29 | path: "/tag-level", 30 | name: "TagLevel", 31 | component: TagLevel 32 | }, 33 | { 34 | path: "/district-manage", 35 | name: "DistrictContainer", 36 | component: DistrictContainer 37 | }, 38 | { 39 | path: "/blacklist-manager", 40 | name: "BlackListManager", 41 | component: BlackListManager 42 | } 43 | ]; 44 | 45 | const router = new VueRouter({ 46 | mode: "history", 47 | base: process.env.BASE_URL, 48 | routes 49 | }); 50 | 51 | export default router; 52 | -------------------------------------------------------------------------------- /hashtagmap-instagram-crawler/src/test/java/com/songpapeople/hashtagmap/crawler/InstaCrawlingResultTest.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.crawler; 2 | 3 | import com.songpapeople.hashtagmap.MockDataFactory; 4 | import com.songpapeople.hashtagmap.dto.PostDtos; 5 | import org.junit.jupiter.api.DisplayName; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.io.IOException; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | class InstaCrawlingResultTest { 13 | @DisplayName("body에서 인기게시물 정보를 가져와 PostDtos를 만드는 기능 테스트") 14 | @Test 15 | void findPostDtos() throws IOException { 16 | String body = MockDataFactory.createBody(); 17 | InstaCrawlingResult instaCrawlingResult = new InstaCrawlingResult(body); 18 | 19 | PostDtos postDtos = instaCrawlingResult.findPostDtos(); 20 | 21 | assertThat(postDtos.size()).isEqualTo(PostDtos.POPULAR_POST_SIZE); 22 | } 23 | 24 | @DisplayName("body에서 hashtagCount를 찾는 테스트") 25 | @Test 26 | void findHashTagCount() throws IOException { 27 | String body = MockDataFactory.createBody(); 28 | InstaCrawlingResult instaCrawlingResult = new InstaCrawlingResult(body); 29 | 30 | String hashTagCount = instaCrawlingResult.findHashTagCount(); 31 | 32 | assertThat(hashTagCount).isNotNull(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /hashtagmap-admin/src/main/java/com/songpapeople/hashtagmap/instagram/repository/InstagramAdminQueryRepository.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.instagram.repository; 2 | 3 | import com.querydsl.jpa.impl.JPAQueryFactory; 4 | import com.songpapeople.hashtagmap.instagram.dto.InstagramForBlacklist; 5 | import com.songpapeople.hashtagmap.instagram.dto.QInstagramForBlacklist; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.util.List; 10 | 11 | import static com.songpapeople.hashtagmap.instagram.domain.model.QInstagram.instagram; 12 | 13 | @RequiredArgsConstructor 14 | @Repository 15 | public class InstagramAdminQueryRepository { 16 | private final JPAQueryFactory jpaQueryFactory; 17 | 18 | public List findAllOrderByHashtagCountAndLimitBy(int limit) { 19 | return jpaQueryFactory.select(new QInstagramForBlacklist( 20 | instagram.hashtagName, 21 | instagram.hashtagCount, 22 | instagram.place.kakaoId, 23 | instagram.place.placeName, 24 | instagram.place.location.roadAddressName 25 | )) 26 | .from(instagram) 27 | .orderBy(instagram.hashtagCount.desc()) 28 | .limit(limit) 29 | .fetch(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /hashtagmap-kakao-api/src/main/java/com/songpapeople/hashtagmap/kakaoapi/domain/rect/location/Latitude.java: -------------------------------------------------------------------------------- 1 | package com.songpapeople.hashtagmap.kakaoapi.domain.rect.location; 2 | 3 | import com.songpapeople.hashtagmap.kakaoapi.exception.KakaoApiException; 4 | import com.songpapeople.hashtagmap.kakaoapi.exception.KakaoApiExceptionStatus; 5 | 6 | import java.math.BigDecimal; 7 | 8 | // 위도, y 9 | public class Latitude extends Coordinate { 10 | private static final BigDecimal MIN_LATITUDE = BigDecimal.valueOf(33); 11 | private static final BigDecimal MAX_LATITUDE = BigDecimal.valueOf(43); 12 | 13 | public Latitude(double latitude) { 14 | super(validateRange(BigDecimal.valueOf(latitude))); 15 | } 16 | 17 | public Latitude(BigDecimal latitude) { 18 | super(validateRange(latitude)); 19 | } 20 | 21 | protected static BigDecimal validateRange(BigDecimal latitude) { 22 | if (isBetween(latitude, MIN_LATITUDE, MAX_LATITUDE)) { 23 | return latitude; 24 | } 25 | String detailMessage = String.format("잘못된 위도 범위(%s)입니다.", latitude); 26 | throw new KakaoApiException(KakaoApiExceptionStatus.INVALID_LATITUDE, detailMessage); 27 | } 28 | 29 | @Override 30 | public Latitude forward(BigDecimal offset) { 31 | return new Latitude(super.value.add(offset)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /hashtagmap-web/front/src/components/place/PlaceSearchContainer.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 46 | 47 | 48 | --------------------------------------------------------------------------------