├── 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 |
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 |
2 |
3 |
4 |
5 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/hashtagmap-admin/front/src/views/district/DistrictContainer.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
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 |
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 |
2 |
7 | {{ gps }}
8 |
9 |
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 | 
6 |
7 | ## 서비스 소개
8 |
9 | 친구들, 연인과 함께할 장소를 어렵게 찾아보지 마세요.
10 |
11 | 인스타그램 속 🔥핫플레이스🔥를 한 눈에 확인하세요.
12 |
13 |
14 |
15 |
16 | ## 송파구 사람들
17 |
18 | 
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 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
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 |
2 |
3 |
8 | {{ getSnackbar.code ? [getSnackbar.code] : "" }} {{ getSnackbar.message }}
9 |
10 |
11 | Close
12 |
13 |
14 |
15 |
16 |
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 |
2 |
3 | {{ category.name }}
4 |
5 |
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 |
2 |
11 | {{
12 | magnify
13 | }}
14 |
15 |
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 extends Event>> brokers = new HashMap<>();
12 |
13 | public EventBrokerGroup() {
14 | for (Class extends Event> 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 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
37 |
38 |
54 |
--------------------------------------------------------------------------------
/hashtagmap-web/front/src/components/place/PlaceCategories.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
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 |
2 |
3 |
4 |
5 | 검색
6 |
7 | {{ showIcon() }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
46 |
47 |
48 |
--------------------------------------------------------------------------------