├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── frontend
├── .babelrc
├── .eslintrc.js
├── .prettierrc.js
├── package-lock.json
├── package.json
├── src
│ ├── App.vue
│ ├── api
│ │ ├── index.js
│ │ └── modules
│ │ │ ├── auth.js
│ │ │ ├── favorite.js
│ │ │ ├── line.js
│ │ │ ├── map.js
│ │ │ ├── member.js
│ │ │ ├── path.js
│ │ │ └── station.js
│ ├── components
│ │ ├── dialogs
│ │ │ ├── ConfirmDialog.vue
│ │ │ └── Dialog.vue
│ │ ├── menus
│ │ │ ├── ButtonMenu.vue
│ │ │ ├── IconMenu.vue
│ │ │ └── MenuListItem.vue
│ │ └── snackbars
│ │ │ └── Snackbar.vue
│ ├── main.js
│ ├── mixins
│ │ ├── dialog.js
│ │ └── responsive.js
│ ├── router
│ │ ├── index.js
│ │ └── modules
│ │ │ ├── auth.js
│ │ │ ├── favorite.js
│ │ │ ├── line.js
│ │ │ ├── main.js
│ │ │ ├── map.js
│ │ │ ├── path.js
│ │ │ ├── section.js
│ │ │ └── station.js
│ ├── store
│ │ ├── index.js
│ │ ├── modules
│ │ │ ├── auth.js
│ │ │ ├── favorite.js
│ │ │ ├── line.js
│ │ │ ├── map.js
│ │ │ ├── member.js
│ │ │ ├── path.js
│ │ │ ├── snackbar.js
│ │ │ └── station.js
│ │ └── shared
│ │ │ ├── actionTypes.js
│ │ │ └── mutationTypes.js
│ ├── styles
│ │ ├── app.scss
│ │ ├── color.scss
│ │ ├── components
│ │ │ └── alert.scss
│ │ ├── custom.scss
│ │ ├── fonts.scss
│ │ ├── index.scss
│ │ └── layout.scss
│ ├── utils
│ │ ├── constants.js
│ │ ├── plugin
│ │ │ └── vuetify.js
│ │ └── validator.js
│ └── views
│ │ ├── auth
│ │ ├── JoinPage.vue
│ │ ├── LoginPage.vue
│ │ ├── Mypage.vue
│ │ └── MypageEdit.vue
│ │ ├── base
│ │ └── header
│ │ │ ├── Header.vue
│ │ │ └── components
│ │ │ ├── FavoritesButton.vue
│ │ │ ├── LogoutButton.vue
│ │ │ └── MyPageButton.vue
│ │ ├── favorite
│ │ ├── Favorites.vue
│ │ └── components
│ │ │ └── FavoriteDeleteButton.vue
│ │ ├── line
│ │ ├── LinePage.vue
│ │ └── components
│ │ │ ├── LineCreateButton.vue
│ │ │ ├── LineDeleteButton.vue
│ │ │ ├── LineEditButton.vue
│ │ │ └── LineForm.vue
│ │ ├── main
│ │ └── MainPage.vue
│ │ ├── map
│ │ └── MapPage.vue
│ │ ├── path
│ │ ├── PathPage.vue
│ │ └── components
│ │ │ └── AddFavoriteButton.vue
│ │ ├── section
│ │ ├── SectionPage.vue
│ │ └── components
│ │ │ ├── SectionCreateButton.vue
│ │ │ └── SectionDeleteButton.vue
│ │ └── station
│ │ └── StationPage.vue
├── webpack.common.js
├── webpack.config.js
├── webpack.dev.js
└── webpack.prod.js
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src
├── main
├── java
│ └── nextstep
│ │ └── subway
│ │ ├── PageController.java
│ │ ├── SubwayApplication.java
│ │ ├── auth
│ │ ├── application
│ │ │ ├── AuthService.java
│ │ │ └── AuthorizationException.java
│ │ ├── domain
│ │ │ ├── AuthenticationPrincipal.java
│ │ │ └── LoginMember.java
│ │ ├── dto
│ │ │ ├── TokenRequest.java
│ │ │ └── TokenResponse.java
│ │ ├── infrastructure
│ │ │ ├── AuthorizationExtractor.java
│ │ │ └── JwtTokenProvider.java
│ │ └── ui
│ │ │ ├── AuthController.java
│ │ │ └── AuthenticationPrincipalArgumentResolver.java
│ │ ├── common
│ │ ├── AuthenticationPrincipalConfig.java
│ │ └── BaseEntity.java
│ │ ├── favorite
│ │ ├── application
│ │ │ └── FavoriteService.java
│ │ ├── domain
│ │ │ ├── Favorite.java
│ │ │ ├── FavoriteRepository.java
│ │ │ └── HasNotPermissionException.java
│ │ ├── dto
│ │ │ ├── FavoriteRequest.java
│ │ │ └── FavoriteResponse.java
│ │ └── ui
│ │ │ └── FavoriteController.java
│ │ ├── line
│ │ ├── application
│ │ │ └── LineService.java
│ │ ├── domain
│ │ │ ├── Line.java
│ │ │ ├── LineRepository.java
│ │ │ └── Section.java
│ │ ├── dto
│ │ │ ├── LineRequest.java
│ │ │ ├── LineResponse.java
│ │ │ └── SectionRequest.java
│ │ └── ui
│ │ │ └── LineController.java
│ │ ├── map
│ │ ├── application
│ │ │ ├── MapService.java
│ │ │ └── PathService.java
│ │ ├── domain
│ │ │ ├── SectionEdge.java
│ │ │ ├── SubwayGraph.java
│ │ │ └── SubwayPath.java
│ │ ├── dto
│ │ │ ├── PathResponse.java
│ │ │ └── PathResponseAssembler.java
│ │ └── ui
│ │ │ └── MapController.java
│ │ ├── member
│ │ ├── application
│ │ │ └── MemberService.java
│ │ ├── domain
│ │ │ ├── Member.java
│ │ │ └── MemberRepository.java
│ │ ├── dto
│ │ │ ├── MemberRequest.java
│ │ │ └── MemberResponse.java
│ │ └── ui
│ │ │ └── MemberController.java
│ │ └── station
│ │ ├── application
│ │ └── StationService.java
│ │ ├── domain
│ │ ├── Station.java
│ │ └── StationRepository.java
│ │ ├── dto
│ │ ├── StationRequest.java
│ │ └── StationResponse.java
│ │ └── ui
│ │ └── StationController.java
└── resources
│ ├── application.properties
│ ├── logback-access.xml
│ ├── static
│ ├── images
│ │ ├── logo_small.png
│ │ └── main_logo.png
│ └── js
│ │ ├── main.js
│ │ ├── vendors.js
│ │ └── vendors.js.LICENSE.txt
│ └── templates
│ └── index.html
└── test
└── java
├── nextstep
└── subway
│ ├── AcceptanceTest.java
│ ├── auth
│ ├── acceptance
│ │ └── AuthAcceptanceTest.java
│ └── application
│ │ └── AuthServiceTest.java
│ ├── favorite
│ └── FavoriteAcceptanceTest.java
│ ├── line
│ └── acceptance
│ │ ├── LineAcceptanceTest.java
│ │ └── LineSectionAcceptanceTest.java
│ ├── member
│ └── MemberAcceptanceTest.java
│ ├── path
│ └── PathAcceptanceTest.java
│ ├── station
│ └── StationAcceptanceTest.java
│ └── utils
│ └── DatabaseCleanup.java
└── study
├── jgraph
└── JgraphTest.java
└── unit
├── MockitoExtensionTest.java
├── MockitoTest.java
├── SpringExtensionTest.java
└── UnitTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | HELP.md
2 | .gradle
3 | build/
4 | !gradle/wrapper/gradle-wrapper.jar
5 | !**/src/main/**/build/
6 | !**/src/test/**/build/
7 |
8 | ### STS ###
9 | .apt_generated
10 | .classpath
11 | .factorypath
12 | .project
13 | .settings
14 | .springBeans
15 | .sts4-cache
16 | bin/
17 | !**/src/main/**/bin/
18 | !**/src/test/**/bin/
19 |
20 | ### IntelliJ IDEA ###
21 | .idea
22 | *.iws
23 | *.iml
24 | *.ipr
25 | out/
26 | !**/src/main/**/out/
27 | !**/src/test/**/out/
28 |
29 | ### NetBeans ###
30 | /nbproject/private/
31 | /nbbuild/
32 | /dist/
33 | /nbdist/
34 | /.nb-gradle/
35 |
36 | ### VS Code ###
37 | .vscode/
38 |
39 | ### Front End ###
40 | frontend/node_modules
41 | fronted/.prttierrc.js
42 | fronted/.eslintrc.js
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 next-step
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | # 인프라공방 샘플 서비스 - 지하철 노선도
16 |
17 |
18 |
19 | ## 🚀 Getting Started
20 |
21 | ### Install
22 | #### npm 설치
23 | ```
24 | cd frontend
25 | npm install
26 | ```
27 | > `frontend` 디렉토리에서 수행해야 합니다.
28 |
29 | ### Usage
30 | #### webpack server 구동
31 | ```
32 | npm run dev
33 | ```
34 | #### application 구동
35 | ```
36 | ./gradlew clean build
37 | ```
38 |
39 |
40 | ## 미션
41 |
42 | * 미션 진행 후에 아래 질문의 답을 README.md 파일에 작성하여 PR을 보내주세요.
43 |
44 | ### 0단계 - pem 키 생성하기
45 |
46 | 1. 서버에 접속을 위한 pem키를 [구글드라이브](https://drive.google.com/drive/folders/1dZiCUwNeH1LMglp8dyTqqsL1b2yBnzd1?usp=sharing)에 업로드해주세요
47 |
48 | 2. 업로드한 pem키는 무엇인가요.
49 |
50 | ### 1단계 - 망 구성하기
51 | 1. 구성한 망의 서브넷 대역을 알려주세요
52 | - 대역 :
53 |
54 | 2. 배포한 서비스의 공인 IP(혹은 URL)를 알려주세요
55 |
56 | - URL :
57 |
58 |
59 |
60 | ---
61 |
62 | ### 2단계 - 배포하기
63 | 1. TLS가 적용된 URL을 알려주세요
64 |
65 | - URL :
66 |
67 | ---
68 |
69 | ### 3단계 - 배포 스크립트 작성하기
70 |
71 | 1. 작성한 배포 스크립트를 공유해주세요.
72 |
73 |
74 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'org.springframework.boot' version '2.4.0-SNAPSHOT'
3 | id 'io.spring.dependency-management' version '1.0.10.RELEASE'
4 | id 'java'
5 | }
6 |
7 | group = 'nextstep'
8 | version = '0.0.1-SNAPSHOT'
9 | sourceCompatibility = '1.8'
10 |
11 | repositories {
12 | mavenCentral()
13 | maven { url 'https://repo.spring.io/milestone' }
14 | maven { url 'https://repo.spring.io/snapshot' }
15 | }
16 |
17 | dependencies {
18 | // spring
19 | implementation 'org.springframework.boot:spring-boot-starter-web'
20 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
21 |
22 | // handlebars
23 | implementation 'pl.allegro.tech.boot:handlebars-spring-boot-starter:0.3.0'
24 |
25 | // log
26 | implementation 'net.rakugakibox.spring.boot:logback-access-spring-boot-starter:2.7.1'
27 |
28 | // jgraph
29 | implementation 'org.jgrapht:jgrapht-core:1.0.1'
30 |
31 | // jwt
32 | implementation 'io.jsonwebtoken:jjwt:0.9.1'
33 |
34 | testImplementation 'io.rest-assured:rest-assured:3.3.0'
35 | testImplementation 'org.springframework.boot:spring-boot-starter-test'
36 |
37 | runtimeOnly 'com.h2database:h2'
38 | }
39 |
40 | test {
41 | useJUnitPlatform()
42 | }
43 |
--------------------------------------------------------------------------------
/frontend/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [["@babel/env", { "modules": false }]],
3 | "env": {
4 | "test": {
5 | "presets": [
6 | [
7 | "@babel/env",
8 | {
9 | "targets": {
10 | "node": "current"
11 | }
12 | }
13 | ]
14 | ]
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/frontend/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es6: true,
5 | jest: true
6 | },
7 | extends: ['plugin:vue/essential', 'airbnb-base', 'prettier'],
8 | globals: {
9 | Atomics: 'readonly',
10 | SharedArrayBuffer: 'readonly'
11 | },
12 | parserOptions: {
13 | ecmaVersion: 2018,
14 | sourceType: 'module'
15 | },
16 | plugins: ['vue'],
17 | rules: {
18 | semi: 0,
19 | 'import/no-unresolved': 'off',
20 | 'comma-dangle': 'off',
21 | 'no-new': 0
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/frontend/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | tabWidth: 2,
3 | semi: false,
4 | singleQuote: true,
5 | endOfLine: 'lf',
6 | trailingComma: 'none',
7 | bracketSpacing: true,
8 | printWidth: 150,
9 | jsxBracketSameLine: false
10 | }
11 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "atdd-subway-path",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "prod": "webpack --env=prod",
8 | "dev": "webpack-dev-server --env=dev",
9 | "start": "npm run dev",
10 | "test": "jest"
11 | },
12 | "jest": {
13 | "moduleFileExtensions": [
14 | "js",
15 | "json",
16 | "vue"
17 | ],
18 | "moduleNameMapper": {
19 | "^@/(.*)$": "/src/$1"
20 | },
21 | "transform": {
22 | ".*\\.(vue)$": "vue-jest",
23 | "^.+\\.js$": "/node_modules/babel-jest"
24 | },
25 | "collectCoverage": true,
26 | "collectCoverageFrom": [
27 | "**/*.{js,vue}",
28 | "!**/node_modules/**"
29 | ],
30 | "snapshotSerializers": [
31 | "jest-serializer-vue"
32 | ]
33 | },
34 | "repository": {
35 | "type": "git",
36 | "url": "https://github.com/next-step/atdd-subway-path.git"
37 | },
38 | "keywords": [],
39 | "author": "",
40 | "license": "ISC",
41 | "bugs": {
42 | "url": "https://github.com/next-step/atdd-subway-path/issues"
43 | },
44 | "engines": {
45 | "npm": ">=5.5.0",
46 | "node": ">=9.3.0"
47 | },
48 | "homepage": "https://github.com/next-step/atdd-subway-path#readme",
49 | "devDependencies": {
50 | "@babel/preset-env": "^7.9.0",
51 | "@vue/test-utils": "^1.0.0-beta.32",
52 | "babel-core": "^7.0.0-bridge.0",
53 | "babel-jest": "^25.1.0",
54 | "babel-loader": "^8.0.6",
55 | "babel-polyfill": "^6.26.0",
56 | "case-sensitive-paths-webpack-plugin": "^2.3.0",
57 | "css-loader": "^3.4.2",
58 | "deepmerge": "^4.2.2",
59 | "eslint": "^6.8.0",
60 | "eslint-config-airbnb": "^18.0.1",
61 | "eslint-config-prettier": "^6.10.0",
62 | "eslint-plugin-import": "^2.20.1",
63 | "eslint-plugin-jsx-a11y": "^6.2.3",
64 | "eslint-plugin-prettier": "^3.1.2",
65 | "eslint-plugin-vue": "^6.1.2",
66 | "fibers": "^4.0.2",
67 | "file-loader": "^5.0.2",
68 | "html-webpack-plugin": "^3.2.0",
69 | "jest": "^25.1.0",
70 | "jest-serializer-vue": "^2.0.2",
71 | "mini-css-extract-plugin": "^0.9.0",
72 | "node-sass": "^4.13.1",
73 | "optimize-css-assets-webpack-plugin": "^5.0.3",
74 | "prettier": "^1.19.1",
75 | "sass": "^1.25.0",
76 | "sass-loader": "^8.0.2",
77 | "style-loader": "^1.1.3",
78 | "terser-webpack-plugin": "^2.3.4",
79 | "url-loader": "^3.0.0",
80 | "vue-jest": "^3.0.5",
81 | "vue-loader": "^15.8.3",
82 | "vue-style-loader": "^4.1.2",
83 | "vue-template-compiler": "^2.6.11",
84 | "vuetify-loader": "^1.4.3",
85 | "webpack": "^4.41.5",
86 | "webpack-bundle-analyzer": "^3.6.0",
87 | "webpack-cli": "^3.3.10",
88 | "webpack-dev-server": "^3.10.3",
89 | "webpack-merge": "^4.2.2",
90 | "yargs": "^15.3.1"
91 | },
92 | "dependencies": {
93 | "axios": "^0.19.2",
94 | "eslint-plugin-react": "^7.21.5",
95 | "eslint-plugin-react-hooks": "^4.2.0",
96 | "vue": "^2.6.11",
97 | "vue-axios": "^2.1.5",
98 | "vue-router": "^3.1.5",
99 | "vuetify": "^2.2.11",
100 | "vuex": "^3.1.2"
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/frontend/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
35 |
--------------------------------------------------------------------------------
/frontend/src/api/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import axios from 'axios'
3 | import VueAxios from 'vue-axios'
4 |
5 | const ApiService = {
6 | init() {
7 | Vue.use(VueAxios, axios)
8 | },
9 | get(uri) {
10 | return Vue.axios.get(`${uri}`, {
11 | headers: {
12 | Authorization: `Bearer ${localStorage.getItem('token')}` || ''
13 | }
14 | })
15 | },
16 | login(uri, config) {
17 | return Vue.axios.post(`${uri}`, {}, config)
18 | },
19 | post(uri, params) {
20 | return Vue.axios.post(`${uri}`, params, {
21 | headers: {
22 | Authorization: `Bearer ${localStorage.getItem('token')}` || ''
23 | }
24 | })
25 | },
26 | update(uri, params) {
27 | return Vue.axios.put(uri, params, {
28 | headers: {
29 | Authorization: `Bearer ${localStorage.getItem('token')}` || ''
30 | }
31 | })
32 | },
33 | delete(uri) {
34 | return Vue.axios.delete(uri, {
35 | headers: {
36 | Authorization: `Bearer ${localStorage.getItem('token')}` || ''
37 | }
38 | })
39 | }
40 | }
41 |
42 | ApiService.init()
43 |
44 | export default ApiService
45 |
--------------------------------------------------------------------------------
/frontend/src/api/modules/auth.js:
--------------------------------------------------------------------------------
1 | import ApiService from '@/api'
2 |
3 | const AuthService = {
4 | login(userInfo) {
5 | return ApiService.post(`/login/token`, userInfo)
6 | }
7 | }
8 |
9 | export default AuthService
10 |
--------------------------------------------------------------------------------
/frontend/src/api/modules/favorite.js:
--------------------------------------------------------------------------------
1 | import ApiService from '@/api'
2 |
3 | const BASE_URL = '/favorites'
4 |
5 | const FavoriteService = {
6 | get() {
7 | return ApiService.get(`${BASE_URL}`)
8 | },
9 | create(newFavorite) {
10 | return ApiService.post(`${BASE_URL}`, newFavorite)
11 | },
12 | delete(favoriteId) {
13 | return ApiService.delete(`${BASE_URL}/${favoriteId}`)
14 | }
15 | }
16 |
17 | export default FavoriteService
18 |
--------------------------------------------------------------------------------
/frontend/src/api/modules/line.js:
--------------------------------------------------------------------------------
1 | import ApiService from '@/api'
2 |
3 | const BASE_URL = '/lines'
4 |
5 | const LineService = {
6 | get(lineId) {
7 | return ApiService.get(`${BASE_URL}/${lineId}`)
8 | },
9 | getAll() {
10 | return ApiService.get(`${BASE_URL}`)
11 | },
12 | create(newLine) {
13 | return ApiService.post(`${BASE_URL}`, newLine)
14 | },
15 | update(editingLine) {
16 | return ApiService.update(`${BASE_URL}/${editingLine.lineId}`, editingLine.line)
17 | },
18 | delete(lineId) {
19 | return ApiService.delete(`${BASE_URL}/${lineId}`)
20 | },
21 | createSection({ lineId, section }) {
22 | return ApiService.post(`${BASE_URL}/${lineId}/sections`, section)
23 | },
24 | deleteSection({ lineId, stationId }) {
25 | return ApiService.delete(`${BASE_URL}/${lineId}/sections?stationId=${stationId}`)
26 | }
27 | }
28 |
29 | export default LineService
30 |
--------------------------------------------------------------------------------
/frontend/src/api/modules/map.js:
--------------------------------------------------------------------------------
1 | import ApiService from '@/api'
2 |
3 | const BASE_URL = '/maps'
4 |
5 | const MapService = {
6 | get() {
7 | return ApiService.get(`${BASE_URL}`)
8 | }
9 | }
10 |
11 | export default MapService
12 |
--------------------------------------------------------------------------------
/frontend/src/api/modules/member.js:
--------------------------------------------------------------------------------
1 | import ApiService from '@/api'
2 |
3 | const BASE_URL = '/members'
4 |
5 | const MemberService = {
6 | get() {
7 | return ApiService.get(`${BASE_URL}/me`)
8 | },
9 | create(newMember) {
10 | return ApiService.post(`${BASE_URL}`, newMember)
11 | },
12 | update(updateMemberView) {
13 | return ApiService.update(`${BASE_URL}/me`, updateMemberView)
14 | },
15 | delete() {
16 | return ApiService.delete(`${BASE_URL}/me`)
17 | }
18 | }
19 |
20 | export default MemberService
--------------------------------------------------------------------------------
/frontend/src/api/modules/path.js:
--------------------------------------------------------------------------------
1 | import ApiService from '@/api'
2 |
3 | const BASE_URL = '/paths'
4 |
5 | const PathService = {
6 | get({ source, target }) {
7 | return ApiService.get(`${BASE_URL}/?source=${source}&target=${target}`)
8 | }
9 | }
10 |
11 | export default PathService
12 |
--------------------------------------------------------------------------------
/frontend/src/api/modules/station.js:
--------------------------------------------------------------------------------
1 | import ApiService from '@/api'
2 |
3 | const BASE_URL = '/stations'
4 |
5 | const StationService = {
6 | get(stationId) {
7 | return ApiService.get(`${BASE_URL}/${stationId}`)
8 | },
9 | getAll() {
10 | return ApiService.get(`${BASE_URL}`)
11 | },
12 | create(newStationName) {
13 | return ApiService.post(`${BASE_URL}`, newStationName)
14 | },
15 | update(station) {
16 | return ApiService.put(`${BASE_URL}/${station.id}`, station)
17 | },
18 | delete(stationId) {
19 | return ApiService.delete(`${BASE_URL}/${stationId}`)
20 | }
21 | }
22 |
23 | export default StationService
24 |
--------------------------------------------------------------------------------
/frontend/src/components/dialogs/ConfirmDialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ title }}
6 |
7 |
8 | {{ message }}
9 |
10 |
11 |
12 | 취소
13 | 탈퇴
14 |
15 |
16 |
17 |
18 |
19 |
71 |
--------------------------------------------------------------------------------
/frontend/src/components/dialogs/Dialog.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | 취소
21 |
22 |
23 |
24 |
25 |
26 |
27 |
82 |
--------------------------------------------------------------------------------
/frontend/src/components/menus/ButtonMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
--------------------------------------------------------------------------------
/frontend/src/components/menus/IconMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
44 |
--------------------------------------------------------------------------------
/frontend/src/components/menus/MenuListItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
14 |
15 |
20 |
--------------------------------------------------------------------------------
/frontend/src/components/snackbars/Snackbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{ snackbarMsg }}
4 |
5 |
6 |
7 |
34 |
--------------------------------------------------------------------------------
/frontend/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import vuetify from '@/utils/plugin/vuetify'
3 | import router from '@/router'
4 | import store from '@/store'
5 | import App from './App.vue'
6 | import '@/api'
7 | import '@/styles/index.scss'
8 |
9 | new Vue({
10 | el: '#app',
11 | vuetify,
12 | router,
13 | store,
14 | render: h => h(App)
15 | })
16 |
--------------------------------------------------------------------------------
/frontend/src/mixins/dialog.js:
--------------------------------------------------------------------------------
1 | import validator from '@/utils/validator'
2 |
3 | const dialog = {
4 | methods: {
5 | closeDialog() {
6 | this.close = !this.close
7 | },
8 | isValid($form) {
9 | return $form.validate()
10 | }
11 | },
12 | data() {
13 | return {
14 | close: false,
15 | rules: {
16 | ...validator
17 | },
18 | valid: false,
19 | isRequested: false
20 | }
21 | }
22 | }
23 |
24 | export default dialog
25 |
--------------------------------------------------------------------------------
/frontend/src/mixins/responsive.js:
--------------------------------------------------------------------------------
1 | const responsive = {
2 | methods: {
3 | xsOnly() {
4 | return this.$vuetify.breakpoint.xsOnly
5 | },
6 | smAndUp() {
7 | return this.$vuetify.breakpoint.smAndUp
8 | }
9 | }
10 | }
11 |
12 | export default responsive
13 |
--------------------------------------------------------------------------------
/frontend/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 | import stationRoutes from '@/router/modules/station'
4 | import lineRoutes from '@/router/modules/line'
5 | import mainRoutes from '@/router/modules/main'
6 | import sectionRoutes from '@/router/modules/section'
7 | import mapRoutes from '@/router/modules/map'
8 | import pathRoutes from '@/router/modules/path'
9 | import authRoutes from '@/router/modules/auth'
10 | import favoriteRoutes from '@/router/modules/favorite'
11 |
12 | Vue.use(VueRouter)
13 |
14 | export default new VueRouter({
15 | mode: 'history',
16 | routes: [...mapRoutes, ...pathRoutes, ...stationRoutes, ...lineRoutes, ...sectionRoutes, ...mainRoutes, ...authRoutes, ...favoriteRoutes]
17 | })
18 |
--------------------------------------------------------------------------------
/frontend/src/router/modules/auth.js:
--------------------------------------------------------------------------------
1 | import LoginPage from '@/views/auth/LoginPage'
2 | import JoinPage from '@/views/auth/JoinPage'
3 | import Mypage from '@/views/auth/Mypage'
4 | import MypageEdit from '@/views/auth/MypageEdit'
5 |
6 | const authRoutes = [
7 | {
8 | path: '/login',
9 | component: LoginPage
10 | },
11 | {
12 | path: '/join',
13 | component: JoinPage
14 | },
15 | {
16 | path: '/mypage',
17 | component: Mypage
18 | },
19 | {
20 | path: '/mypage/edit',
21 | component: MypageEdit
22 | }
23 | ]
24 | export default authRoutes
25 |
--------------------------------------------------------------------------------
/frontend/src/router/modules/favorite.js:
--------------------------------------------------------------------------------
1 | import Favorites from '@/views/favorite/Favorites'
2 |
3 | const favoriteRoutes = [
4 | {
5 | path: '/favorites',
6 | component: Favorites
7 | }
8 | ]
9 | export default favoriteRoutes
10 |
--------------------------------------------------------------------------------
/frontend/src/router/modules/line.js:
--------------------------------------------------------------------------------
1 | import LinePage from '@/views/line/LinePage'
2 |
3 | const lineRoutes = [
4 | {
5 | path: '/lines',
6 | component: LinePage
7 | }
8 | ]
9 | export default lineRoutes
10 |
--------------------------------------------------------------------------------
/frontend/src/router/modules/main.js:
--------------------------------------------------------------------------------
1 | import MainPage from '@/views/main/MainPage'
2 |
3 | const mainRoutes = [
4 | {
5 | path: '/',
6 | component: MainPage
7 | }
8 | ]
9 | export default mainRoutes
10 |
--------------------------------------------------------------------------------
/frontend/src/router/modules/map.js:
--------------------------------------------------------------------------------
1 | import MapPage from '../../views/map/MapPage'
2 |
3 | const mapRoutes = [
4 | {
5 | path: '/maps',
6 | component: MapPage
7 | }
8 | ]
9 | export default mapRoutes
10 |
--------------------------------------------------------------------------------
/frontend/src/router/modules/path.js:
--------------------------------------------------------------------------------
1 | import PathPage from '../../views/path/PathPage'
2 |
3 | const pathRoutes = [
4 | {
5 | path: '/path',
6 | component: PathPage
7 | }
8 | ]
9 | export default pathRoutes
10 |
--------------------------------------------------------------------------------
/frontend/src/router/modules/section.js:
--------------------------------------------------------------------------------
1 | import SectionPage from '@/views/section/SectionPage'
2 |
3 | const sectionRoutes = [
4 | {
5 | path: '/sections',
6 | component: SectionPage
7 | }
8 | ]
9 | export default sectionRoutes
10 |
--------------------------------------------------------------------------------
/frontend/src/router/modules/station.js:
--------------------------------------------------------------------------------
1 | import StationPage from '@/views/station/StationPage'
2 |
3 | const stationRoutes = [
4 | {
5 | path: '/stations',
6 | component: StationPage
7 | }
8 | ]
9 | export default stationRoutes
10 |
--------------------------------------------------------------------------------
/frontend/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 | import station from '@/store/modules/station'
4 | import line from '@/store/modules/line'
5 | import snackbar from '@/store/modules/snackbar'
6 | import map from '@/store/modules/map'
7 | import path from '@/store/modules/path'
8 | import member from '@/store/modules/member'
9 | import auth from '@/store/modules/auth'
10 | import favorite from '@/store/modules/favorite'
11 |
12 | Vue.use(Vuex)
13 |
14 | export default new Vuex.Store({
15 | modules: {
16 | station,
17 | line,
18 | snackbar,
19 | map,
20 | path,
21 | member,
22 | favorite,
23 | auth
24 | }
25 | })
26 |
--------------------------------------------------------------------------------
/frontend/src/store/modules/auth.js:
--------------------------------------------------------------------------------
1 | import { SET_ACCESS_TOKEN } from '@/store/shared/mutationTypes'
2 | import { FETCH_MEMBER, LOGIN } from '@/store/shared/actionTypes'
3 | import AuthService from '@/api/modules/auth'
4 |
5 | const state = {
6 | accessToken: null
7 | }
8 |
9 | const getters = {
10 | accessToken(state) {
11 | return state.accessToken
12 | }
13 | }
14 |
15 | const mutations = {
16 | [SET_ACCESS_TOKEN](state, accessToken) {
17 | state.accessToken = accessToken
18 | }
19 | }
20 |
21 | const actions = {
22 | async [LOGIN]({ commit, dispatch }, loginInfo) {
23 | return AuthService.login(loginInfo).then(({ data }) => {
24 | commit(SET_ACCESS_TOKEN, data.accessToken)
25 | localStorage.setItem('token', data.accessToken)
26 | dispatch(FETCH_MEMBER)
27 | })
28 | }
29 | }
30 |
31 | export default {
32 | state,
33 | getters,
34 | actions,
35 | mutations
36 | }
37 |
--------------------------------------------------------------------------------
/frontend/src/store/modules/favorite.js:
--------------------------------------------------------------------------------
1 | import { SET_FAVORITES } from '@/store/shared/mutationTypes'
2 | import { CREATE_FAVORITE, DELETE_FAVORITE, FETCH_FAVORITES } from '@/store/shared/actionTypes'
3 | import FavoriteService from '@/api/modules/favorite'
4 |
5 | const state = {
6 | favorites: []
7 | }
8 |
9 | const getters = {
10 | favorites(state) {
11 | return state.favorites
12 | }
13 | }
14 |
15 | const mutations = {
16 | [SET_FAVORITES](state, favorites) {
17 | state.favorites = favorites
18 | }
19 | }
20 |
21 | const actions = {
22 | async [FETCH_FAVORITES]({ commit }) {
23 | return FavoriteService.get().then(({ data }) => {
24 | commit(SET_FAVORITES, data)
25 | })
26 | },
27 | async [CREATE_FAVORITE]({ commit }, newFavorite) {
28 | return FavoriteService.create(newFavorite)
29 | },
30 | async [DELETE_FAVORITE]({ commit }, favoriteId) {
31 | return FavoriteService.delete(favoriteId)
32 | }
33 | }
34 |
35 | export default {
36 | state,
37 | getters,
38 | actions,
39 | mutations
40 | }
41 |
--------------------------------------------------------------------------------
/frontend/src/store/modules/line.js:
--------------------------------------------------------------------------------
1 | import { SET_LINE, SET_LINES } from '@/store/shared/mutationTypes'
2 | import { CREATE_LINE, DELETE_LINE, FETCH_LINES, EDIT_LINE, DELETE_SECTION, CREATE_SECTION, FETCH_LINE } from '@/store/shared/actionTypes'
3 | import LineService from '@/api/modules/line'
4 |
5 | const state = {
6 | line: {},
7 | lines: []
8 | }
9 |
10 | const getters = {
11 | line(state) {
12 | return state.line
13 | },
14 | lines(state) {
15 | return state.lines
16 | }
17 | }
18 |
19 | const mutations = {
20 | [SET_LINE](state, line) {
21 | state.line = line
22 | },
23 | [SET_LINES](state, lines) {
24 | state.lines = lines
25 | }
26 | }
27 |
28 | const actions = {
29 | async [CREATE_LINE]({ commit }, newLine) {
30 | return LineService.create(newLine)
31 | },
32 | async [FETCH_LINE]({ commit }, lineId) {
33 | return LineService.get(lineId).then(({ data }) => {
34 | commit(SET_LINE, data)
35 | return data
36 | })
37 | },
38 | async [FETCH_LINES]({ commit }) {
39 | return LineService.getAll().then(({ data }) => {
40 | commit(SET_LINES, data)
41 | })
42 | },
43 | async [EDIT_LINE]({ commit }, editingLine) {
44 | return LineService.update(editingLine)
45 | },
46 | async [DELETE_LINE]({ commit }, lineId) {
47 | return LineService.delete(lineId)
48 | },
49 | async [DELETE_SECTION]({ commit }, { lineId, stationId }) {
50 | return LineService.deleteSection({ lineId, stationId })
51 | },
52 | async [CREATE_SECTION]({ commit }, { lineId, section }) {
53 | return LineService.createSection({ lineId, section })
54 | }
55 | }
56 |
57 | export default {
58 | state,
59 | getters,
60 | actions,
61 | mutations
62 | }
63 |
--------------------------------------------------------------------------------
/frontend/src/store/modules/map.js:
--------------------------------------------------------------------------------
1 | import { SET_MAP } from '@/store/shared/mutationTypes'
2 | import { FETCH_MAP } from '@/store/shared/actionTypes'
3 | import MapService from '@/api/modules/map'
4 |
5 | const state = {
6 | map: {}
7 | }
8 |
9 | const getters = {
10 | map(state) {
11 | return state.map
12 | }
13 | }
14 |
15 | const mutations = {
16 | [SET_MAP](state, map) {
17 | state.map = map
18 | }
19 | }
20 |
21 | const actions = {
22 | async [FETCH_MAP]({ commit }, lineId) {
23 | return MapService.get(lineId).then(({ data: { lineResponses } }) => {
24 | commit(SET_MAP, lineResponses)
25 | return lineResponses
26 | })
27 | }
28 | }
29 |
30 | export default {
31 | state,
32 | getters,
33 | actions,
34 | mutations
35 | }
36 |
--------------------------------------------------------------------------------
/frontend/src/store/modules/member.js:
--------------------------------------------------------------------------------
1 | import { CREATE_MEMBER, DELETE_MEMBER, FETCH_MEMBER, UPDATE_MEMBER } from '@/store/shared/actionTypes'
2 | import MemberService from '@/api/modules/member'
3 | import { SET_MEMBER } from '@/store/shared/mutationTypes'
4 |
5 | const state = {
6 | member: null
7 | }
8 |
9 | const getters = {
10 | member(state) {
11 | return state.member
12 | }
13 | }
14 |
15 | const mutations = {
16 | [SET_MEMBER](state, member) {
17 | state.member = member
18 | }
19 | }
20 |
21 | const actions = {
22 | async [CREATE_MEMBER]({ commit }, newMemberView) {
23 | return MemberService.create(newMemberView)
24 | },
25 | async [FETCH_MEMBER]({ commit }) {
26 | return MemberService.get().then(({ data }) => {
27 | commit(SET_MEMBER, data)
28 | })
29 | },
30 | async [DELETE_MEMBER]({ commit }, memberId) {
31 | return MemberService.delete().then(() => {
32 | commit(SET_MEMBER, null)
33 | localStorage.setItem('token', null)
34 | })
35 | },
36 | async [UPDATE_MEMBER]({ commit, dispatch }, updateMemberView) {
37 | return MemberService.update(updateMemberView).then(() => {
38 | dispatch(FETCH_MEMBER)
39 | })
40 | }
41 | }
42 |
43 | export default {
44 | state,
45 | getters,
46 | mutations,
47 | actions
48 | }
49 |
--------------------------------------------------------------------------------
/frontend/src/store/modules/path.js:
--------------------------------------------------------------------------------
1 | import { SET_PATH } from '@/store/shared/mutationTypes'
2 | import { SEARCH_PATH } from '@/store/shared/actionTypes'
3 | import PathService from '@/api/modules/path'
4 |
5 | const state = {
6 | pathResult: null
7 | }
8 |
9 | const getters = {
10 | pathResult(state) {
11 | return state.pathResult
12 | }
13 | }
14 |
15 | const mutations = {
16 | [SET_PATH](state, pathResult) {
17 | state.pathResult = pathResult
18 | }
19 | }
20 |
21 | const actions = {
22 | async [SEARCH_PATH]({ commit }, { source, target, type }) {
23 | return PathService.get({ source, target, type }).then(({ data }) => {
24 | commit(SET_PATH, data)
25 | })
26 | }
27 | }
28 |
29 | export default {
30 | state,
31 | getters,
32 | actions,
33 | mutations
34 | }
35 |
--------------------------------------------------------------------------------
/frontend/src/store/modules/snackbar.js:
--------------------------------------------------------------------------------
1 | import { SHOW_SNACKBAR, HIDE_SNACKBAR } from '@/store/shared/mutationTypes'
2 |
3 | const state = {
4 | isShow: false,
5 | message: ''
6 | }
7 |
8 | const getters = {
9 | isShow(state) {
10 | return state.isShow
11 | },
12 | message(state) {
13 | return state.message
14 | }
15 | }
16 |
17 | const mutations = {
18 | [SHOW_SNACKBAR](state, message) {
19 | state.isShow = !state.isShow
20 | state.message = message
21 | },
22 | [HIDE_SNACKBAR](state) {
23 | state.isShow = !state.isShow
24 | }
25 | }
26 |
27 | export default {
28 | state,
29 | getters,
30 | mutations
31 | }
32 |
--------------------------------------------------------------------------------
/frontend/src/store/modules/station.js:
--------------------------------------------------------------------------------
1 | import { SET_STATIONS } from '@/store/shared/mutationTypes'
2 | import { CREATE_STATION, DELETE_STATION, FETCH_STATIONS } from '@/store/shared/actionTypes'
3 | import StationService from '@/api/modules/station'
4 |
5 | const state = {
6 | stations: []
7 | }
8 |
9 | const getters = {
10 | stations(state) {
11 | return state.stations
12 | }
13 | }
14 |
15 | const mutations = {
16 | [SET_STATIONS](state, stations) {
17 | state.stations = stations
18 | }
19 | }
20 |
21 | const actions = {
22 | async [CREATE_STATION]({ commit }, newStationName) {
23 | return StationService.create(newStationName)
24 | },
25 | async [FETCH_STATIONS]({ commit }) {
26 | return StationService.getAll().then(({ data }) => {
27 | commit(SET_STATIONS, data)
28 | })
29 | },
30 | async [DELETE_STATION]({ commit }, stationId) {
31 | return StationService.delete(stationId)
32 | }
33 | }
34 |
35 | export default {
36 | state,
37 | getters,
38 | actions,
39 | mutations
40 | }
41 |
--------------------------------------------------------------------------------
/frontend/src/store/shared/actionTypes.js:
--------------------------------------------------------------------------------
1 | export const CREATE_STATION = 'createStation'
2 | export const FETCH_STATIONS = 'fetchStations'
3 | export const DELETE_STATION = 'deleteStation'
4 | export const CREATE_LINE = 'createLine'
5 | export const FETCH_LINE = 'fetchLine'
6 | export const FETCH_LINES = 'fetchLines'
7 | export const DELETE_LINE = 'deleteLine'
8 | export const EDIT_LINE = 'editLine'
9 | export const DELETE_SECTION = 'deleteSection'
10 | export const CREATE_SECTION = 'createSection'
11 | export const FETCH_MAP = 'fetchMap'
12 | export const SEARCH_PATH = 'searchPath'
13 | export const CREATE_MEMBER = 'createMember'
14 | export const LOGIN = 'login'
15 | export const CREATE_FAVORITE = 'createFavorite'
16 | export const DELETE_FAVORITE = 'deleteFavorite'
17 | export const FETCH_FAVORITES = 'fetchFavorites'
18 | export const FETCH_MEMBER = 'fetchMember'
19 | export const UPDATE_MEMBER = 'updateMember'
20 | export const DELETE_MEMBER = 'deleteMember'
21 |
--------------------------------------------------------------------------------
/frontend/src/store/shared/mutationTypes.js:
--------------------------------------------------------------------------------
1 | export const SET_STATIONS = 'setStations'
2 | export const SHOW_SNACKBAR = 'showSnackbar'
3 | export const HIDE_SNACKBAR = 'hideSnackbar'
4 | export const SET_LINES = 'setLines'
5 | export const SET_LINE = 'setLine'
6 | export const SET_MAP = 'setMap'
7 | export const SET_PATH = 'setPath'
8 | export const SET_MEMBER = 'setMember'
9 | export const SET_FAVORITES = 'setFavorites'
10 | export const SET_ACCESS_TOKEN = 'setAccessToken'
11 |
--------------------------------------------------------------------------------
/frontend/src/styles/color.scss:
--------------------------------------------------------------------------------
1 | .bg-grey-lighten-5 {
2 | background-color: #fafafa;
3 | }
4 |
5 | .bg-grey-lighten-4 {
6 | background-color: #f5f5f5;
7 | }
8 |
9 | .bg-grey-lighten-3 {
10 | background-color: #eeeeee;
11 | }
12 |
13 | .bg-grey-lighten-2 {
14 | background-color: #e0e0e0;
15 | }
16 |
17 | .bg-grey-lighten-1 {
18 | background-color: #bdbdbd;
19 | }
20 |
21 | .bg-light-gray {
22 | background-color: #f5f5f5 !important;
23 | }
24 |
25 | .bg-apricot {
26 | background-color: #ffe0b3;
27 | }
28 |
29 | .bg-aliceblue {
30 | background-color: aliceblue !important;
31 | }
32 |
33 | .bg-lightblue {
34 | background-color: lightblue !important;
35 | }
36 |
37 | .bg-darkseagreen {
38 | background-color: darkseagreen !important;
39 | }
40 |
41 | .bg-bisque {
42 | background-color: bisque !important;
43 | }
44 |
45 | .bg-gray {
46 | background-color: gray !important;
47 | }
48 |
49 | .bg-whitesmoke {
50 | background-color: whitesmoke !important;
51 | }
52 |
53 | .bg-f8 {
54 | background-color: #f8f8f8 !important;
55 | }
56 |
57 | .bg-gainsboro {
58 | background-color: gainsboro;
59 | }
60 |
61 | .bg-black {
62 | background-color: #333 !important;
63 | }
64 |
65 | .bg-blue {
66 | background-color: cornflowerblue !important;
67 | }
68 |
69 | .bg-green {
70 | background-color: #64c5b1 !important;
71 | }
--------------------------------------------------------------------------------
/frontend/src/styles/components/alert.scss:
--------------------------------------------------------------------------------
1 | .alert-success-border {
2 | border-left: 8px solid #4caf50 !important;
3 | }
4 |
5 | .alert-warning-border {
6 | border-left: 8px solid #fb8c00 !important;
7 | }
8 |
9 | .alert-info-border {
10 | border-left: 8px solid #2196f3 !important;
11 | }
12 |
13 | .alert-error-border {
14 | border-left: 8px solid #ff5252 !important;
15 | }
16 |
17 | .alert-cyan-border {
18 | border-left: 8px solid #80deea !important;
19 | background-color: #eefdff !important;
20 | color: #006064 !important;
21 | }
22 |
23 | .alert-dark-border {
24 | border-left: 8px solid #333 !important;
25 | background-color: #eee !important;
26 | color: #333 !important;
27 | }
28 |
--------------------------------------------------------------------------------
/frontend/src/styles/fonts.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'hanna';
3 | src: url(/fonts/BMHANNAPro.otf) format('woff2');
4 | }
5 |
6 | @font-face {
7 | font-family: 'hannaair';
8 | src: url(/fonts/BMHANNAAir.otf) format('woff2');
9 | }
10 |
11 | @font-face {
12 | font-family: 'euljiro';
13 | src: url(/fonts/BMEULJIRO.otf) format('woff2');
14 | }
15 |
16 | @font-face {
17 | font-family: 'jua';
18 | src: url(/fonts/BMJUA.otf) format('woff2');
19 | }
20 |
21 | .font-bamin {
22 | font-family: hanna, 'Noto Sans KR', 'Roboto', sans-serif, 'Apple SD Gothic Neo' !important;
23 | }
24 |
25 | .font-hanna {
26 | font-family: hanna, 'Noto Sans KR', 'Roboto', sans-serif, 'Apple SD Gothic Neo' !important;
27 | }
28 |
29 | .font-hannaair {
30 | font-family: hannaair, 'Noto Sans KR', 'Roboto', sans-serif, 'Apple SD Gothic Neo' !important;
31 | }
32 |
33 | .font-euljiro {
34 | font-family: euljiro, 'Noto Sans KR', 'Roboto', sans-serif, 'Apple SD Gothic Neo' !important;
35 | }
36 |
37 | .font-jua {
38 | font-family: jua, 'Noto Sans KR', 'Roboto', sans-serif, 'Apple SD Gothic Neo' !important;
39 | }
40 |
41 | .font-bamin-light {
42 | font-family: hannaair, 'Noto Sans KR', 'Roboto', sans-serif, 'Apple SD Gothic Neo' !important;
43 | }
44 |
--------------------------------------------------------------------------------
/frontend/src/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import './app.scss';
2 | @import './layout.scss';
3 |
--------------------------------------------------------------------------------
/frontend/src/utils/constants.js:
--------------------------------------------------------------------------------
1 | export const SNACKBAR_MESSAGES = {
2 | COMMON: {
3 | SUCCESS: '😀 성공적으로 변경되었습니다.',
4 | FAIL: '😰 오류가 발생했습니다.'
5 | },
6 | LOGIN: {
7 | SUCCESS: '😀 방문을 환영합니다.',
8 | FAIL: '😰 로그인 중에 오류가 발생했습니다.'
9 | },
10 | LOGOUT: {
11 | SUCCESS: '😀 로그아웃 되었습니다. 다음에 또 만나요.',
12 | FAIL: '😰 로그아웃 중에 오류가 발생했습니다.'
13 | },
14 | MEMBER: {
15 | EDIT: {
16 | SUCCESS: '😀 정보가 수정되었습니다.',
17 | FAIL: '😰 정보를 수정하는 과정에 오류가 발생했습니다.'
18 | },
19 | DELETE: {
20 | SUCCESS: '😀 정상적으로 탈퇴되었습니다. 다음에 또 만나요.',
21 | FAIL: '😰 탈퇴 하는 과정에 오류가 발생했습니다.'
22 | }
23 | },
24 | FAVORITE: {
25 | FETCH: {
26 | FAIL: '😰 즐겨찾기 목록을 불러오는 과정에 오류가 발생했습니다.'
27 | },
28 | ADD: {
29 | SUCCESS: '😀 즐겨찾기에 추가되었습니다.',
30 | FAIL: '😰 즐겨찾기에 추가하는 과정에 오류가 발생했습니다.'
31 | },
32 | DELETE: {
33 | SUCCESS: '😀 성공적으로 삭제 하습니다.',
34 | FAIL: '😰 즐겨찾기 항목을 삭제하는 과정에 오류가 발생했습니다.'
35 | }
36 | },
37 | PATH: {
38 | ARRIVAL_TIME: {
39 | SUCCESS: '😀 빠른 도착으로 다시 검색 하습니다.',
40 | FAIL: '😰 빠른 도착으로 다시 검색하는 과정에 오류가 발생했습니다.'
41 | }
42 | }
43 | }
44 |
45 | export const PATH_TYPE = {
46 | DISTANCE: 'DISTANCE',
47 | DURATION: 'DURATION',
48 | ARRIVAL_TIME: 'ARRIVAL_TIME'
49 | }
50 |
51 | export const LINE_COLORS = [
52 | 'grey lighten-5',
53 | 'grey lighten-4',
54 | 'grey lighten-3',
55 | 'grey lighten-2',
56 | 'grey lighten-1',
57 | 'grey darken-1',
58 | 'grey darken-2',
59 | 'grey darken-3',
60 | 'grey darken-4',
61 |
62 | 'red lighten-5',
63 | 'red lighten-4',
64 | 'red lighten-3',
65 | 'red lighten-2',
66 | 'red lighten-1',
67 | 'red darken-1',
68 | 'red darken-2',
69 | 'red darken-3',
70 | 'red darken-4',
71 |
72 | 'orange lighten-5',
73 | 'orange lighten-4',
74 | 'orange lighten-3',
75 | 'orange lighten-2',
76 | 'orange lighten-1',
77 | 'orange darken-1',
78 | 'orange darken-2',
79 | 'orange darken-3',
80 | 'orange darken-4',
81 |
82 | 'yellow lighten-5',
83 | 'yellow lighten-4',
84 | 'yellow lighten-3',
85 | 'yellow lighten-2',
86 | 'yellow lighten-1',
87 | 'yellow darken-1',
88 | 'yellow darken-2',
89 | 'yellow darken-3',
90 | 'yellow darken-4',
91 |
92 | 'green lighten-5',
93 | 'green lighten-4',
94 | 'green lighten-3',
95 | 'green lighten-2',
96 | 'green lighten-1',
97 | 'green darken-1',
98 | 'green darken-2',
99 | 'green darken-3',
100 | 'green darken-4',
101 |
102 | 'teal lighten-5',
103 | 'teal lighten-4',
104 | 'teal lighten-3',
105 | 'teal lighten-2',
106 | 'teal lighten-1',
107 | 'teal darken-1',
108 | 'teal darken-2',
109 | 'teal darken-3',
110 | 'teal darken-4',
111 |
112 | 'blue lighten-5',
113 | 'blue lighten-4',
114 | 'blue lighten-3',
115 | 'blue lighten-2',
116 | 'blue lighten-1',
117 | 'blue darken-1',
118 | 'blue darken-2',
119 | 'blue darken-3',
120 | 'blue darken-4',
121 |
122 | 'indigo lighten-5',
123 | 'indigo lighten-4',
124 | 'indigo lighten-3',
125 | 'indigo lighten-2',
126 | 'indigo lighten-1',
127 | 'indigo darken-1',
128 | 'indigo darken-2',
129 | 'indigo darken-3',
130 | 'indigo darken-4',
131 |
132 | 'purple lighten-5',
133 | 'purple lighten-4',
134 | 'purple lighten-3',
135 | 'purple lighten-2',
136 | 'purple lighten-1',
137 | 'purple darken-1',
138 | 'purple darken-2',
139 | 'purple darken-3',
140 | 'purple darken-4',
141 |
142 | 'pink lighten-5',
143 | 'pink lighten-4',
144 | 'pink lighten-3',
145 | 'pink lighten-2',
146 | 'pink lighten-1',
147 | 'pink darken-1',
148 | 'pink darken-2',
149 | 'pink darken-3',
150 | 'pink darken-4'
151 | ]
152 |
--------------------------------------------------------------------------------
/frontend/src/utils/plugin/vuetify.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuetify from 'vuetify'
3 | import 'vuetify/dist/vuetify.min.css'
4 |
5 | Vue.use(Vuetify)
6 |
7 | const opts = {}
8 |
9 | export default new Vuetify(opts)
10 |
--------------------------------------------------------------------------------
/frontend/src/utils/validator.js:
--------------------------------------------------------------------------------
1 | const validator = {
2 | path: {
3 | source: [],
4 | target: []
5 | },
6 | departureTime: {
7 | dayTime: [],
8 | hour: [],
9 | minute: []
10 | },
11 | stationName: [(v) => !!v || '이름 입력이 필요합니다.', (v) => v.length > 0 || '이름은 1글자 이상 입력해야 합니다.'],
12 | line: {
13 | name: [(v) => !!v || '이름 입력이 필요합니다.'],
14 | color: [(v) => !!v || '색상 입력이 필요합니다.'],
15 | },
16 | section: {
17 | upStationId: [(v) => !!v || '상행역을 선택하세요.'],
18 | downStationId: [(v) => !!v || '하행역을 선택하세요.'],
19 | distance: [(v) => !!v || '거리 입력이 필요합니다.']
20 | },
21 | member: {
22 | email: [(v) => !!v || '이메일 입력이 필요합니다.', (v) => /.+@.+/.test(v) || '유효한 이메일을 입력해주세요'],
23 | age: [(v) => !!v || '나이 입력이 필요합니다.', (v) => v > 0 || '나이는 1살 이상 이어야 합니다.'],
24 | password: [(v) => !!v || '비밀번호 입력이 필요합니다.'],
25 | confirmPassword: [(v) => !!v || '비밀번호 확인이 필요합니다.', (v, c) => v === c || '비밀번호가 일치하지 않습니다.']
26 | }
27 | }
28 |
29 | export default validator
30 |
--------------------------------------------------------------------------------
/frontend/src/views/auth/JoinPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 회원가입
8 |
9 |
10 |
11 |
20 |
21 |
22 |
31 |
32 |
33 |
43 |
44 |
45 |
55 |
56 |
57 |
58 |
59 |
60 | 회원가입
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
112 |
--------------------------------------------------------------------------------
/frontend/src/views/auth/LoginPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 로그인
8 |
9 |
10 |
11 |
20 |
21 |
22 |
32 |
33 |
34 |
35 |
36 |
37 | 로그인
38 |
39 |
40 |
41 | 아직 회원이 아니신가요?
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
93 |
--------------------------------------------------------------------------------
/frontend/src/views/auth/Mypage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 나의 정보
7 |
8 |
9 |
10 |
11 |
12 | {{ member.email }}
13 |
14 |
15 |
16 |
17 |
18 | {{ member.age }}
19 |
20 |
21 |
22 |
23 |
24 |
25 | 탈퇴
26 |
27 |
28 | 수정
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
72 |
--------------------------------------------------------------------------------
/frontend/src/views/auth/MypageEdit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 나의 정보 수정
8 |
9 |
10 |
11 |
20 |
21 |
22 |
31 |
32 |
33 |
43 |
44 |
45 |
55 |
56 |
57 |
58 |
59 |
60 | 취소
61 |
62 |
63 | 확인
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
131 |
--------------------------------------------------------------------------------
/frontend/src/views/base/header/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | RUNNINGMAP
8 |
9 |
10 |
11 |
12 | {{ navItem.text }}
13 | 로그인
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | {{ member.email.split('@')[0] }}
22 | ti-angle-down
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
79 |
84 |
--------------------------------------------------------------------------------
/frontend/src/views/base/header/components/FavoritesButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | mdi-star
5 | 즐겨찾기
6 |
7 |
8 |
9 |
10 |
18 |
--------------------------------------------------------------------------------
/frontend/src/views/base/header/components/LogoutButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | mdi-logout-variant
5 | 로그아웃
6 |
7 |
8 |
9 |
10 |
36 |
--------------------------------------------------------------------------------
/frontend/src/views/base/header/components/MyPageButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | mdi-account
5 | 나의 페이지
6 |
7 |
8 |
9 |
10 |
18 |
--------------------------------------------------------------------------------
/frontend/src/views/favorite/Favorites.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 즐겨 찾기
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | mdi-subway
18 |
19 | {{ favorite.source.name }}
20 |
21 | mdi-arrow-right-bold
22 |
23 |
24 | mdi-subway
25 |
26 | {{ favorite.target.name }}
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
74 |
79 |
--------------------------------------------------------------------------------
/frontend/src/views/favorite/components/FavoriteDeleteButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | mdi-delete
4 |
5 |
6 |
7 |
36 |
--------------------------------------------------------------------------------
/frontend/src/views/line/LinePage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 노선 관리
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | {{ line.name }}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
65 |
66 |
71 |
--------------------------------------------------------------------------------
/frontend/src/views/line/components/LineCreateButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
77 |
78 |
79 |
160 |
161 |
170 |
--------------------------------------------------------------------------------
/frontend/src/views/line/components/LineDeleteButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | mdi-delete
4 |
5 |
6 |
7 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/frontend/src/views/line/components/LineEditButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
49 |
50 |
51 |
110 |
115 |
--------------------------------------------------------------------------------
/frontend/src/views/line/components/LineForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
21 |
22 |
23 |
31 |
32 |
33 |
41 |
42 |
43 |
44 |
45 |
46 | 노선의 색상을 아래 팔레트에서 선택해주세요.
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
108 |
109 |
114 |
--------------------------------------------------------------------------------
/frontend/src/views/main/MainPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |

5 |
6 | RunningMap Service 페이지입니다.
7 | 제공되는 템플릿을 활용해 프로젝트를 완성해 보세요!
8 |
9 |
10 | -
11 |
12 | {{ navItem.text }}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
46 |
47 |
56 |
--------------------------------------------------------------------------------
/frontend/src/views/map/MapPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ line.name }}
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
39 |
58 |
--------------------------------------------------------------------------------
/frontend/src/views/path/PathPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 경로 검색
7 |
8 |
9 |
10 |
11 |
21 | mdi-arrow-right-bold
22 |
32 |
33 |
34 | 검색
35 |
36 |
37 |
38 |
39 |
40 | 최단 거리
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | 거리 |
49 |
50 |
51 |
52 |
53 | {{ pathResult.distance }}km |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | 거리 |
65 |
66 |
67 |
68 |
69 | {{ pathResult.distance }}km |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | mdi-subway
86 |
87 | {{ station.name }}
88 |
89 | mdi-arrow-right-bold
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
158 |
--------------------------------------------------------------------------------
/frontend/src/views/path/components/AddFavoriteButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | mdi-star-outline
6 |
7 |
8 | 즐겨 찾기
9 |
10 |
11 |
12 |
41 |
--------------------------------------------------------------------------------
/frontend/src/views/section/SectionPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 구간 관리
7 |
8 |
9 |
10 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | {{ activeLine.name }}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | 아직 추가된 역이 없습니다.
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
117 |
--------------------------------------------------------------------------------
/frontend/src/views/section/components/SectionCreateButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
54 |
55 |
56 |
169 |
170 |
175 |
--------------------------------------------------------------------------------
/frontend/src/views/section/components/SectionDeleteButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | mdi-delete
4 |
5 |
6 |
7 |
44 |
--------------------------------------------------------------------------------
/frontend/src/views/station/StationPage.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 지하철 역 관리
7 |
8 |
9 |
10 |
11 |
23 | 추가
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | mdi-delete
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
105 |
106 |
111 |
--------------------------------------------------------------------------------
/frontend/webpack.common.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const VuetifyLoaderPlugin = require('vuetify-loader/lib/plugin')
3 | const VueLoaderPlugin = require('vue-loader/lib/plugin')
4 | const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin')
5 |
6 | const clientPath = path.resolve(__dirname, 'src')
7 |
8 | module.exports = {
9 | entry: {
10 | 'js/vendors': ['vue', 'vue-router', 'vuex', 'vuetify', 'axios', 'vue-axios'],
11 | 'js/main': ['babel-polyfill', `${clientPath}/main.js`]
12 | },
13 | resolve: {
14 | alias: {
15 | vue$: 'vue/dist/vue.esm.js',
16 | '@': path.join(__dirname, 'src')
17 | },
18 | extensions: ['*', '.js', '.vue', '.json']
19 | },
20 | optimization: {
21 | splitChunks: {
22 | chunks: 'all',
23 | cacheGroups: {
24 | vendors: {
25 | test: /[\\/]node_modules[\\/]/,
26 | name: 'js/vendors'
27 | }
28 | }
29 | }
30 | },
31 | module: {
32 | rules: [
33 | { test: /\.vue$/, loader: 'vue-loader' },
34 | {
35 | test: /\.s(c|a)ss$/,
36 | use: [
37 | 'vue-style-loader',
38 | 'css-loader',
39 | {
40 | loader: 'sass-loader',
41 | options: {
42 | implementation: require('sass'),
43 | sassOptions: {
44 | fiber: require('fibers')
45 | }
46 | }
47 | }
48 | ]
49 | },
50 | {
51 | test: /\.(jpe?g|png|gif)$/i,
52 | use: [
53 | {
54 | loader: 'url-loader'
55 | }
56 | ]
57 | },
58 | {
59 | test: /\.css$/,
60 | loader: 'style-loader!css-loader'
61 | },
62 | {
63 | test: /\.js$/,
64 | exclude: /node_modules/,
65 | use: {
66 | loader: 'babel-loader'
67 | }
68 | }
69 | ]
70 | },
71 | plugins: [new VuetifyLoaderPlugin(), new VueLoaderPlugin(), new CaseSensitivePathsPlugin()]
72 | }
73 |
--------------------------------------------------------------------------------
/frontend/webpack.config.js:
--------------------------------------------------------------------------------
1 | const commonConfig = require('./webpack.common.js')
2 | const webpackMerge = require('webpack-merge')
3 | const { argv } = require('yargs')
4 |
5 | module.exports = () => {
6 | const envConfig = require(`./webpack.${argv.env}.js`)
7 | return webpackMerge(commonConfig, envConfig)
8 | }
9 |
--------------------------------------------------------------------------------
/frontend/webpack.dev.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const outputPath = path.resolve(__dirname, 'out')
4 |
5 | module.exports = {
6 | mode: 'development',
7 | devtool: 'cheap-eval-source-map',
8 | output: {
9 | path: outputPath,
10 | filename: '[name].js'
11 | },
12 | devServer: {
13 | contentBase: outputPath,
14 | publicPath: '/',
15 | host: '0.0.0.0',
16 | port: 8081,
17 | proxy: {
18 | '/resources/\\d*/js/(main|vendors).js': {
19 | target: 'http://127.0.0.1:8081',
20 | pathRewrite: {'/resources/\\d*' : ''}
21 | },
22 | '**': 'http://127.0.0.1:8080'
23 | },
24 | inline: true,
25 | hot: false
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/frontend/webpack.prod.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 |
3 | const outputPath = path.resolve(__dirname, '../src/main/resources/static')
4 |
5 | module.exports = {
6 | mode: 'production',
7 | output: {
8 | path: outputPath,
9 | filename: '[name].js'
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/next-step/infra-subway-deploy/22d6124f5936782075f2b09f5d3d9052fe39140d/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | maven { url 'https://repo.spring.io/milestone' }
4 | maven { url 'https://repo.spring.io/snapshot' }
5 | gradlePluginPortal()
6 | }
7 | resolutionStrategy {
8 | eachPlugin {
9 | if (requested.id.id == 'org.springframework.boot') {
10 | useModule("org.springframework.boot:spring-boot-gradle-plugin:${requested.version}")
11 | }
12 | }
13 | }
14 | }
15 | rootProject.name = 'subway'
16 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/PageController.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway;
2 |
3 | import org.springframework.http.MediaType;
4 | import org.springframework.stereotype.Controller;
5 | import org.springframework.web.bind.annotation.GetMapping;
6 |
7 | @Controller
8 | public class PageController {
9 | @GetMapping(value = {
10 | "/",
11 | "/stations",
12 | "/lines",
13 | "/sections",
14 | "/path",
15 | "/login",
16 | "/join",
17 | "/mypage",
18 | "/mypage/edit",
19 | "/favorites"}, produces = MediaType.TEXT_HTML_VALUE)
20 | public String index() {
21 | return "index";
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/SubwayApplication.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
6 | import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
7 |
8 | @EnableJpaRepositories
9 | @EnableJpaAuditing
10 | @SpringBootApplication
11 | public class SubwayApplication {
12 |
13 | public static void main(String[] args) {
14 | SpringApplication.run(SubwayApplication.class, args);
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/auth/application/AuthService.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.auth.application;
2 |
3 | import nextstep.subway.auth.domain.LoginMember;
4 | import nextstep.subway.auth.dto.TokenRequest;
5 | import nextstep.subway.auth.dto.TokenResponse;
6 | import nextstep.subway.auth.infrastructure.JwtTokenProvider;
7 | import nextstep.subway.member.domain.Member;
8 | import nextstep.subway.member.domain.MemberRepository;
9 | import org.springframework.stereotype.Service;
10 | import org.springframework.transaction.annotation.Transactional;
11 |
12 | @Service
13 | @Transactional
14 | public class AuthService {
15 | private MemberRepository memberRepository;
16 | private JwtTokenProvider jwtTokenProvider;
17 |
18 | public AuthService(MemberRepository memberRepository, JwtTokenProvider jwtTokenProvider) {
19 | this.memberRepository = memberRepository;
20 | this.jwtTokenProvider = jwtTokenProvider;
21 | }
22 |
23 | public TokenResponse login(TokenRequest request) {
24 | Member member = memberRepository.findByEmail(request.getEmail()).orElseThrow(AuthorizationException::new);
25 | member.checkPassword(request.getPassword());
26 |
27 | String token = jwtTokenProvider.createToken(request.getEmail());
28 | return new TokenResponse(token);
29 | }
30 |
31 | public LoginMember findMemberByToken(String credentials) {
32 | if (!jwtTokenProvider.validateToken(credentials)) {
33 | throw new AuthorizationException();
34 | }
35 |
36 | String email = jwtTokenProvider.getPayload(credentials);
37 | Member member = memberRepository.findByEmail(email).orElseThrow(RuntimeException::new);
38 | return new LoginMember(member.getId(), member.getEmail(), member.getAge());
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/auth/application/AuthorizationException.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.auth.application;
2 |
3 | import org.springframework.http.HttpStatus;
4 | import org.springframework.web.bind.annotation.ResponseStatus;
5 |
6 | @ResponseStatus(HttpStatus.UNAUTHORIZED)
7 | public class AuthorizationException extends RuntimeException {
8 | public AuthorizationException() {
9 | }
10 |
11 | public AuthorizationException(String message) {
12 | super(message);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/auth/domain/AuthenticationPrincipal.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.auth.domain;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Target(ElementType.PARAMETER)
9 | @Retention(RetentionPolicy.RUNTIME)
10 | public @interface AuthenticationPrincipal {
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/auth/domain/LoginMember.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.auth.domain;
2 |
3 | public class LoginMember {
4 | private Long id;
5 | private String email;
6 | private Integer age;
7 |
8 | public LoginMember(Long id, String email, Integer age) {
9 | this.id = id;
10 | this.email = email;
11 | this.age = age;
12 | }
13 |
14 | public Long getId() {
15 | return id;
16 | }
17 |
18 | public String getEmail() {
19 | return email;
20 | }
21 |
22 | public Integer getAge() {
23 | return age;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/auth/dto/TokenRequest.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.auth.dto;
2 |
3 | public class TokenRequest {
4 | private String email;
5 | private String password;
6 |
7 | public TokenRequest() {
8 | }
9 |
10 | public TokenRequest(String email, String password) {
11 | this.email = email;
12 | this.password = password;
13 | }
14 |
15 | public String getEmail() {
16 | return email;
17 | }
18 |
19 | public String getPassword() {
20 | return password;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/auth/dto/TokenResponse.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.auth.dto;
2 |
3 | public class TokenResponse {
4 | private String accessToken;
5 |
6 | public TokenResponse() {
7 | }
8 |
9 | public TokenResponse(String accessToken) {
10 | this.accessToken = accessToken;
11 | }
12 |
13 | public String getAccessToken() {
14 | return accessToken;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/auth/infrastructure/AuthorizationExtractor.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.auth.infrastructure;
2 |
3 | import javax.servlet.http.HttpServletRequest;
4 | import java.util.Enumeration;
5 |
6 | public class AuthorizationExtractor {
7 | public static final String AUTHORIZATION = "Authorization";
8 | public static final String BEARER_TYPE = "Bearer";
9 | public static final String ACCESS_TOKEN_TYPE = AuthorizationExtractor.class.getSimpleName() + ".ACCESS_TOKEN_TYPE";
10 |
11 | private AuthorizationExtractor() {
12 | }
13 |
14 | public static String extract(HttpServletRequest request) {
15 | Enumeration headers = request.getHeaders(AUTHORIZATION);
16 | while (headers.hasMoreElements()) {
17 | String value = headers.nextElement();
18 | if (isBearerType(value)) {
19 | request.setAttribute(ACCESS_TOKEN_TYPE, BEARER_TYPE);
20 | return extractAuthHeader(value);
21 | }
22 | }
23 |
24 | return null;
25 | }
26 |
27 | private static String extractAuthHeader(String value) {
28 | String authHeaderValue = value.substring(BEARER_TYPE.length()).trim();
29 | return (authHeaderValue.contains(","))
30 | ? authHeaderValue.split(",")[0]
31 | : authHeaderValue;
32 | }
33 |
34 | private static boolean isBearerType(String value) {
35 | return value.toLowerCase().startsWith(BEARER_TYPE.toLowerCase());
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/auth/infrastructure/JwtTokenProvider.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.auth.infrastructure;
2 |
3 | import io.jsonwebtoken.*;
4 | import org.springframework.beans.factory.annotation.Value;
5 | import org.springframework.stereotype.Component;
6 |
7 | import java.util.Date;
8 |
9 | @Component
10 | public class JwtTokenProvider {
11 | @Value("${security.jwt.token.secret-key}")
12 | private String secretKey;
13 | @Value("${security.jwt.token.expire-length}")
14 | private long validityInMilliseconds;
15 |
16 | public String createToken(String payload) {
17 | Claims claims = Jwts.claims().setSubject(payload);
18 | Date now = new Date();
19 | Date validity = new Date(now.getTime() + validityInMilliseconds);
20 |
21 | return Jwts.builder()
22 | .setClaims(claims)
23 | .setIssuedAt(now)
24 | .setExpiration(validity)
25 | .signWith(SignatureAlgorithm.HS256, secretKey)
26 | .compact();
27 | }
28 |
29 | public String getPayload(String token) {
30 | return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
31 | }
32 |
33 | public boolean validateToken(String token) {
34 | try {
35 | Jws claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
36 |
37 | return !claims.getBody().getExpiration().before(new Date());
38 | } catch (JwtException | IllegalArgumentException e) {
39 | return false;
40 | }
41 | }
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/auth/ui/AuthController.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.auth.ui;
2 |
3 | import nextstep.subway.auth.application.AuthService;
4 | import nextstep.subway.auth.dto.TokenRequest;
5 | import nextstep.subway.auth.dto.TokenResponse;
6 | import org.springframework.http.ResponseEntity;
7 | import org.springframework.web.bind.annotation.PostMapping;
8 | import org.springframework.web.bind.annotation.RequestBody;
9 | import org.springframework.web.bind.annotation.RestController;
10 |
11 | @RestController
12 | public class AuthController {
13 | private AuthService authService;
14 |
15 | public AuthController(AuthService authService) {
16 | this.authService = authService;
17 | }
18 |
19 | @PostMapping("/login/token")
20 | public ResponseEntity login(@RequestBody TokenRequest request) {
21 | TokenResponse token = authService.login(request);
22 | return ResponseEntity.ok().body(token);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/auth/ui/AuthenticationPrincipalArgumentResolver.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.auth.ui;
2 |
3 | import nextstep.subway.auth.application.AuthService;
4 | import nextstep.subway.auth.domain.AuthenticationPrincipal;
5 | import nextstep.subway.auth.infrastructure.AuthorizationExtractor;
6 | import org.springframework.core.MethodParameter;
7 | import org.springframework.web.bind.support.WebDataBinderFactory;
8 | import org.springframework.web.context.request.NativeWebRequest;
9 | import org.springframework.web.method.support.HandlerMethodArgumentResolver;
10 | import org.springframework.web.method.support.ModelAndViewContainer;
11 |
12 | import javax.servlet.http.HttpServletRequest;
13 |
14 | public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver {
15 | private AuthService authService;
16 |
17 | public AuthenticationPrincipalArgumentResolver(AuthService authService) {
18 | this.authService = authService;
19 | }
20 |
21 | @Override
22 | public boolean supportsParameter(MethodParameter parameter) {
23 | return parameter.hasParameterAnnotation(AuthenticationPrincipal.class);
24 | }
25 |
26 | @Override
27 | public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
28 | String credentials = AuthorizationExtractor.extract(webRequest.getNativeRequest(HttpServletRequest.class));
29 | return authService.findMemberByToken(credentials);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/common/AuthenticationPrincipalConfig.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.common;
2 |
3 | import nextstep.subway.auth.application.AuthService;
4 | import nextstep.subway.auth.ui.AuthenticationPrincipalArgumentResolver;
5 | import org.springframework.context.annotation.Bean;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
8 |
9 | import java.util.List;
10 |
11 | @Configuration
12 | public class AuthenticationPrincipalConfig implements WebMvcConfigurer {
13 | private final AuthService authService;
14 |
15 | public AuthenticationPrincipalConfig(AuthService authService) {
16 | this.authService = authService;
17 | }
18 |
19 | @Override
20 | public void addArgumentResolvers(List argumentResolvers) {
21 | argumentResolvers.add(createAuthenticationPrincipalArgumentResolver());
22 | }
23 |
24 | @Bean
25 | public AuthenticationPrincipalArgumentResolver createAuthenticationPrincipalArgumentResolver() {
26 | return new AuthenticationPrincipalArgumentResolver(authService);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/common/BaseEntity.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.common;
2 |
3 | import org.springframework.data.annotation.CreatedDate;
4 | import org.springframework.data.annotation.LastModifiedDate;
5 | import org.springframework.data.jpa.domain.support.AuditingEntityListener;
6 |
7 | import javax.persistence.EntityListeners;
8 | import javax.persistence.MappedSuperclass;
9 | import java.time.LocalDateTime;
10 |
11 | @MappedSuperclass
12 | @EntityListeners(AuditingEntityListener.class)
13 | public class BaseEntity {
14 | @CreatedDate
15 | private LocalDateTime createdDate;
16 |
17 | @LastModifiedDate
18 | private LocalDateTime modifiedDate;
19 |
20 | public LocalDateTime getCreatedDate() {
21 | return createdDate;
22 | }
23 |
24 | public LocalDateTime getModifiedDate() {
25 | return modifiedDate;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/favorite/application/FavoriteService.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.favorite.application;
2 |
3 | import nextstep.subway.auth.domain.LoginMember;
4 | import nextstep.subway.favorite.domain.Favorite;
5 | import nextstep.subway.favorite.domain.FavoriteRepository;
6 | import nextstep.subway.favorite.domain.HasNotPermissionException;
7 | import nextstep.subway.favorite.dto.FavoriteRequest;
8 | import nextstep.subway.favorite.dto.FavoriteResponse;
9 | import nextstep.subway.station.domain.Station;
10 | import nextstep.subway.station.domain.StationRepository;
11 | import nextstep.subway.station.dto.StationResponse;
12 | import org.springframework.stereotype.Service;
13 |
14 | import java.util.HashSet;
15 | import java.util.List;
16 | import java.util.Map;
17 | import java.util.Set;
18 | import java.util.function.Function;
19 | import java.util.stream.Collectors;
20 |
21 | @Service
22 | public class FavoriteService {
23 | private FavoriteRepository favoriteRepository;
24 | private StationRepository stationRepository;
25 |
26 | public FavoriteService(FavoriteRepository favoriteRepository, StationRepository stationRepository) {
27 | this.favoriteRepository = favoriteRepository;
28 | this.stationRepository = stationRepository;
29 | }
30 |
31 | public void createFavorite(LoginMember loginMember, FavoriteRequest request) {
32 | Favorite favorite = new Favorite(loginMember.getId(), request.getSource(), request.getTarget());
33 | favoriteRepository.save(favorite);
34 | }
35 |
36 | public List findFavorites(LoginMember loginMember) {
37 | List favorites = favoriteRepository.findByMemberId(loginMember.getId());
38 | Map stations = extractStations(favorites);
39 |
40 | return favorites.stream()
41 | .map(it -> FavoriteResponse.of(
42 | it,
43 | StationResponse.of(stations.get(it.getSourceStationId())),
44 | StationResponse.of(stations.get(it.getTargetStationId()))))
45 | .collect(Collectors.toList());
46 | }
47 |
48 | public void deleteFavorite(LoginMember loginMember, Long id) {
49 | Favorite favorite = favoriteRepository.findById(id).orElseThrow(RuntimeException::new);
50 | if (!favorite.isCreatedBy(loginMember.getId())) {
51 | throw new HasNotPermissionException(loginMember.getId() + "는 삭제할 권한이 없습니다.");
52 | }
53 | favoriteRepository.deleteById(id);
54 | }
55 |
56 | private Map extractStations(List favorites) {
57 | Set stationIds = extractStationIds(favorites);
58 | return stationRepository.findAllById(stationIds).stream()
59 | .collect(Collectors.toMap(Station::getId, Function.identity()));
60 | }
61 |
62 | private Set extractStationIds(List favorites) {
63 | Set stationIds = new HashSet<>();
64 | for (Favorite favorite : favorites) {
65 | stationIds.add(favorite.getSourceStationId());
66 | stationIds.add(favorite.getTargetStationId());
67 | }
68 | return stationIds;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/favorite/domain/Favorite.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.favorite.domain;
2 |
3 | import nextstep.subway.common.BaseEntity;
4 |
5 | import javax.persistence.Entity;
6 | import javax.persistence.GeneratedValue;
7 | import javax.persistence.GenerationType;
8 | import javax.persistence.Id;
9 |
10 | @Entity
11 | public class Favorite extends BaseEntity {
12 | @Id
13 | @GeneratedValue(strategy = GenerationType.IDENTITY)
14 | private Long id;
15 | private Long memberId;
16 | private Long sourceStationId;
17 | private Long targetStationId;
18 |
19 | public Favorite() {
20 | }
21 |
22 | public Favorite(Long memberId, Long sourceStationId, Long targetStationId) {
23 | this.memberId = memberId;
24 | this.sourceStationId = sourceStationId;
25 | this.targetStationId = targetStationId;
26 | }
27 |
28 | public Long getId() {
29 | return id;
30 | }
31 |
32 | public Long getMemberId() {
33 | return memberId;
34 | }
35 |
36 | public Long getSourceStationId() {
37 | return sourceStationId;
38 | }
39 |
40 | public Long getTargetStationId() {
41 | return targetStationId;
42 | }
43 |
44 | public boolean isCreatedBy(Long memberId) {
45 | return this.memberId.equals(memberId);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/favorite/domain/FavoriteRepository.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.favorite.domain;
2 |
3 | import org.springframework.data.jpa.repository.JpaRepository;
4 |
5 | import java.util.List;
6 |
7 | public interface FavoriteRepository extends JpaRepository {
8 | List findByMemberId(Long memberId);
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/favorite/domain/HasNotPermissionException.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.favorite.domain;
2 |
3 | public class HasNotPermissionException extends RuntimeException {
4 | public HasNotPermissionException(String message) {
5 | super(message);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/favorite/dto/FavoriteRequest.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.favorite.dto;
2 |
3 | public class FavoriteRequest {
4 | private Long source;
5 | private Long target;
6 |
7 | public FavoriteRequest() {
8 | }
9 |
10 | public FavoriteRequest(Long source, Long target) {
11 | this.source = source;
12 | this.target = target;
13 | }
14 |
15 | public Long getSource() {
16 | return source;
17 | }
18 |
19 | public Long getTarget() {
20 | return target;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/favorite/dto/FavoriteResponse.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.favorite.dto;
2 |
3 | import nextstep.subway.favorite.domain.Favorite;
4 | import nextstep.subway.station.dto.StationResponse;
5 |
6 | public class FavoriteResponse {
7 | private Long id;
8 | private StationResponse source;
9 | private StationResponse target;
10 |
11 | public FavoriteResponse() {
12 | }
13 |
14 | public FavoriteResponse(Long id, StationResponse source, StationResponse target) {
15 | this.id = id;
16 | this.source = source;
17 | this.target = target;
18 | }
19 |
20 | public static FavoriteResponse of(Favorite favorite, StationResponse source, StationResponse target) {
21 | return new FavoriteResponse(favorite.getId(), source, target);
22 | }
23 |
24 | public Long getId() {
25 | return id;
26 | }
27 |
28 | public StationResponse getSource() {
29 | return source;
30 | }
31 |
32 | public StationResponse getTarget() {
33 | return target;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/favorite/ui/FavoriteController.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.favorite.ui;
2 |
3 | import nextstep.subway.auth.domain.AuthenticationPrincipal;
4 | import nextstep.subway.auth.domain.LoginMember;
5 | import nextstep.subway.favorite.application.FavoriteService;
6 | import nextstep.subway.favorite.dto.FavoriteRequest;
7 | import nextstep.subway.favorite.dto.FavoriteResponse;
8 | import org.springframework.http.ResponseEntity;
9 | import org.springframework.web.bind.annotation.*;
10 |
11 | import java.net.URI;
12 | import java.util.List;
13 |
14 | @RestController
15 | public class FavoriteController {
16 | private FavoriteService favoriteService;
17 |
18 | public FavoriteController(FavoriteService favoriteService) {
19 | this.favoriteService = favoriteService;
20 | }
21 |
22 | @PostMapping("/favorites")
23 | public ResponseEntity createFavorite(@AuthenticationPrincipal LoginMember loginMember, @RequestBody FavoriteRequest request) {
24 | favoriteService.createFavorite(loginMember, request);
25 | return ResponseEntity
26 | .created(URI.create("/favorites/" + 1L))
27 | .build();
28 | }
29 |
30 | @GetMapping("/favorites")
31 | public ResponseEntity> getFavorites(@AuthenticationPrincipal LoginMember loginMember) {
32 | List favorites = favoriteService.findFavorites(loginMember);
33 | return ResponseEntity.ok().body(favorites);
34 | }
35 |
36 | @DeleteMapping("/favorites/{id}")
37 | public ResponseEntity deleteFavorite(@AuthenticationPrincipal LoginMember loginMember, @PathVariable Long id) {
38 | favoriteService.deleteFavorite(loginMember, id);
39 | return ResponseEntity.noContent().build();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/line/application/LineService.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.line.application;
2 |
3 | import nextstep.subway.line.domain.Line;
4 | import nextstep.subway.line.domain.LineRepository;
5 | import nextstep.subway.line.dto.LineRequest;
6 | import nextstep.subway.line.dto.LineResponse;
7 | import nextstep.subway.line.dto.SectionRequest;
8 | import nextstep.subway.station.application.StationService;
9 | import nextstep.subway.station.domain.Station;
10 | import org.springframework.stereotype.Service;
11 | import org.springframework.transaction.annotation.Transactional;
12 |
13 | import java.util.List;
14 | import java.util.stream.Collectors;
15 |
16 | @Service
17 | @Transactional
18 | public class LineService {
19 | private LineRepository lineRepository;
20 | private StationService stationService;
21 |
22 | public LineService(LineRepository lineRepository, StationService stationService) {
23 | this.lineRepository = lineRepository;
24 | this.stationService = stationService;
25 | }
26 |
27 | public LineResponse saveLine(LineRequest request) {
28 | Station upStation = stationService.findById(request.getUpStationId());
29 | Station downStation = stationService.findById(request.getDownStationId());
30 | Line persistLine = lineRepository.save(new Line(request.getName(), request.getColor(), upStation, downStation, request.getDistance()));
31 | return LineResponse.of(persistLine);
32 | }
33 |
34 | public List findLineResponses() {
35 | List persistLines = lineRepository.findAll();
36 | return persistLines.stream()
37 | .map(LineResponse::of)
38 | .collect(Collectors.toList());
39 | }
40 |
41 | public List findLines() {
42 | return lineRepository.findAll();
43 | }
44 |
45 | public Line findLineById(Long id) {
46 | return lineRepository.findById(id).orElseThrow(RuntimeException::new);
47 | }
48 |
49 |
50 | public LineResponse findLineResponseById(Long id) {
51 | Line persistLine = findLineById(id);
52 | return LineResponse.of(persistLine);
53 | }
54 |
55 | public void updateLine(Long id, LineRequest lineUpdateRequest) {
56 | Line persistLine = lineRepository.findById(id).orElseThrow(RuntimeException::new);
57 | persistLine.update(new Line(lineUpdateRequest.getName(), lineUpdateRequest.getColor()));
58 | }
59 |
60 | public void deleteLineById(Long id) {
61 | lineRepository.deleteById(id);
62 | }
63 |
64 | public void addLineStation(Long lineId, SectionRequest request) {
65 | Line line = findLineById(lineId);
66 | Station upStation = stationService.findStationById(request.getUpStationId());
67 | Station downStation = stationService.findStationById(request.getDownStationId());
68 | line.addLineSection(upStation, downStation, request.getDistance());
69 | }
70 |
71 | public void removeLineStation(Long lineId, Long stationId) {
72 | Line line = findLineById(lineId);
73 | line.removeStation(stationId);
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/line/domain/LineRepository.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.line.domain;
2 |
3 | import org.springframework.data.jpa.repository.JpaRepository;
4 |
5 | public interface LineRepository extends JpaRepository {
6 | }
7 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/line/domain/Section.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.line.domain;
2 |
3 | import nextstep.subway.station.domain.Station;
4 |
5 | import javax.persistence.*;
6 | import java.io.Serializable;
7 |
8 | @Entity
9 | public class Section implements Serializable {
10 | @Id
11 | @GeneratedValue(strategy = GenerationType.IDENTITY)
12 | private Long id;
13 |
14 | @ManyToOne(cascade = CascadeType.PERSIST)
15 | @JoinColumn(name = "line_id")
16 | private Line line;
17 |
18 | @ManyToOne(cascade = CascadeType.PERSIST)
19 | @JoinColumn(name = "up_station_id")
20 | private Station upStation;
21 |
22 | @ManyToOne(cascade = CascadeType.PERSIST)
23 | @JoinColumn(name = "down_station_id")
24 | private Station downStation;
25 |
26 | private int distance;
27 |
28 | public Section() {
29 | }
30 |
31 | public Section(Line line, Station upStation, Station downStation, int distance) {
32 | this.line = line;
33 | this.upStation = upStation;
34 | this.downStation = downStation;
35 | this.distance = distance;
36 | }
37 |
38 | public Long getId() {
39 | return id;
40 | }
41 |
42 | public Line getLine() {
43 | return line;
44 | }
45 |
46 | public Station getUpStation() {
47 | return upStation;
48 | }
49 |
50 | public Boolean equalUpStation(Long stationId) {
51 | return this.upStation.getId().equals(stationId);
52 | }
53 |
54 | public Boolean equalUpStation(Station station) {
55 | return this.upStation.equals(station);
56 | }
57 |
58 | public Boolean equalDownStation(Long stationId) {
59 | return this.downStation.getId().equals(stationId);
60 | }
61 |
62 | public Boolean equalDownStation(Station station) {
63 | return this.downStation.equals(station);
64 | }
65 |
66 | public Station getDownStation() {
67 | return downStation;
68 | }
69 |
70 | public int getDistance() {
71 | return distance;
72 | }
73 |
74 | public void updateUpStation(Station station, int newDistance) {
75 | if (this.distance < newDistance) {
76 | throw new IllegalArgumentException("역과 역 사이의 거리보다 좁은 거리를 입력해주세요");
77 | }
78 | this.upStation = station;
79 | this.distance -= newDistance;
80 | }
81 |
82 | public void updateDownStation(Station station, int newDistance) {
83 | if (this.distance < newDistance) {
84 | throw new IllegalArgumentException("역과 역 사이의 거리보다 좁은 거리를 입력해주세요");
85 | }
86 | this.downStation = station;
87 | this.distance -= newDistance;
88 | }
89 |
90 | public boolean existDownStation() {
91 | return this.downStation != null;
92 | }
93 |
94 | public boolean existUpStation() {
95 | return this.upStation != null;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/line/dto/LineRequest.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.line.dto;
2 |
3 | import nextstep.subway.line.domain.Line;
4 |
5 | public class LineRequest {
6 | private String name;
7 | private String color;
8 | private Long upStationId;
9 | private Long downStationId;
10 | private int distance;
11 |
12 | private LineRequest() {
13 | }
14 |
15 | public String getName() {
16 | return name;
17 | }
18 |
19 | public String getColor() {
20 | return color;
21 | }
22 |
23 | public Long getUpStationId() {
24 | return upStationId;
25 | }
26 |
27 | public Long getDownStationId() {
28 | return downStationId;
29 | }
30 |
31 | public int getDistance() {
32 | return distance;
33 | }
34 |
35 | public Line toLine() {
36 | return new Line(name, color);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/line/dto/LineResponse.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.line.dto;
2 |
3 | import nextstep.subway.line.domain.Line;
4 | import nextstep.subway.station.dto.StationResponse;
5 |
6 | import java.time.LocalDateTime;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 | import java.util.stream.Collectors;
10 |
11 | public class LineResponse {
12 | private Long id;
13 | private String name;
14 | private String color;
15 | private List stations;
16 | private LocalDateTime createdDate;
17 | private LocalDateTime modifiedDate;
18 |
19 | public LineResponse() {
20 | }
21 |
22 | public LineResponse(Long id, String name, String color, List stations, LocalDateTime createdDate, LocalDateTime modifiedDate) {
23 | this.id = id;
24 | this.name = name;
25 | this.color = color;
26 | this.stations = stations;
27 | this.createdDate = createdDate;
28 | this.modifiedDate = modifiedDate;
29 | }
30 |
31 | public static LineResponse of(Line line) {
32 | if(isEmpty(line)) {
33 | return new LineResponse(line.getId(), line.getName(), line.getColor(), new ArrayList(), line.getCreatedDate(), line.getModifiedDate());
34 | }
35 | return new LineResponse(line.getId(), line.getName(), line.getColor(), assembleStations(line), line.getCreatedDate(), line.getModifiedDate());
36 | }
37 |
38 | private static boolean isEmpty(Line line) {
39 | return line.getStations().isEmpty();
40 | }
41 |
42 | private static List assembleStations(Line line) {
43 | return line.getStations().stream()
44 | .map(StationResponse::of)
45 | .collect(Collectors.toList());
46 | }
47 |
48 | public Long getId() {
49 | return id;
50 | }
51 |
52 | public String getName() {
53 | return name;
54 | }
55 |
56 | public String getColor() {
57 | return color;
58 | }
59 |
60 | public List getStations() {
61 | return stations;
62 | }
63 |
64 | public LocalDateTime getCreatedDate() {
65 | return createdDate;
66 | }
67 |
68 | public LocalDateTime getModifiedDate() {
69 | return modifiedDate;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/line/dto/SectionRequest.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.line.dto;
2 |
3 | public class SectionRequest {
4 | private Long upStationId;
5 | private Long downStationId;
6 | private int distance;
7 |
8 | private SectionRequest() {
9 | }
10 |
11 | public Long getUpStationId() {
12 | return upStationId;
13 | }
14 |
15 | public Long getDownStationId() {
16 | return downStationId;
17 | }
18 |
19 | public int getDistance() {
20 | return distance;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/line/ui/LineController.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.line.ui;
2 |
3 | import nextstep.subway.line.application.LineService;
4 | import nextstep.subway.line.dto.LineRequest;
5 | import nextstep.subway.line.dto.LineResponse;
6 | import nextstep.subway.line.dto.SectionRequest;
7 | import org.springframework.dao.DataIntegrityViolationException;
8 | import org.springframework.http.ResponseEntity;
9 | import org.springframework.web.bind.annotation.*;
10 |
11 | import java.net.URI;
12 | import java.util.List;
13 |
14 | @RestController
15 | @RequestMapping("/lines")
16 | public class LineController {
17 | private final LineService lineService;
18 |
19 | public LineController(final LineService lineService) {
20 | this.lineService = lineService;
21 | }
22 |
23 | @PostMapping
24 | public ResponseEntity createLine(@RequestBody LineRequest lineRequest) {
25 | LineResponse line = lineService.saveLine(lineRequest);
26 | return ResponseEntity.created(URI.create("/lines/" + line.getId())).body(line);
27 | }
28 |
29 | @GetMapping
30 | public ResponseEntity> findAllLines() {
31 | return ResponseEntity.ok(lineService.findLineResponses());
32 | }
33 |
34 | @GetMapping("/{id}")
35 | public ResponseEntity findLineById(@PathVariable Long id) {
36 | return ResponseEntity.ok(lineService.findLineResponseById(id));
37 | }
38 |
39 | @PutMapping("/{id}")
40 | public ResponseEntity updateLine(@PathVariable Long id, @RequestBody LineRequest lineUpdateRequest) {
41 | lineService.updateLine(id, lineUpdateRequest);
42 | return ResponseEntity.ok().build();
43 | }
44 |
45 | @DeleteMapping("/{id}")
46 | public ResponseEntity deleteLine(@PathVariable Long id) {
47 | lineService.deleteLineById(id);
48 | return ResponseEntity.noContent().build();
49 | }
50 |
51 | @PostMapping("/{lineId}/sections")
52 | public ResponseEntity addLineStation(@PathVariable Long lineId, @RequestBody SectionRequest sectionRequest) {
53 | lineService.addLineStation(lineId, sectionRequest);
54 | return ResponseEntity.ok().build();
55 | }
56 |
57 | @DeleteMapping("/{lineId}/sections")
58 | public ResponseEntity removeLineStation(@PathVariable Long lineId, @RequestParam Long stationId) {
59 | lineService.removeLineStation(lineId, stationId);
60 | return ResponseEntity.ok().build();
61 | }
62 |
63 | @ExceptionHandler(DataIntegrityViolationException.class)
64 | public ResponseEntity handleIllegalArgsException(DataIntegrityViolationException e) {
65 | return ResponseEntity.badRequest().build();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/map/application/MapService.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.map.application;
2 |
3 | import nextstep.subway.line.application.LineService;
4 | import nextstep.subway.line.domain.Line;
5 | import nextstep.subway.map.domain.SubwayPath;
6 | import nextstep.subway.map.dto.PathResponse;
7 | import nextstep.subway.map.dto.PathResponseAssembler;
8 | import nextstep.subway.station.application.StationService;
9 | import nextstep.subway.station.domain.Station;
10 | import org.springframework.stereotype.Service;
11 | import org.springframework.transaction.annotation.Transactional;
12 |
13 | import java.util.List;
14 |
15 | @Service
16 | @Transactional
17 | public class MapService {
18 | private LineService lineService;
19 | private StationService stationService;
20 | private PathService pathService;
21 |
22 | public MapService(LineService lineService, StationService stationService, PathService pathService) {
23 | this.lineService = lineService;
24 | this.stationService = stationService;
25 | this.pathService = pathService;
26 | }
27 |
28 | public PathResponse findPath(Long source, Long target) {
29 | List lines = lineService.findLines();
30 | Station sourceStation = stationService.findById(source);
31 | Station targetStation = stationService.findById(target);
32 | SubwayPath subwayPath = pathService.findPath(lines, sourceStation, targetStation);
33 |
34 | return PathResponseAssembler.assemble(subwayPath);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/map/application/PathService.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.map.application;
2 |
3 | import nextstep.subway.line.domain.Line;
4 | import nextstep.subway.map.domain.SectionEdge;
5 | import nextstep.subway.map.domain.SubwayGraph;
6 | import nextstep.subway.map.domain.SubwayPath;
7 | import nextstep.subway.station.domain.Station;
8 | import org.jgrapht.GraphPath;
9 | import org.jgrapht.alg.shortestpath.DijkstraShortestPath;
10 | import org.springframework.stereotype.Service;
11 |
12 | import java.util.List;
13 | import java.util.stream.Collectors;
14 |
15 | @Service
16 | public class PathService {
17 | public SubwayPath findPath(List lines, Station source, Station target) {
18 | SubwayGraph graph = new SubwayGraph(SectionEdge.class);
19 | graph.addVertexWith(lines);
20 | graph.addEdge(lines);
21 |
22 | // 다익스트라 최단 경로 찾기
23 | DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath(graph);
24 | GraphPath path = dijkstraShortestPath.getPath(source, target);
25 |
26 | return convertSubwayPath(path);
27 | }
28 |
29 | private SubwayPath convertSubwayPath(GraphPath graphPath) {
30 | List edges = (List) graphPath.getEdgeList().stream().collect(Collectors.toList());
31 | List stations = graphPath.getVertexList();
32 | return new SubwayPath(edges, stations);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/map/domain/SectionEdge.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.map.domain;
2 |
3 | import nextstep.subway.line.domain.Section;
4 | import org.jgrapht.graph.DefaultWeightedEdge;
5 |
6 | public class SectionEdge extends DefaultWeightedEdge {
7 | private Section section;
8 | private Long lineId;
9 |
10 | public SectionEdge(Section section, Long lineId) {
11 | this.section = section;
12 | this.lineId = lineId;
13 | }
14 |
15 | public Section getSection() {
16 | return section;
17 | }
18 |
19 | public Long getLineId() {
20 | return lineId;
21 | }
22 |
23 | @Override
24 | protected Object getSource() {
25 | return this.section.getUpStation();
26 | }
27 |
28 | @Override
29 | protected Object getTarget() {
30 | return this.section.getDownStation();
31 | }
32 |
33 | @Override
34 | protected double getWeight() {
35 | return this.section.getDistance();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/map/domain/SubwayGraph.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.map.domain;
2 |
3 | import nextstep.subway.line.domain.Line;
4 | import nextstep.subway.line.domain.Section;
5 | import nextstep.subway.station.domain.Station;
6 | import org.jgrapht.graph.WeightedMultigraph;
7 |
8 | import java.util.List;
9 | import java.util.stream.Collectors;
10 |
11 | public class SubwayGraph extends WeightedMultigraph {
12 | public SubwayGraph(Class edgeClass) {
13 | super(edgeClass);
14 | }
15 |
16 | public void addVertexWith(List lines) {
17 | // 지하철 역(정점)을 등록
18 | lines.stream()
19 | .flatMap(it -> it.getStations().stream())
20 | .distinct()
21 | .collect(Collectors.toList())
22 | .forEach(this::addVertex);
23 | }
24 |
25 | public void addEdge(List lines) {
26 | // 지하철 역의 연결 정보(간선)을 등록
27 | for (Line line : lines) {
28 | line.getSections().stream()
29 | .forEach(it -> addEdge(it, line));
30 | }
31 | }
32 |
33 | private void addEdge(Section section, Line line) {
34 | SectionEdge sectionEdge = new SectionEdge(section, line.getId());
35 | addEdge(section.getUpStation(), section.getDownStation(), sectionEdge);
36 | setEdgeWeight(sectionEdge, section.getDistance());
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/map/domain/SubwayPath.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.map.domain;
2 |
3 | import nextstep.subway.station.domain.Station;
4 |
5 | import java.util.List;
6 |
7 | public class SubwayPath {
8 | private List sectionEdges;
9 | private List stations;
10 |
11 | public SubwayPath(List sectionEdges, List stations) {
12 | this.sectionEdges = sectionEdges;
13 | this.stations = stations;
14 | }
15 |
16 | public List getSectionEdges() {
17 | return sectionEdges;
18 | }
19 |
20 | public List getStations() {
21 | return stations;
22 | }
23 |
24 | public int calculateDistance() {
25 | return sectionEdges.stream().mapToInt(it -> it.getSection().getDistance()).sum();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/map/dto/PathResponse.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.map.dto;
2 |
3 | import nextstep.subway.station.dto.StationResponse;
4 |
5 | import java.util.List;
6 |
7 | public class PathResponse {
8 | private List stations;
9 | private int distance;
10 |
11 | public PathResponse() {
12 | }
13 |
14 | public PathResponse(List stations, int distance) {
15 | this.stations = stations;
16 | this.distance = distance;
17 | }
18 |
19 | public List getStations() {
20 | return stations;
21 | }
22 |
23 | public int getDistance() {
24 | return distance;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/map/dto/PathResponseAssembler.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.map.dto;
2 |
3 | import nextstep.subway.map.domain.SubwayPath;
4 | import nextstep.subway.station.dto.StationResponse;
5 |
6 | import java.util.List;
7 | import java.util.stream.Collectors;
8 |
9 | public class PathResponseAssembler {
10 | public static PathResponse assemble(SubwayPath subwayPath) {
11 | List stationResponses = subwayPath.getStations().stream()
12 | .map(StationResponse::of)
13 | .collect(Collectors.toList());
14 |
15 | int distance = subwayPath.calculateDistance();
16 |
17 | return new PathResponse(stationResponses, distance);
18 | }
19 |
20 | private PathResponseAssembler() {
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/map/ui/MapController.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.map.ui;
2 |
3 | import nextstep.subway.map.application.MapService;
4 | import nextstep.subway.map.dto.PathResponse;
5 | import org.springframework.http.ResponseEntity;
6 | import org.springframework.web.bind.annotation.GetMapping;
7 | import org.springframework.web.bind.annotation.RequestParam;
8 | import org.springframework.web.bind.annotation.RestController;
9 |
10 | @RestController
11 | public class MapController {
12 | private MapService mapService;
13 |
14 | public MapController(MapService mapService) {
15 | this.mapService = mapService;
16 | }
17 |
18 | @GetMapping("/paths")
19 | public ResponseEntity findPath(@RequestParam Long source, @RequestParam Long target) {
20 | return ResponseEntity.ok(mapService.findPath(source, target));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/member/application/MemberService.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.member.application;
2 |
3 | import nextstep.subway.member.domain.Member;
4 | import nextstep.subway.member.domain.MemberRepository;
5 | import nextstep.subway.member.dto.MemberRequest;
6 | import nextstep.subway.member.dto.MemberResponse;
7 | import org.springframework.stereotype.Service;
8 | import org.springframework.transaction.annotation.Transactional;
9 |
10 | @Service
11 | @Transactional
12 | public class MemberService {
13 | private MemberRepository memberRepository;
14 |
15 | public MemberService(MemberRepository memberRepository) {
16 | this.memberRepository = memberRepository;
17 | }
18 |
19 | public MemberResponse createMember(MemberRequest request) {
20 | Member member = memberRepository.save(request.toMember());
21 | return MemberResponse.of(member);
22 | }
23 |
24 | public MemberResponse findMember(Long id) {
25 | Member member = memberRepository.findById(id).orElseThrow(RuntimeException::new);
26 | return MemberResponse.of(member);
27 | }
28 |
29 | public void updateMember(Long id, MemberRequest param) {
30 | Member member = memberRepository.findById(id).orElseThrow(RuntimeException::new);
31 | member.update(param.toMember());
32 | }
33 |
34 | public void deleteMember(Long id) {
35 | memberRepository.deleteById(id);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/member/domain/Member.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.member.domain;
2 |
3 | import nextstep.subway.common.BaseEntity;
4 | import nextstep.subway.auth.application.AuthorizationException;
5 | import org.apache.commons.lang3.StringUtils;
6 |
7 | import javax.persistence.Entity;
8 | import javax.persistence.GeneratedValue;
9 | import javax.persistence.GenerationType;
10 | import javax.persistence.Id;
11 |
12 | @Entity
13 | public class Member extends BaseEntity {
14 | @Id
15 | @GeneratedValue(strategy = GenerationType.IDENTITY)
16 | private Long id;
17 | private String email;
18 | private String password;
19 | private Integer age;
20 |
21 | public Member() {
22 | }
23 |
24 | public Member(String email, String password, Integer age) {
25 | this.email = email;
26 | this.password = password;
27 | this.age = age;
28 | }
29 |
30 | public Long getId() {
31 | return id;
32 | }
33 |
34 | public String getEmail() {
35 | return email;
36 | }
37 |
38 | public String getPassword() {
39 | return password;
40 | }
41 |
42 | public Integer getAge() {
43 | return age;
44 | }
45 |
46 | public void update(Member member) {
47 | this.email = member.email;
48 | this.password = member.password;
49 | this.age = member.age;
50 | }
51 |
52 | public void checkPassword(String password) {
53 | if (!StringUtils.equals(this.password, password)) {
54 | throw new AuthorizationException();
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/member/domain/MemberRepository.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.member.domain;
2 |
3 | import org.springframework.data.jpa.repository.JpaRepository;
4 |
5 | import java.util.Optional;
6 |
7 | public interface MemberRepository extends JpaRepository {
8 | Optional findByEmail(String email);
9 | }
10 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/member/dto/MemberRequest.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.member.dto;
2 |
3 | import nextstep.subway.member.domain.Member;
4 |
5 | public class MemberRequest {
6 | private String email;
7 | private String password;
8 | private Integer age;
9 |
10 | public MemberRequest() {
11 | }
12 |
13 | public MemberRequest(String email, String password, Integer age) {
14 | this.email = email;
15 | this.password = password;
16 | this.age = age;
17 | }
18 |
19 | public String getEmail() {
20 | return email;
21 | }
22 |
23 | public String getPassword() {
24 | return password;
25 | }
26 |
27 | public Integer getAge() {
28 | return age;
29 | }
30 |
31 | public Member toMember() {
32 | return new Member(email, password, age);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/member/dto/MemberResponse.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.member.dto;
2 |
3 | import nextstep.subway.member.domain.Member;
4 |
5 | public class MemberResponse {
6 | private Long id;
7 | private String email;
8 | private Integer age;
9 |
10 | public MemberResponse() {
11 | }
12 |
13 | public MemberResponse(Long id, String email, Integer age) {
14 | this.id = id;
15 | this.email = email;
16 | this.age = age;
17 | }
18 |
19 | public static MemberResponse of(Member member) {
20 | return new MemberResponse(member.getId(), member.getEmail(), member.getAge());
21 | }
22 |
23 | public Long getId() {
24 | return id;
25 | }
26 |
27 | public String getEmail() {
28 | return email;
29 | }
30 |
31 | public Integer getAge() {
32 | return age;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/member/ui/MemberController.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.member.ui;
2 |
3 | import nextstep.subway.auth.domain.AuthenticationPrincipal;
4 | import nextstep.subway.auth.domain.LoginMember;
5 | import nextstep.subway.member.application.MemberService;
6 | import nextstep.subway.member.dto.MemberRequest;
7 | import nextstep.subway.member.dto.MemberResponse;
8 | import org.springframework.http.ResponseEntity;
9 | import org.springframework.web.bind.annotation.*;
10 |
11 | import java.net.URI;
12 |
13 | @RestController
14 | public class MemberController {
15 | private MemberService memberService;
16 |
17 | public MemberController(MemberService memberService) {
18 | this.memberService = memberService;
19 | }
20 |
21 | @PostMapping("/members")
22 | public ResponseEntity createMember(@RequestBody MemberRequest request) {
23 | MemberResponse member = memberService.createMember(request);
24 | return ResponseEntity.created(URI.create("/members/" + member.getId())).build();
25 | }
26 |
27 | @GetMapping("/members/{id}")
28 | public ResponseEntity findMember(@PathVariable Long id) {
29 | MemberResponse member = memberService.findMember(id);
30 | return ResponseEntity.ok().body(member);
31 | }
32 |
33 | @PutMapping("/members/{id}")
34 | public ResponseEntity updateMember(@PathVariable Long id, @RequestBody MemberRequest param) {
35 | memberService.updateMember(id, param);
36 | return ResponseEntity.ok().build();
37 | }
38 |
39 | @DeleteMapping("/members/{id}")
40 | public ResponseEntity deleteMember(@PathVariable Long id) {
41 | memberService.deleteMember(id);
42 | return ResponseEntity.noContent().build();
43 | }
44 |
45 | @GetMapping("/members/me")
46 | public ResponseEntity findMemberOfMine(@AuthenticationPrincipal LoginMember loginMember) {
47 | MemberResponse member = memberService.findMember(loginMember.getId());
48 | return ResponseEntity.ok().body(member);
49 | }
50 |
51 | @PutMapping("/members/me")
52 | public ResponseEntity updateMemberOfMine(@AuthenticationPrincipal LoginMember loginMember, @RequestBody MemberRequest param) {
53 | memberService.updateMember(loginMember.getId(), param);
54 | return ResponseEntity.ok().build();
55 | }
56 |
57 | @DeleteMapping("/members/me")
58 | public ResponseEntity deleteMemberOfMine(@AuthenticationPrincipal LoginMember loginMember) {
59 | memberService.deleteMember(loginMember.getId());
60 | return ResponseEntity.noContent().build();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/station/application/StationService.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.station.application;
2 |
3 | import nextstep.subway.station.domain.Station;
4 | import nextstep.subway.station.domain.StationRepository;
5 | import nextstep.subway.station.dto.StationRequest;
6 | import nextstep.subway.station.dto.StationResponse;
7 | import org.springframework.stereotype.Service;
8 | import org.springframework.transaction.annotation.Transactional;
9 |
10 | import java.util.List;
11 | import java.util.stream.Collectors;
12 |
13 | @Service
14 | @Transactional
15 | public class StationService {
16 | private StationRepository stationRepository;
17 |
18 | public StationService(StationRepository stationRepository) {
19 | this.stationRepository = stationRepository;
20 | }
21 |
22 | public StationResponse saveStation(StationRequest stationRequest) {
23 | Station persistStation = stationRepository.save(stationRequest.toStation());
24 | return StationResponse.of(persistStation);
25 | }
26 |
27 | @Transactional(readOnly = true)
28 | public List findAllStations() {
29 | List stations = stationRepository.findAll();
30 |
31 | return stations.stream()
32 | .map(StationResponse::of)
33 | .collect(Collectors.toList());
34 | }
35 |
36 | public void deleteStationById(Long id) {
37 | stationRepository.deleteById(id);
38 | }
39 |
40 | public Station findStationById(Long id) {
41 | return stationRepository.findById(id).orElseThrow(RuntimeException::new);
42 | }
43 |
44 | public Station findById(Long id) {
45 | return stationRepository.findById(id).orElseThrow(RuntimeException::new);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/station/domain/Station.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.station.domain;
2 |
3 | import nextstep.subway.common.BaseEntity;
4 |
5 | import javax.persistence.*;
6 | import java.io.Serializable;
7 | import java.util.Objects;
8 |
9 | @Entity
10 | public class Station extends BaseEntity implements Serializable {
11 | @Id
12 | @GeneratedValue(strategy = GenerationType.IDENTITY)
13 | private Long id;
14 | @Column(unique = true)
15 | private String name;
16 |
17 | public Station() {
18 | }
19 |
20 | public Station(String name) {
21 | this.name = name;
22 | }
23 |
24 | public Long getId() {
25 | return id;
26 | }
27 |
28 | public String getName() {
29 | return name;
30 | }
31 |
32 | @Override
33 | public boolean equals(Object o) {
34 | if (this == o) return true;
35 | if (o == null || getClass() != o.getClass()) return false;
36 | Station station = (Station) o;
37 | return Objects.equals(id, station.id) &&
38 | Objects.equals(name, station.name);
39 | }
40 |
41 | @Override
42 | public int hashCode() {
43 | return Objects.hash(id, name);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/station/domain/StationRepository.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.station.domain;
2 |
3 | import org.springframework.data.jpa.repository.JpaRepository;
4 |
5 | import java.util.List;
6 |
7 | public interface StationRepository extends JpaRepository {
8 | @Override
9 | List findAll();
10 | }
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/station/dto/StationRequest.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.station.dto;
2 |
3 | import nextstep.subway.station.domain.Station;
4 |
5 | public class StationRequest {
6 | private String name;
7 |
8 | public String getName() {
9 | return name;
10 | }
11 |
12 | public Station toStation() {
13 | return new Station(name);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/station/dto/StationResponse.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.station.dto;
2 |
3 | import nextstep.subway.station.domain.Station;
4 |
5 | import java.time.LocalDateTime;
6 |
7 | public class StationResponse {
8 | private Long id;
9 | private String name;
10 | private LocalDateTime createdDate;
11 | private LocalDateTime modifiedDate;
12 |
13 | public static StationResponse of(Station station) {
14 | return new StationResponse(station.getId(), station.getName(), station.getCreatedDate(), station.getModifiedDate());
15 | }
16 |
17 | public StationResponse() {
18 | }
19 |
20 | public StationResponse(Long id, String name, LocalDateTime createdDate, LocalDateTime modifiedDate) {
21 | this.id = id;
22 | this.name = name;
23 | this.createdDate = createdDate;
24 | this.modifiedDate = modifiedDate;
25 | }
26 |
27 | public Long getId() {
28 | return id;
29 | }
30 |
31 | public String getName() {
32 | return name;
33 | }
34 |
35 | public LocalDateTime getCreatedDate() {
36 | return createdDate;
37 | }
38 |
39 | public LocalDateTime getModifiedDate() {
40 | return modifiedDate;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/station/ui/StationController.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.station.ui;
2 |
3 | import nextstep.subway.station.application.StationService;
4 | import nextstep.subway.station.dto.StationRequest;
5 | import nextstep.subway.station.dto.StationResponse;
6 | import org.springframework.dao.DataIntegrityViolationException;
7 | import org.springframework.http.MediaType;
8 | import org.springframework.http.ResponseEntity;
9 | import org.springframework.web.bind.annotation.*;
10 |
11 | import java.net.URI;
12 | import java.util.List;
13 |
14 | @RestController
15 | public class StationController {
16 | private StationService stationService;
17 |
18 | public StationController(StationService stationService) {
19 | this.stationService = stationService;
20 | }
21 |
22 | @PostMapping("/stations")
23 | public ResponseEntity createStation(@RequestBody StationRequest stationRequest) {
24 | StationResponse station = stationService.saveStation(stationRequest);
25 | return ResponseEntity.created(URI.create("/stations/" + station.getId())).body(station);
26 | }
27 |
28 | @GetMapping(value = "/stations", produces = MediaType.APPLICATION_JSON_VALUE)
29 | public ResponseEntity> showStations() {
30 | return ResponseEntity.ok().body(stationService.findAllStations());
31 | }
32 |
33 | @DeleteMapping("/stations/{id}")
34 | public ResponseEntity deleteStation(@PathVariable Long id) {
35 | stationService.deleteStationById(id);
36 | return ResponseEntity.noContent().build();
37 | }
38 |
39 | @ExceptionHandler(DataIntegrityViolationException.class)
40 | public ResponseEntity handleIllegalArgsException(DataIntegrityViolationException e) {
41 | return ResponseEntity.badRequest().build();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | spring.profiles.active=local
2 |
3 | handlebars.suffix=.html
4 | handlebars.enabled=true
5 |
6 | logging.level.org.hibernate.type.descriptor.sql=trace
7 |
8 | spring.jpa.properties.hibernate.show_sql=true
9 | spring.jpa.properties.hibernate.format_sql=true
10 |
11 | security.jwt.token.secret-key= eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.ih1aovtQShabQ7l0cINw4k1fagApg3qLWiB8Kt59Lno
12 | security.jwt.token.expire-length= 3600000
13 |
--------------------------------------------------------------------------------
/src/main/resources/logback-access.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %fullRequest%n%n%fullResponse
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/main/resources/static/images/logo_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/next-step/infra-subway-deploy/22d6124f5936782075f2b09f5d3d9052fe39140d/src/main/resources/static/images/logo_small.png
--------------------------------------------------------------------------------
/src/main/resources/static/images/main_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/next-step/infra-subway-deploy/22d6124f5936782075f2b09f5d3d9052fe39140d/src/main/resources/static/images/main_logo.png
--------------------------------------------------------------------------------
/src/main/resources/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Running Map
5 |
9 |
13 |
18 |
19 |
23 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/src/test/java/nextstep/subway/AcceptanceTest.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway;
2 |
3 | import io.restassured.RestAssured;
4 | import nextstep.subway.utils.DatabaseCleanup;
5 | import org.junit.jupiter.api.BeforeEach;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.boot.test.context.SpringBootTest;
8 | import org.springframework.boot.web.server.LocalServerPort;
9 | import org.springframework.test.context.ActiveProfiles;
10 |
11 | @ActiveProfiles("test")
12 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
13 | public class AcceptanceTest {
14 | @LocalServerPort
15 | int port;
16 |
17 | @Autowired
18 | private DatabaseCleanup databaseCleanup;
19 |
20 | @BeforeEach
21 | public void setUp() {
22 | if (RestAssured.port == RestAssured.UNDEFINED_PORT) {
23 | RestAssured.port = port;
24 | databaseCleanup.afterPropertiesSet();
25 | }
26 |
27 | databaseCleanup.execute();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/test/java/nextstep/subway/auth/acceptance/AuthAcceptanceTest.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.auth.acceptance;
2 |
3 | import io.restassured.RestAssured;
4 | import io.restassured.response.ExtractableResponse;
5 | import io.restassured.response.Response;
6 | import nextstep.subway.AcceptanceTest;
7 | import nextstep.subway.auth.dto.TokenResponse;
8 | import org.junit.jupiter.api.DisplayName;
9 | import org.junit.jupiter.api.Test;
10 | import org.springframework.http.HttpStatus;
11 | import org.springframework.http.MediaType;
12 |
13 | import java.util.HashMap;
14 | import java.util.Map;
15 |
16 | import static nextstep.subway.member.MemberAcceptanceTest.회원_생성을_요청;
17 | import static nextstep.subway.member.MemberAcceptanceTest.회원_정보_조회됨;
18 |
19 | public class AuthAcceptanceTest extends AcceptanceTest {
20 | private static final String EMAIL = "email@email.com";
21 | private static final String PASSWORD = "password";
22 | private static final Integer AGE = 20;
23 |
24 | @DisplayName("Bearer Auth")
25 | @Test
26 | void myInfoWithBearerAuth() {
27 | 회원_등록되어_있음(EMAIL, PASSWORD, AGE);
28 | TokenResponse tokenResponse = 로그인_되어_있음(EMAIL, PASSWORD);
29 |
30 | ExtractableResponse response = 내_회원_정보_조회_요청(tokenResponse);
31 |
32 | 회원_정보_조회됨(response, EMAIL, AGE);
33 | }
34 |
35 | @DisplayName("Bearer Auth 로그인 실패")
36 | @Test
37 | void myInfoWithBadBearerAuth() {
38 | 회원_등록되어_있음(EMAIL, PASSWORD, AGE);
39 |
40 | Map params = new HashMap<>();
41 | params.put("email", EMAIL + "OTHER");
42 | params.put("password", PASSWORD);
43 |
44 | RestAssured.given().log().all().
45 | contentType(MediaType.APPLICATION_JSON_VALUE).
46 | body(params).
47 | when().
48 | post("/login/token").
49 | then().
50 | log().all().
51 | statusCode(HttpStatus.UNAUTHORIZED.value());
52 | }
53 |
54 | @DisplayName("Bearer Auth 유효하지 않은 토큰")
55 | @Test
56 | void myInfoWithWrongBearerAuth() {
57 | TokenResponse tokenResponse = new TokenResponse("accesstoken");
58 |
59 | RestAssured.given().log().all().
60 | auth().oauth2(tokenResponse.getAccessToken()).
61 | accept(MediaType.APPLICATION_JSON_VALUE).
62 | when().
63 | get("/members/me").
64 | then().
65 | log().all().
66 | statusCode(HttpStatus.UNAUTHORIZED.value());
67 | }
68 |
69 | public static ExtractableResponse 회원_등록되어_있음(String email, String password, Integer age) {
70 | return 회원_생성을_요청(email, password, age);
71 | }
72 |
73 | public static TokenResponse 로그인_되어_있음(String email, String password) {
74 | ExtractableResponse response = 로그인_요청(email, password);
75 | return response.as(TokenResponse.class);
76 | }
77 |
78 | public static ExtractableResponse 로그인_요청(String email, String password) {
79 | Map params = new HashMap<>();
80 | params.put("email", email);
81 | params.put("password", password);
82 |
83 | return RestAssured.given().log().all().
84 | contentType(MediaType.APPLICATION_JSON_VALUE).
85 | body(params).
86 | when().
87 | post("/login/token").
88 | then().
89 | log().all().
90 | statusCode(HttpStatus.OK.value()).
91 | extract();
92 | }
93 |
94 | public static ExtractableResponse 내_회원_정보_조회_요청(TokenResponse tokenResponse) {
95 | return RestAssured.given().log().all().
96 | auth().oauth2(tokenResponse.getAccessToken()).
97 | accept(MediaType.APPLICATION_JSON_VALUE).
98 | when().
99 | get("/members/me").
100 | then().
101 | log().all().
102 | statusCode(HttpStatus.OK.value()).
103 | extract();
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/test/java/nextstep/subway/auth/application/AuthServiceTest.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.auth.application;
2 |
3 | import nextstep.subway.auth.dto.TokenRequest;
4 | import nextstep.subway.auth.dto.TokenResponse;
5 | import nextstep.subway.auth.infrastructure.JwtTokenProvider;
6 | import nextstep.subway.member.domain.Member;
7 | import nextstep.subway.member.domain.MemberRepository;
8 | import org.junit.jupiter.api.BeforeEach;
9 | import org.junit.jupiter.api.Test;
10 | import org.junit.jupiter.api.extension.ExtendWith;
11 | import org.mockito.Mock;
12 | import org.mockito.junit.jupiter.MockitoExtension;
13 |
14 | import java.util.Optional;
15 |
16 | import static org.assertj.core.api.Assertions.assertThat;
17 | import static org.mockito.ArgumentMatchers.anyString;
18 | import static org.mockito.Mockito.when;
19 |
20 | @ExtendWith(MockitoExtension.class)
21 | public class AuthServiceTest {
22 | public static final String EMAIL = "email@email.com";
23 | public static final String PASSWORD = "password";
24 | public static final int AGE = 10;
25 |
26 | private AuthService authService;
27 |
28 | @Mock
29 | private MemberRepository memberRepository;
30 | @Mock
31 | private JwtTokenProvider jwtTokenProvider;
32 |
33 | @BeforeEach
34 | void setUp() {
35 | authService = new AuthService(memberRepository, jwtTokenProvider);
36 | }
37 |
38 | @Test
39 | void login() {
40 | when(memberRepository.findByEmail(anyString())).thenReturn(Optional.of(new Member(EMAIL, PASSWORD, AGE)));
41 | when(jwtTokenProvider.createToken(anyString())).thenReturn("TOKEN");
42 |
43 | TokenResponse token = authService.login(new TokenRequest(EMAIL, PASSWORD));
44 |
45 | assertThat(token.getAccessToken()).isNotBlank();
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/test/java/nextstep/subway/favorite/FavoriteAcceptanceTest.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.favorite;
2 |
3 | import io.restassured.RestAssured;
4 | import io.restassured.response.ExtractableResponse;
5 | import io.restassured.response.Response;
6 | import nextstep.subway.AcceptanceTest;
7 | import nextstep.subway.auth.dto.TokenResponse;
8 | import nextstep.subway.line.acceptance.LineAcceptanceTest;
9 | import nextstep.subway.line.dto.LineResponse;
10 | import nextstep.subway.station.StationAcceptanceTest;
11 | import nextstep.subway.station.dto.StationResponse;
12 | import org.junit.jupiter.api.BeforeEach;
13 | import org.junit.jupiter.api.DisplayName;
14 | import org.junit.jupiter.api.Test;
15 | import org.springframework.http.HttpStatus;
16 | import org.springframework.http.MediaType;
17 |
18 | import java.util.HashMap;
19 | import java.util.Map;
20 |
21 | import static nextstep.subway.auth.acceptance.AuthAcceptanceTest.로그인_되어_있음;
22 | import static nextstep.subway.auth.acceptance.AuthAcceptanceTest.회원_등록되어_있음;
23 | import static nextstep.subway.line.acceptance.LineSectionAcceptanceTest.지하철_노선에_지하철역_등록_요청;
24 | import static org.assertj.core.api.Assertions.assertThat;
25 |
26 | @DisplayName("즐겨찾기 관련 기능")
27 | public class FavoriteAcceptanceTest extends AcceptanceTest {
28 | public static final String EMAIL = "email@email.com";
29 | public static final String PASSWORD = "password";
30 |
31 | private LineResponse 신분당선;
32 | private StationResponse 강남역;
33 | private StationResponse 양재역;
34 | private StationResponse 정자역;
35 | private StationResponse 광교역;
36 |
37 | private TokenResponse 사용자;
38 |
39 | @BeforeEach
40 | public void setUp() {
41 | super.setUp();
42 |
43 | 강남역 = StationAcceptanceTest.지하철역_등록되어_있음("강남역").as(StationResponse.class);
44 | 양재역 = StationAcceptanceTest.지하철역_등록되어_있음("양재역").as(StationResponse.class);
45 | 정자역 = StationAcceptanceTest.지하철역_등록되어_있음("정자역").as(StationResponse.class);
46 | 광교역 = StationAcceptanceTest.지하철역_등록되어_있음("광교역").as(StationResponse.class);
47 |
48 | Map lineCreateParams;
49 | lineCreateParams = new HashMap<>();
50 | lineCreateParams.put("name", "신분당선");
51 | lineCreateParams.put("color", "bg-red-600");
52 | lineCreateParams.put("upStationId", 강남역.getId() + "");
53 | lineCreateParams.put("downStationId", 광교역.getId() + "");
54 | lineCreateParams.put("distance", 10 + "");
55 | 신분당선 = LineAcceptanceTest.지하철_노선_등록되어_있음(lineCreateParams).as(LineResponse.class);
56 |
57 | 지하철_노선에_지하철역_등록_요청(신분당선, 강남역, 양재역, 3);
58 | 지하철_노선에_지하철역_등록_요청(신분당선, 양재역, 정자역, 3);
59 |
60 | 회원_등록되어_있음(EMAIL, PASSWORD, 20);
61 | 사용자 = 로그인_되어_있음(EMAIL, PASSWORD);
62 | }
63 |
64 | @DisplayName("즐겨찾기를 관리한다.")
65 | @Test
66 | void manageMember() {
67 | // when
68 | ExtractableResponse createResponse = 즐겨찾기_생성을_요청(사용자, 강남역, 정자역);
69 | // then
70 | 즐겨찾기_생성됨(createResponse);
71 |
72 | // when
73 | ExtractableResponse findResponse = 즐겨찾기_목록_조회_요청(사용자);
74 | // then
75 | 즐겨찾기_목록_조회됨(findResponse);
76 |
77 | // when
78 | ExtractableResponse deleteResponse = 즐겨찾기_삭제_요청(사용자, createResponse);
79 | // then
80 | 즐겨찾기_삭제됨(deleteResponse);
81 | }
82 |
83 | public static ExtractableResponse 즐겨찾기_생성을_요청(TokenResponse tokenResponse, StationResponse source, StationResponse target) {
84 | Map params = new HashMap<>();
85 | params.put("source", source.getId() + "");
86 | params.put("target", target.getId() + "");
87 |
88 | return RestAssured.given().log().all().
89 | auth().oauth2(tokenResponse.getAccessToken()).
90 | contentType(MediaType.APPLICATION_JSON_VALUE).
91 | body(params).
92 | when().
93 | post("/favorites").
94 | then().
95 | log().all().
96 | extract();
97 | }
98 |
99 | public static ExtractableResponse 즐겨찾기_목록_조회_요청(TokenResponse tokenResponse) {
100 | return RestAssured.given().log().all().
101 | auth().oauth2(tokenResponse.getAccessToken()).
102 | accept(MediaType.APPLICATION_JSON_VALUE).
103 | when().
104 | get("/favorites").
105 | then().
106 | log().all().
107 | extract();
108 | }
109 |
110 | public static ExtractableResponse 즐겨찾기_삭제_요청(TokenResponse tokenResponse, ExtractableResponse response) {
111 | String uri = response.header("Location");
112 |
113 | return RestAssured.given().log().all().
114 | auth().oauth2(tokenResponse.getAccessToken()).
115 | when().
116 | delete(uri).
117 | then().
118 | log().all().
119 | extract();
120 | }
121 |
122 | public static void 즐겨찾기_생성됨(ExtractableResponse response) {
123 | assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value());
124 | }
125 |
126 | public static void 즐겨찾기_목록_조회됨(ExtractableResponse response) {
127 | assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
128 | }
129 |
130 | public static void 즐겨찾기_삭제됨(ExtractableResponse response) {
131 | assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value());
132 | }
133 | }
--------------------------------------------------------------------------------
/src/test/java/nextstep/subway/member/MemberAcceptanceTest.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.member;
2 |
3 | import io.restassured.RestAssured;
4 | import io.restassured.response.ExtractableResponse;
5 | import io.restassured.response.Response;
6 | import nextstep.subway.AcceptanceTest;
7 | import nextstep.subway.member.dto.MemberResponse;
8 | import org.junit.jupiter.api.DisplayName;
9 | import org.junit.jupiter.api.Test;
10 | import org.springframework.http.HttpStatus;
11 | import org.springframework.http.MediaType;
12 |
13 | import java.util.HashMap;
14 | import java.util.Map;
15 |
16 | import static org.assertj.core.api.Assertions.assertThat;
17 |
18 | public class MemberAcceptanceTest extends AcceptanceTest {
19 | public static final String EMAIL = "email@email.com";
20 | public static final String PASSWORD = "password";
21 | public static final int AGE = 20;
22 |
23 | @DisplayName("회원 정보를 관리한다.")
24 | @Test
25 | void manageMember() {
26 | // when
27 | ExtractableResponse createResponse = 회원_생성을_요청(EMAIL, PASSWORD, AGE);
28 | // then
29 | 회원_생성됨(createResponse);
30 |
31 | // when
32 | ExtractableResponse findResponse = 회원_정보_조회_요청(createResponse);
33 | // then
34 | 회원_정보_조회됨(findResponse, EMAIL, AGE);
35 |
36 | // when
37 | ExtractableResponse updateResponse = 회원_정보_수정_요청(createResponse, "new" + EMAIL, "new" + PASSWORD, AGE + 2);
38 | // then
39 | 회원_정보_수정됨(updateResponse);
40 |
41 | // when
42 | ExtractableResponse deleteResponse = 회원_삭제_요청(createResponse);
43 | // then
44 | 회원_삭제됨(deleteResponse);
45 | }
46 |
47 | public static ExtractableResponse 회원_생성을_요청(String email, String password, Integer age) {
48 | Map params = new HashMap<>();
49 | params.put("email", email);
50 | params.put("password", password);
51 | params.put("age", age + "");
52 |
53 | return RestAssured.given().log().all().
54 | contentType(MediaType.APPLICATION_JSON_VALUE).
55 | body(params).
56 | when().
57 | post("/members").
58 | then().
59 | log().all().
60 | extract();
61 | }
62 |
63 | public static ExtractableResponse 회원_정보_조회_요청(ExtractableResponse response) {
64 | String uri = response.header("Location");
65 |
66 | return RestAssured.given().log().all().
67 | accept(MediaType.APPLICATION_JSON_VALUE).
68 | when().
69 | get(uri).
70 | then().
71 | log().all().
72 | extract();
73 | }
74 |
75 | public static ExtractableResponse 회원_정보_수정_요청(ExtractableResponse response, String email, String password, Integer age) {
76 | String uri = response.header("Location");
77 |
78 | Map params = new HashMap<>();
79 | params.put("email", email);
80 | params.put("password", password);
81 | params.put("age", age + "");
82 |
83 | return RestAssured.given().log().all().
84 | contentType(MediaType.APPLICATION_JSON_VALUE).
85 | body(params).
86 | when().
87 | put(uri).
88 | then().
89 | log().all().
90 | extract();
91 | }
92 |
93 | public static ExtractableResponse 회원_삭제_요청(ExtractableResponse response) {
94 | String uri = response.header("Location");
95 | return RestAssured.given().log().all().
96 | when().
97 | delete(uri).
98 | then().
99 | log().all().
100 | extract();
101 | }
102 |
103 | public static void 회원_생성됨(ExtractableResponse response) {
104 | assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value());
105 | }
106 |
107 | public static void 회원_정보_조회됨(ExtractableResponse response, String email, int age) {
108 | MemberResponse memberResponse = response.as(MemberResponse.class);
109 | assertThat(memberResponse.getId()).isNotNull();
110 | assertThat(memberResponse.getEmail()).isEqualTo(email);
111 | assertThat(memberResponse.getAge()).isEqualTo(age);
112 | }
113 |
114 | public static void 회원_정보_수정됨(ExtractableResponse response) {
115 | assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
116 | }
117 |
118 | public static void 회원_삭제됨(ExtractableResponse response) {
119 | assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value());
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/test/java/nextstep/subway/path/PathAcceptanceTest.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.path;
2 |
3 | import com.google.common.collect.Lists;
4 | import io.restassured.RestAssured;
5 | import io.restassured.response.ExtractableResponse;
6 | import io.restassured.response.Response;
7 | import nextstep.subway.AcceptanceTest;
8 | import nextstep.subway.line.acceptance.LineAcceptanceTest;
9 | import nextstep.subway.line.acceptance.LineSectionAcceptanceTest;
10 | import nextstep.subway.line.dto.LineResponse;
11 | import nextstep.subway.map.dto.PathResponse;
12 | import nextstep.subway.station.StationAcceptanceTest;
13 | import nextstep.subway.station.dto.StationResponse;
14 | import org.junit.jupiter.api.BeforeEach;
15 | import org.junit.jupiter.api.DisplayName;
16 | import org.junit.jupiter.api.Test;
17 | import org.springframework.http.MediaType;
18 |
19 | import java.util.ArrayList;
20 | import java.util.HashMap;
21 | import java.util.List;
22 | import java.util.Map;
23 | import java.util.stream.Collectors;
24 |
25 | import static org.assertj.core.api.Assertions.assertThat;
26 |
27 |
28 | @DisplayName("지하철 경로 조회")
29 | public class PathAcceptanceTest extends AcceptanceTest {
30 | private LineResponse 신분당선;
31 | private LineResponse 이호선;
32 | private LineResponse 삼호선;
33 | private StationResponse 강남역;
34 | private StationResponse 양재역;
35 | private StationResponse 교대역;
36 | private StationResponse 남부터미널역;
37 |
38 | /**
39 | * 교대역 --- *2호선* --- 강남역
40 | * | |
41 | * *3호선* *신분당선*
42 | * | |
43 | * 남부터미널역 --- *3호선* --- 양재
44 | */
45 | @BeforeEach
46 | public void setUp() {
47 | super.setUp();
48 |
49 | 강남역 = StationAcceptanceTest.지하철역_등록되어_있음("강남역").as(StationResponse.class);
50 | 양재역 = StationAcceptanceTest.지하철역_등록되어_있음("양재역").as(StationResponse.class);
51 | 교대역 = StationAcceptanceTest.지하철역_등록되어_있음("교대역").as(StationResponse.class);
52 | 남부터미널역 = StationAcceptanceTest.지하철역_등록되어_있음("남부터미널역").as(StationResponse.class);
53 |
54 | 신분당선 = 지하철_노선_등록되어_있음("신분당선", "bg-red-600", 강남역, 양재역, 10);
55 | 이호선 = 지하철_노선_등록되어_있음("이호선", "bg-red-600", 교대역, 강남역, 10);
56 | 삼호선 = 지하철_노선_등록되어_있음("삼호선", "bg-red-600", 교대역, 양재역, 5);
57 |
58 | 지하철_노선에_지하철역_등록되어_있음(삼호선, 교대역, 남부터미널역, 3);
59 | }
60 |
61 | @DisplayName("두 역의 최단 거리 경로를 조회한다.")
62 | @Test
63 | void findPathByDistance() {
64 | //when
65 | ExtractableResponse response = 거리_경로_조회_요청(3L, 2L);
66 |
67 | //then
68 | 적절한_경로를_응답(response, Lists.newArrayList(교대역, 남부터미널역, 양재역));
69 | 총_거리와_소요_시간을_함께_응답함(response, 5);
70 | }
71 |
72 | private LineResponse 지하철_노선_등록되어_있음(String name, String color, StationResponse upStation, StationResponse downStation, int distance) {
73 | Map lineCreateParams;
74 | lineCreateParams = new HashMap<>();
75 | lineCreateParams.put("name", name);
76 | lineCreateParams.put("color", color);
77 | lineCreateParams.put("upStationId", upStation.getId() + "");
78 | lineCreateParams.put("downStationId", downStation.getId() + "");
79 | lineCreateParams.put("distance", distance + "");
80 | return LineAcceptanceTest.지하철_노선_등록되어_있음(lineCreateParams).as(LineResponse.class);
81 | }
82 |
83 | private void 지하철_노선에_지하철역_등록되어_있음(LineResponse line, StationResponse upStation, StationResponse downStation, int distance) {
84 | LineSectionAcceptanceTest.지하철_노선에_지하철역_등록_요청(line, upStation, downStation, distance);
85 | }
86 |
87 | public static ExtractableResponse 거리_경로_조회_요청(long source, long target) {
88 | return RestAssured.given().log().all().
89 | accept(MediaType.APPLICATION_JSON_VALUE).
90 | when().
91 | get("/paths?source={sourceId}&target={targetId}", source, target).
92 | then().
93 | log().all().
94 | extract();
95 | }
96 |
97 | public static void 적절한_경로를_응답(ExtractableResponse response, ArrayList expectedPath) {
98 | PathResponse pathResponse = response.as(PathResponse.class);
99 |
100 | List stationIds = pathResponse.getStations().stream()
101 | .map(StationResponse::getId)
102 | .collect(Collectors.toList());
103 |
104 | List expectedPathIds = expectedPath.stream()
105 | .map(StationResponse::getId)
106 | .collect(Collectors.toList());
107 |
108 | assertThat(stationIds).containsExactlyElementsOf(expectedPathIds);
109 | }
110 |
111 | public static void 총_거리와_소요_시간을_함께_응답함(ExtractableResponse response, int totalDistance) {
112 | PathResponse pathResponse = response.as(PathResponse.class);
113 | assertThat(pathResponse.getDistance()).isEqualTo(totalDistance);
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/test/java/nextstep/subway/station/StationAcceptanceTest.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.station;
2 |
3 | import io.restassured.RestAssured;
4 | import io.restassured.response.ExtractableResponse;
5 | import io.restassured.response.Response;
6 | import nextstep.subway.AcceptanceTest;
7 | import nextstep.subway.station.dto.StationResponse;
8 | import org.junit.jupiter.api.DisplayName;
9 | import org.junit.jupiter.api.Test;
10 | import org.springframework.http.HttpStatus;
11 | import org.springframework.http.MediaType;
12 |
13 | import java.util.Arrays;
14 | import java.util.HashMap;
15 | import java.util.List;
16 | import java.util.Map;
17 | import java.util.stream.Collectors;
18 |
19 | import static org.assertj.core.api.Assertions.assertThat;
20 |
21 | @DisplayName("지하철역 관련 기능")
22 | public class StationAcceptanceTest extends AcceptanceTest {
23 | private static final String 강남역 = "강남역";
24 | private static final String 역삼역 = "역삼역";
25 |
26 | @DisplayName("지하철역을 생성한다.")
27 | @Test
28 | void createStation() {
29 | // when
30 | ExtractableResponse response = 지하철역_생성_요청(강남역);
31 |
32 | // then
33 | 지하철역_생성됨(response);
34 | }
35 |
36 | @DisplayName("기존에 존재하는 지하철역 이름으로 지하철역을 생성한다.")
37 | @Test
38 | void createStationWithDuplicateName() {
39 | //given
40 | 지하철역_등록되어_있음(강남역);
41 |
42 | // when
43 | ExtractableResponse response = 지하철역_생성_요청(강남역);
44 |
45 | // then
46 | 지하철역_생성_실패됨(response);
47 | }
48 |
49 | @DisplayName("지하철역을 조회한다.")
50 | @Test
51 | void getStations() {
52 | // given
53 | ExtractableResponse createResponse1 = 지하철역_등록되어_있음(강남역);
54 | ExtractableResponse createResponse2 = 지하철역_등록되어_있음(역삼역);
55 |
56 | // when
57 | ExtractableResponse response = 지하철역_목록_조회_요청();
58 |
59 | // then
60 | 지하철역_목록_응답됨(response);
61 | 지하철역_목록_포함됨(response, Arrays.asList(createResponse1, createResponse2));
62 | }
63 |
64 | @DisplayName("지하철역을 제거한다.")
65 | @Test
66 | void deleteStation() {
67 | // given
68 | ExtractableResponse createResponse = 지하철역_등록되어_있음(강남역);
69 |
70 | // when
71 | ExtractableResponse response = 지하철역_제거_요청(createResponse);
72 |
73 | // then
74 | 지하철역_삭제됨(response);
75 | }
76 |
77 | public static ExtractableResponse 지하철역_등록되어_있음(String name) {
78 | return 지하철역_생성_요청(name);
79 | }
80 |
81 | public static ExtractableResponse 지하철역_생성_요청(String name) {
82 | Map params = new HashMap<>();
83 | params.put("name", name);
84 |
85 | return RestAssured.given().log().all().
86 | body(params).
87 | contentType(MediaType.APPLICATION_JSON_VALUE).
88 | when().
89 | post("/stations").
90 | then().
91 | log().all().
92 | extract();
93 | }
94 |
95 | public static ExtractableResponse 지하철역_목록_조회_요청() {
96 | return RestAssured.given().log().all().
97 | when().
98 | get("/stations").
99 | then().
100 | log().all().
101 | extract();
102 | }
103 |
104 | public static ExtractableResponse 지하철역_제거_요청(ExtractableResponse response) {
105 | String uri = response.header("Location");
106 |
107 | return RestAssured.given().log().all().
108 | when().
109 | delete(uri).
110 | then().
111 | log().all().
112 | extract();
113 | }
114 |
115 | public static void 지하철역_생성됨(ExtractableResponse response) {
116 | assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value());
117 | assertThat(response.header("Location")).isNotBlank();
118 | }
119 |
120 | public static void 지하철역_생성_실패됨(ExtractableResponse response) {
121 | assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value());
122 | }
123 |
124 | public static void 지하철역_목록_응답됨(ExtractableResponse response) {
125 | assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
126 | }
127 |
128 | public static void 지하철역_삭제됨(ExtractableResponse response) {
129 | assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value());
130 | }
131 |
132 | public static void 지하철역_목록_포함됨(ExtractableResponse response, List> createdResponses) {
133 | List expectedLineIds = createdResponses.stream()
134 | .map(it -> Long.parseLong(it.header("Location").split("/")[2]))
135 | .collect(Collectors.toList());
136 |
137 | List resultLineIds = response.jsonPath().getList(".", StationResponse.class).stream()
138 | .map(StationResponse::getId)
139 | .collect(Collectors.toList());
140 |
141 | assertThat(resultLineIds).containsAll(expectedLineIds);
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/test/java/nextstep/subway/utils/DatabaseCleanup.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.utils;
2 |
3 | import com.google.common.base.CaseFormat;
4 | import org.springframework.beans.factory.InitializingBean;
5 | import org.springframework.stereotype.Service;
6 | import org.springframework.test.context.ActiveProfiles;
7 | import org.springframework.transaction.annotation.Transactional;
8 |
9 | import javax.persistence.Entity;
10 | import javax.persistence.EntityManager;
11 | import javax.persistence.PersistenceContext;
12 | import java.util.List;
13 | import java.util.stream.Collectors;
14 |
15 | @Service
16 | @ActiveProfiles("test")
17 | public class DatabaseCleanup implements InitializingBean {
18 | @PersistenceContext
19 | private EntityManager entityManager;
20 |
21 | private List tableNames;
22 |
23 | @Override
24 | public void afterPropertiesSet() {
25 | tableNames = entityManager.getMetamodel().getEntities().stream()
26 | .filter(e -> e.getJavaType().getAnnotation(Entity.class) != null)
27 | .map(e -> CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, e.getName()))
28 | .collect(Collectors.toList());
29 | }
30 |
31 | @Transactional
32 | public void execute() {
33 | entityManager.flush();
34 | entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY FALSE").executeUpdate();
35 |
36 | for (String tableName : tableNames) {
37 | entityManager.createNativeQuery("TRUNCATE TABLE " + tableName).executeUpdate();
38 | entityManager.createNativeQuery("ALTER TABLE " + tableName + " ALTER COLUMN ID RESTART WITH 1").executeUpdate();
39 | }
40 |
41 | entityManager.createNativeQuery("SET REFERENTIAL_INTEGRITY TRUE").executeUpdate();
42 | }
43 | }
--------------------------------------------------------------------------------
/src/test/java/study/jgraph/JgraphTest.java:
--------------------------------------------------------------------------------
1 | package study.jgraph;
2 |
3 | import org.jgrapht.GraphPath;
4 | import org.jgrapht.alg.shortestpath.DijkstraShortestPath;
5 | import org.jgrapht.alg.shortestpath.KShortestPaths;
6 | import org.jgrapht.graph.DefaultWeightedEdge;
7 | import org.jgrapht.graph.WeightedMultigraph;
8 | import org.junit.jupiter.api.Test;
9 |
10 | import java.util.List;
11 |
12 | import static org.assertj.core.api.Assertions.assertThat;
13 |
14 | public class JgraphTest {
15 | @Test
16 | public void getDijkstraShortestPath() {
17 | String source = "v3";
18 | String target = "v1";
19 | WeightedMultigraph graph = new WeightedMultigraph(DefaultWeightedEdge.class);
20 | graph.addVertex("v1");
21 | graph.addVertex("v2");
22 | graph.addVertex("v3");
23 | graph.setEdgeWeight(graph.addEdge("v1", "v2"), 2);
24 | graph.setEdgeWeight(graph.addEdge("v2", "v3"), 2);
25 | graph.setEdgeWeight(graph.addEdge("v1", "v3"), 100);
26 |
27 | DijkstraShortestPath dijkstraShortestPath = new DijkstraShortestPath(graph);
28 | List shortestPath = dijkstraShortestPath.getPath(source, target).getVertexList();
29 |
30 | assertThat(shortestPath.size()).isEqualTo(3);
31 | }
32 |
33 | @Test
34 | public void getKShortestPaths() {
35 | String source = "v3";
36 | String target = "v1";
37 |
38 | WeightedMultigraph graph = new WeightedMultigraph(DefaultWeightedEdge.class);
39 | graph.addVertex("v1");
40 | graph.addVertex("v2");
41 | graph.addVertex("v3");
42 | graph.setEdgeWeight(graph.addEdge("v1", "v2"), 2);
43 | graph.setEdgeWeight(graph.addEdge("v2", "v3"), 2);
44 | graph.setEdgeWeight(graph.addEdge("v1", "v3"), 100);
45 |
46 | List paths = new KShortestPaths(graph, 100).getPaths(source, target);
47 |
48 | assertThat(paths).hasSize(2);
49 | paths.stream()
50 | .forEach(it -> {
51 | assertThat(it.getVertexList()).startsWith(source);
52 | assertThat(it.getVertexList()).endsWith(target);
53 | });
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/test/java/study/unit/MockitoExtensionTest.java:
--------------------------------------------------------------------------------
1 | package study.unit;
2 |
3 | import com.google.common.collect.Lists;
4 | import nextstep.subway.line.application.LineService;
5 | import nextstep.subway.line.domain.Line;
6 | import nextstep.subway.line.domain.LineRepository;
7 | import nextstep.subway.line.dto.LineResponse;
8 | import nextstep.subway.station.application.StationService;
9 | import org.junit.jupiter.api.DisplayName;
10 | import org.junit.jupiter.api.Test;
11 | import org.junit.jupiter.api.extension.ExtendWith;
12 | import org.mockito.Mock;
13 | import org.mockito.junit.jupiter.MockitoExtension;
14 |
15 | import java.util.List;
16 |
17 | import static org.assertj.core.api.Assertions.assertThat;
18 | import static org.mockito.Mockito.when;
19 |
20 | @DisplayName("단위 테스트 - mockito의 MockitoExtension을 활용한 가짜 협력 객체 사용")
21 | @ExtendWith(MockitoExtension.class)
22 | public class MockitoExtensionTest {
23 | @Mock
24 | private LineRepository lineRepository;
25 | @Mock
26 | private StationService stationService;
27 |
28 | @Test
29 | void findAllLines() {
30 | // given
31 | when(lineRepository.findAll()).thenReturn(Lists.newArrayList(new Line()));
32 | LineService lineService = new LineService(lineRepository, stationService);
33 |
34 | // when
35 | List responses = lineService.findLineResponses();
36 |
37 | // then
38 | assertThat(responses).hasSize(1);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/test/java/study/unit/MockitoTest.java:
--------------------------------------------------------------------------------
1 | package study.unit;
2 |
3 | import com.google.common.collect.Lists;
4 | import nextstep.subway.line.application.LineService;
5 | import nextstep.subway.line.domain.Line;
6 | import nextstep.subway.line.domain.LineRepository;
7 | import nextstep.subway.line.dto.LineResponse;
8 | import nextstep.subway.station.application.StationService;
9 | import org.junit.jupiter.api.DisplayName;
10 | import org.junit.jupiter.api.Test;
11 |
12 | import java.util.List;
13 |
14 | import static org.assertj.core.api.Assertions.assertThat;
15 | import static org.mockito.Mockito.mock;
16 | import static org.mockito.Mockito.when;
17 |
18 | @DisplayName("단위 테스트 - mockito를 활용한 가짜 협력 객체 사용")
19 | public class MockitoTest {
20 | @Test
21 | void findAllLines() {
22 | // given
23 | LineRepository lineRepository = mock(LineRepository.class);
24 | StationService stationService = mock(StationService.class);
25 |
26 | when(lineRepository.findAll()).thenReturn(Lists.newArrayList(new Line()));
27 | LineService lineService = new LineService(lineRepository, stationService);
28 |
29 | // when
30 | List responses = lineService.findLineResponses();
31 |
32 | // then
33 | assertThat(responses).hasSize(1);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/test/java/study/unit/SpringExtensionTest.java:
--------------------------------------------------------------------------------
1 | package study.unit;
2 |
3 | import com.google.common.collect.Lists;
4 | import nextstep.subway.line.application.LineService;
5 | import nextstep.subway.line.domain.Line;
6 | import nextstep.subway.line.domain.LineRepository;
7 | import nextstep.subway.line.dto.LineResponse;
8 | import nextstep.subway.station.application.StationService;
9 | import org.junit.jupiter.api.DisplayName;
10 | import org.junit.jupiter.api.Test;
11 | import org.junit.jupiter.api.extension.ExtendWith;
12 | import org.springframework.boot.test.mock.mockito.MockBean;
13 |
14 | import java.util.List;
15 |
16 | import static org.assertj.core.api.Assertions.assertThat;
17 | import static org.mockito.Mockito.when;
18 |
19 | @DisplayName("단위 테스트 - SpringExtension을 활용한 가짜 협력 객체 사용")
20 | @ExtendWith(org.springframework.test.context.junit.jupiter.SpringExtension.class)
21 | public class SpringExtensionTest {
22 | @MockBean
23 | private LineRepository lineRepository;
24 | @MockBean
25 | private StationService stationService;
26 |
27 | @Test
28 | void findAllLines() {
29 | // given
30 | when(lineRepository.findAll()).thenReturn(Lists.newArrayList(new Line()));
31 | LineService lineService = new LineService(lineRepository, stationService);
32 |
33 | // when
34 | List responses = lineService.findLineResponses();
35 |
36 | // then
37 | assertThat(responses).hasSize(1);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/test/java/study/unit/UnitTest.java:
--------------------------------------------------------------------------------
1 | package study.unit;
2 |
3 | import nextstep.subway.line.domain.Line;
4 | import nextstep.subway.station.domain.Station;
5 | import org.junit.jupiter.api.DisplayName;
6 | import org.junit.jupiter.api.Test;
7 |
8 | import static org.assertj.core.api.Assertions.assertThat;
9 |
10 | @DisplayName("단위 테스트")
11 | public class UnitTest {
12 | @DisplayName("단위 테스트 1")
13 | @Test
14 | void update() {
15 | // given
16 | String newName = "구분당선";
17 |
18 | Station upStation = new Station("강남역");
19 | Station downStation = new Station("광교역");
20 | Line line = new Line("신분당선", "RED", upStation, downStation, 10);
21 | Line newLine = new Line(newName, "GREEN");
22 |
23 | // when
24 | line.update(newLine);
25 |
26 | // then
27 | assertThat(line.getName()).isEqualTo(newName);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------