├── .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
│ │ ├── BaseEntity.java
│ │ ├── DataLoaderConfig.java
│ │ ├── PageController.java
│ │ ├── SubwayApplication.java
│ │ ├── auth
│ │ ├── application
│ │ │ ├── AuthService.java
│ │ │ └── AuthorizationException.java
│ │ ├── domain
│ │ │ ├── AuthenticationPrincipal.java
│ │ │ └── LoginMember.java
│ │ ├── dto
│ │ │ ├── TokenRequest.java
│ │ │ └── TokenResponse.java
│ │ ├── infrastructure
│ │ │ ├── AuthenticationPrincipalConfig.java
│ │ │ ├── AuthorizationExtractor.java
│ │ │ └── JwtTokenProvider.java
│ │ └── ui
│ │ │ ├── AuthController.java
│ │ │ └── AuthenticationPrincipalArgumentResolver.java
│ │ ├── line
│ │ ├── application
│ │ │ └── LineService.java
│ │ ├── domain
│ │ │ ├── Line.java
│ │ │ ├── LineRepository.java
│ │ │ └── Section.java
│ │ ├── dto
│ │ │ ├── LineRequest.java
│ │ │ ├── LineResponse.java
│ │ │ └── SectionRequest.java
│ │ └── ui
│ │ │ └── LineController.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
│ └── templates
│ └── index.html
└── test
└── java
├── nextstep
└── subway
│ ├── AcceptanceTest.java
│ ├── SubwayApplicationTests.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 | [ATDD 강의](https://edu.nextstep.camp/c/R89PYi5H) 실습을 위한 지하철 노선도 애플리케이션
17 |
18 |
19 |
20 | ## 🚀 Getting Started
21 |
22 | ### Install
23 | #### npm 설치
24 | ```
25 | cd frontend
26 | npm install
27 | ```
28 | > `frontend` 디렉토리에서 수행해야 합니다.
29 |
30 | ### Usage
31 | #### webpack server 구동
32 | ```
33 | npm run dev
34 | ```
35 | #### application 구동
36 | ```
37 | ./gradlew bootRun
38 | ```
39 |
40 |
41 | ## ✏️ Code Review Process
42 | [텍스트와 이미지로 살펴보는 온라인 코드 리뷰 과정](https://github.com/next-step/nextstep-docs/tree/master/codereview)
43 |
44 |
45 |
46 | ## 🐞 Bug Report
47 |
48 | 버그를 발견한다면, [Issues](https://github.com/next-step/atdd-subway-service/issues) 에 등록해주세요 :)
49 |
50 |
51 |
52 | ## 📝 License
53 |
54 | This project is [MIT](https://github.com/next-step/atdd-subway-service/blob/master/LICENSE.md) licensed.
55 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'org.springframework.boot' version '2.5.1'
3 | id 'io.spring.dependency-management' version '1.0.11.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 | }
14 |
15 | dependencies {
16 | // spring
17 | implementation 'org.springframework.boot:spring-boot-starter-web'
18 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
19 |
20 | // handlebars
21 | implementation 'pl.allegro.tech.boot:handlebars-spring-boot-starter:0.3.0'
22 |
23 | // log
24 | implementation 'net.rakugakibox.spring.boot:logback-access-spring-boot-starter:2.7.1'
25 |
26 | // jgraph
27 | implementation 'org.jgrapht:jgrapht-core:1.0.1'
28 |
29 | // jwt
30 | implementation 'io.jsonwebtoken:jjwt:0.9.1'
31 |
32 | testImplementation 'io.rest-assured:rest-assured:3.3.0'
33 | testImplementation 'org.springframework.boot:spring-boot-starter-test'
34 |
35 | runtimeOnly 'com.h2database:h2'
36 | }
37 |
38 | test {
39 | useJUnitPlatform()
40 | }
41 |
--------------------------------------------------------------------------------
/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 | Accept: 'application/json'
14 | }
15 | })
16 | },
17 | login(uri, config) {
18 | return Vue.axios.post(`${uri}`, {}, config)
19 | },
20 | post(uri, params) {
21 | return Vue.axios.post(`${uri}`, params, {
22 | headers: {
23 | Authorization: `Bearer ${localStorage.getItem('token')}` || ''
24 | }
25 | })
26 | },
27 | update(uri, params) {
28 | return Vue.axios.put(uri, params, {
29 | headers: {
30 | Authorization: `Bearer ${localStorage.getItem('token')}` || ''
31 | }
32 | })
33 | },
34 | delete(uri) {
35 | return Vue.axios.delete(uri, {
36 | headers: {
37 | Authorization: `Bearer ${localStorage.getItem('token')}` || ''
38 | }
39 | })
40 | }
41 | }
42 |
43 | ApiService.init()
44 |
45 | export default ApiService
46 |
--------------------------------------------------------------------------------
/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 | import station from "../../store/modules/station";
3 |
4 | const BASE_URL = '/lines'
5 |
6 | const LineService = {
7 | get(lineId) {
8 | return ApiService.get(`${BASE_URL}/${lineId}`)
9 | },
10 | getAll() {
11 | return ApiService.get(`${BASE_URL}`)
12 | },
13 | create(newLine) {
14 | return ApiService.post(`${BASE_URL}`, newLine)
15 | },
16 | update(editingLine) {
17 | return ApiService.update(`${BASE_URL}/${editingLine.lineId}`, editingLine.line)
18 | },
19 | delete(lineId) {
20 | return ApiService.delete(`${BASE_URL}/${lineId}`)
21 | },
22 | createSection({ lineId, section }) {
23 | return ApiService.post(`${BASE_URL}/${lineId}/sections`, section)
24 | },
25 | deleteSection({ lineId, stationId }) {
26 | return ApiService.delete(`${BASE_URL}/${lineId}/sections?stationId=${stationId}`)
27 | }
28 | }
29 |
30 | export default LineService
31 |
--------------------------------------------------------------------------------
/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('truetype');
4 | }
5 |
6 | @font-face {
7 | font-family: 'hannaair';
8 | src: url(/fonts/BMHANNAAir.otf) format('truetype');
9 | }
10 |
11 | @font-face {
12 | font-family: 'euljiro';
13 | src: url(/fonts/BMEULJIRO.otf) format('truetype');
14 | }
15 |
16 | @font-face {
17 | font-family: 'jua';
18 | src: url(/fonts/BMJUA.otf) format('truetype');
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 |
54 | {{ pathResult.distance }}km |
55 | {{ pathResult.fare }}원 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | 거리 |
67 |
68 |
69 |
70 |
71 | {{ pathResult.distance }}km |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | mdi-subway
88 |
89 | {{ station.name }}
90 |
91 | mdi-arrow-right-bold
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
160 |
--------------------------------------------------------------------------------
/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 |
64 |
65 |
66 |
178 |
179 |
184 |
--------------------------------------------------------------------------------
/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 | const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin')
3 | const TerserPlugin = require('terser-webpack-plugin')
4 |
5 | const outputPath = path.resolve(__dirname, '../src/main/resources/static')
6 |
7 | module.exports = {
8 | mode: 'production',
9 | output: {
10 | path: outputPath,
11 | filename: '[name].js'
12 | },
13 | optimization: {
14 | splitChunks: {
15 | chunks: 'all',
16 | cacheGroups: {
17 | vendors: {
18 | test: /[\\/]node_modules[\\/]/,
19 | name: 'js/vendors'
20 | }
21 | }
22 | },
23 | minimizer: [
24 | new TerserPlugin({
25 | cache: true,
26 | parallel: true,
27 | terserOptions: {
28 | warnings: false,
29 | compress: {
30 | warnings: false,
31 | unused: true
32 | },
33 | ecma: 6,
34 | mangle: true,
35 | unused: true
36 | },
37 | sourceMap: true
38 | }),
39 | new OptimizeCssAssetsPlugin()
40 | ]
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/next-step/atdd-subway-service/6b0b91c3cf22015ff7d519e3c96f7de0cc7b4692/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/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/BaseEntity.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway;
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/DataLoaderConfig.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway;
2 |
3 | import com.google.common.collect.Lists;
4 | import nextstep.subway.line.domain.Line;
5 | import nextstep.subway.line.domain.LineRepository;
6 | import nextstep.subway.member.domain.Member;
7 | import nextstep.subway.member.domain.MemberRepository;
8 | import nextstep.subway.station.domain.Station;
9 | import org.springframework.boot.CommandLineRunner;
10 | import org.springframework.context.annotation.Profile;
11 | import org.springframework.stereotype.Component;
12 |
13 | @Component
14 | @Profile("!test")
15 | public class DataLoaderConfig implements CommandLineRunner {
16 | private LineRepository lineRepository;
17 | private MemberRepository memberRepository;
18 |
19 | public DataLoaderConfig(LineRepository lineRepository, MemberRepository memberRepository) {
20 | this.lineRepository = lineRepository;
21 | this.memberRepository = memberRepository;
22 | }
23 |
24 | @Override
25 | public void run(String... args) throws Exception {
26 | Station 강남역 = new Station("강남역");
27 | Station 교대역 = new Station("교대역");
28 | Station 양재역 = new Station("양재역");
29 | Station 남부터미널역 = new Station("남부터미널역");
30 |
31 | Line 신분당선 = new Line("신분당선", "red lighten-1", 강남역, 양재역, 10);
32 | Line 이호선 = new Line("2호선", "green lighten-1", 교대역, 강남역, 10);
33 | Line 삼호선 = new Line("3호선", "orange darken-1", 교대역, 양재역, 10);
34 |
35 | lineRepository.saveAll(Lists.newArrayList(신분당선, 이호선, 삼호선));
36 |
37 | memberRepository.save(new Member("probitanima11@gmail.com", "11", 10));
38 | }
39 | }
--------------------------------------------------------------------------------
/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 | public class AuthService {
14 | private MemberRepository memberRepository;
15 | private JwtTokenProvider jwtTokenProvider;
16 |
17 | public AuthService(MemberRepository memberRepository, JwtTokenProvider jwtTokenProvider) {
18 | this.memberRepository = memberRepository;
19 | this.jwtTokenProvider = jwtTokenProvider;
20 | }
21 |
22 | public TokenResponse login(TokenRequest request) {
23 | Member member = memberRepository.findByEmail(request.getEmail()).orElseThrow(AuthorizationException::new);
24 | member.checkPassword(request.getPassword());
25 |
26 | String token = jwtTokenProvider.createToken(request.getEmail());
27 | return new TokenResponse(token);
28 | }
29 |
30 | public LoginMember findMemberByToken(String credentials) {
31 | if (!jwtTokenProvider.validateToken(credentials)) {
32 | return new LoginMember();
33 | }
34 |
35 | String email = jwtTokenProvider.getPayload(credentials);
36 | Member member = memberRepository.findByEmail(email).orElseThrow(RuntimeException::new);
37 | return new LoginMember(member.getId(), member.getEmail(), member.getAge());
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/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() {
9 | }
10 |
11 | public LoginMember(Long id, String email, Integer age) {
12 | this.id = id;
13 | this.email = email;
14 | this.age = age;
15 | }
16 |
17 | public Long getId() {
18 | return id;
19 | }
20 |
21 | public String getEmail() {
22 | return email;
23 | }
24 |
25 | public Integer getAge() {
26 | return age;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/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/AuthenticationPrincipalConfig.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.auth.infrastructure;
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/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 String BEARER_TYPE = "Bearer";
9 | public static final String ACCESS_TOKEN_TYPE = AuthorizationExtractor.class.getSimpleName() + ".ACCESS_TOKEN_TYPE";
10 |
11 | public static String extract(HttpServletRequest request) {
12 | Enumeration headers = request.getHeaders(AUTHORIZATION);
13 | while (headers.hasMoreElements()) {
14 | String value = headers.nextElement();
15 | if ((value.toLowerCase().startsWith(BEARER_TYPE.toLowerCase()))) {
16 | String authHeaderValue = value.substring(BEARER_TYPE.length()).trim();
17 | request.setAttribute(ACCESS_TOKEN_TYPE, value.substring(0, BEARER_TYPE.length()).trim());
18 | int commaIndex = authHeaderValue.indexOf(',');
19 | if (commaIndex > 0) {
20 | authHeaderValue = authHeaderValue.substring(0, commaIndex);
21 | }
22 | return authHeaderValue;
23 | }
24 | }
25 |
26 | return null;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/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/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.domain.Section;
6 | import nextstep.subway.line.dto.LineRequest;
7 | import nextstep.subway.line.dto.LineResponse;
8 | import nextstep.subway.line.dto.SectionRequest;
9 | import nextstep.subway.station.application.StationService;
10 | import nextstep.subway.station.domain.Station;
11 | import nextstep.subway.station.dto.StationResponse;
12 | import org.springframework.stereotype.Service;
13 | import org.springframework.transaction.annotation.Transactional;
14 |
15 | import java.util.ArrayList;
16 | import java.util.Arrays;
17 | import java.util.List;
18 | import java.util.Optional;
19 | import java.util.stream.Collectors;
20 |
21 | @Service
22 | @Transactional
23 | public class LineService {
24 | private LineRepository lineRepository;
25 | private StationService stationService;
26 |
27 | public LineService(LineRepository lineRepository, StationService stationService) {
28 | this.lineRepository = lineRepository;
29 | this.stationService = stationService;
30 | }
31 |
32 | public LineResponse saveLine(LineRequest request) {
33 | Station upStation = stationService.findById(request.getUpStationId());
34 | Station downStation = stationService.findById(request.getDownStationId());
35 | Line persistLine = lineRepository.save(new Line(request.getName(), request.getColor(), upStation, downStation, request.getDistance()));
36 | List stations = getStations(persistLine).stream()
37 | .map(it -> StationResponse.of(it))
38 | .collect(Collectors.toList());
39 | return LineResponse.of(persistLine, stations);
40 | }
41 |
42 | public List findLines() {
43 | List persistLines = lineRepository.findAll();
44 | return persistLines.stream()
45 | .map(line -> {
46 | List stations = getStations(line).stream()
47 | .map(it -> StationResponse.of(it))
48 | .collect(Collectors.toList());
49 | return LineResponse.of(line, stations);
50 | })
51 | .collect(Collectors.toList());
52 | }
53 |
54 | public Line findLineById(Long id) {
55 | return lineRepository.findById(id).orElseThrow(RuntimeException::new);
56 | }
57 |
58 |
59 | public LineResponse findLineResponseById(Long id) {
60 | Line persistLine = findLineById(id);
61 | List stations = getStations(persistLine).stream()
62 | .map(it -> StationResponse.of(it))
63 | .collect(Collectors.toList());
64 | return LineResponse.of(persistLine, stations);
65 | }
66 |
67 | public void updateLine(Long id, LineRequest lineUpdateRequest) {
68 | Line persistLine = lineRepository.findById(id).orElseThrow(RuntimeException::new);
69 | persistLine.update(new Line(lineUpdateRequest.getName(), lineUpdateRequest.getColor()));
70 | }
71 |
72 | public void deleteLineById(Long id) {
73 | lineRepository.deleteById(id);
74 | }
75 |
76 | public void addLineStation(Long lineId, SectionRequest request) {
77 | Line line = findLineById(lineId);
78 | Station upStation = stationService.findStationById(request.getUpStationId());
79 | Station downStation = stationService.findStationById(request.getDownStationId());
80 | List stations = getStations(line);
81 | boolean isUpStationExisted = stations.stream().anyMatch(it -> it == upStation);
82 | boolean isDownStationExisted = stations.stream().anyMatch(it -> it == downStation);
83 |
84 | if (isUpStationExisted && isDownStationExisted) {
85 | throw new RuntimeException("이미 등록된 구간 입니다.");
86 | }
87 |
88 | if (!stations.isEmpty() && stations.stream().noneMatch(it -> it == upStation) &&
89 | stations.stream().noneMatch(it -> it == downStation)) {
90 | throw new RuntimeException("등록할 수 없는 구간 입니다.");
91 | }
92 |
93 | if (stations.isEmpty()) {
94 | line.getSections().add(new Section(line, upStation, downStation, request.getDistance()));
95 | return;
96 | }
97 |
98 | if (isUpStationExisted) {
99 | line.getSections().stream()
100 | .filter(it -> it.getUpStation() == upStation)
101 | .findFirst()
102 | .ifPresent(it -> it.updateUpStation(downStation, request.getDistance()));
103 |
104 | line.getSections().add(new Section(line, upStation, downStation, request.getDistance()));
105 | } else if (isDownStationExisted) {
106 | line.getSections().stream()
107 | .filter(it -> it.getDownStation() == downStation)
108 | .findFirst()
109 | .ifPresent(it -> it.updateDownStation(upStation, request.getDistance()));
110 |
111 | line.getSections().add(new Section(line, upStation, downStation, request.getDistance()));
112 | } else {
113 | throw new RuntimeException();
114 | }
115 | }
116 |
117 | public void removeLineStation(Long lineId, Long stationId) {
118 | Line line = findLineById(lineId);
119 | Station station = stationService.findStationById(stationId);
120 | if (line.getSections().size() <= 1) {
121 | throw new RuntimeException();
122 | }
123 |
124 | Optional upLineStation = line.getSections().stream()
125 | .filter(it -> it.getUpStation() == station)
126 | .findFirst();
127 | Optional downLineStation = line.getSections().stream()
128 | .filter(it -> it.getDownStation() == station)
129 | .findFirst();
130 |
131 | if (upLineStation.isPresent() && downLineStation.isPresent()) {
132 | Station newUpStation = downLineStation.get().getUpStation();
133 | Station newDownStation = upLineStation.get().getDownStation();
134 | int newDistance = upLineStation.get().getDistance() + downLineStation.get().getDistance();
135 | line.getSections().add(new Section(line, newUpStation, newDownStation, newDistance));
136 | }
137 |
138 | upLineStation.ifPresent(it -> line.getSections().remove(it));
139 | downLineStation.ifPresent(it -> line.getSections().remove(it));
140 | }
141 |
142 |
143 | public List getStations(Line line) {
144 | if (line.getSections().isEmpty()) {
145 | return Arrays.asList();
146 | }
147 |
148 | List stations = new ArrayList<>();
149 | Station downStation = findUpStation(line);
150 | stations.add(downStation);
151 |
152 | while (downStation != null) {
153 | Station finalDownStation = downStation;
154 | Optional nextLineStation = line.getSections().stream()
155 | .filter(it -> it.getUpStation() == finalDownStation)
156 | .findFirst();
157 | if (!nextLineStation.isPresent()) {
158 | break;
159 | }
160 | downStation = nextLineStation.get().getDownStation();
161 | stations.add(downStation);
162 | }
163 |
164 | return stations;
165 | }
166 |
167 | private Station findUpStation(Line line) {
168 | Station downStation = line.getSections().get(0).getUpStation();
169 | while (downStation != null) {
170 | Station finalDownStation = downStation;
171 | Optional nextLineStation = line.getSections().stream()
172 | .filter(it -> it.getDownStation() == finalDownStation)
173 | .findFirst();
174 | if (!nextLineStation.isPresent()) {
175 | break;
176 | }
177 | downStation = nextLineStation.get().getUpStation();
178 | }
179 |
180 | return downStation;
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/line/domain/Line.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.line.domain;
2 |
3 | import nextstep.subway.BaseEntity;
4 | import nextstep.subway.station.domain.Station;
5 |
6 | import javax.persistence.*;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | @Entity
11 | public class Line extends BaseEntity {
12 | @Id
13 | @GeneratedValue(strategy = GenerationType.IDENTITY)
14 | private Long id;
15 | @Column(unique = true)
16 | private String name;
17 | private String color;
18 |
19 | @OneToMany(mappedBy = "line", cascade = {CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval = true)
20 | private List sections = new ArrayList<>();
21 |
22 | public Line() {
23 | }
24 |
25 | public Line(String name, String color) {
26 | this.name = name;
27 | this.color = color;
28 | }
29 |
30 | public Line(String name, String color, Station upStation, Station downStation, int distance) {
31 | this.name = name;
32 | this.color = color;
33 | sections.add(new Section(this, upStation, downStation, distance));
34 | }
35 |
36 | public void update(Line line) {
37 | this.name = line.getName();
38 | this.color = line.getColor();
39 | }
40 |
41 | public Long getId() {
42 | return id;
43 | }
44 |
45 | public String getName() {
46 | return name;
47 | }
48 |
49 | public String getColor() {
50 | return color;
51 | }
52 |
53 | public List getSections() {
54 | return sections;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/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 |
7 | @Entity
8 | public class Section {
9 | @Id
10 | @GeneratedValue(strategy = GenerationType.IDENTITY)
11 | private Long id;
12 |
13 | @ManyToOne(cascade = CascadeType.PERSIST)
14 | @JoinColumn(name = "line_id")
15 | private Line line;
16 |
17 | @ManyToOne(cascade = CascadeType.PERSIST)
18 | @JoinColumn(name = "up_station_id")
19 | private Station upStation;
20 |
21 | @ManyToOne(cascade = CascadeType.PERSIST)
22 | @JoinColumn(name = "down_station_id")
23 | private Station downStation;
24 |
25 | private int distance;
26 |
27 | public Section() {
28 | }
29 |
30 | public Section(Line line, Station upStation, Station downStation, int distance) {
31 | this.line = line;
32 | this.upStation = upStation;
33 | this.downStation = downStation;
34 | this.distance = distance;
35 | }
36 |
37 | public Long getId() {
38 | return id;
39 | }
40 |
41 | public Line getLine() {
42 | return line;
43 | }
44 |
45 | public Station getUpStation() {
46 | return upStation;
47 | }
48 |
49 | public Station getDownStation() {
50 | return downStation;
51 | }
52 |
53 | public int getDistance() {
54 | return distance;
55 | }
56 |
57 | public void updateUpStation(Station station, int newDistance) {
58 | if (this.distance <= newDistance) {
59 | throw new RuntimeException("역과 역 사이의 거리보다 좁은 거리를 입력해주세요");
60 | }
61 | this.upStation = station;
62 | this.distance -= newDistance;
63 | }
64 |
65 | public void updateDownStation(Station station, int newDistance) {
66 | if (this.distance <= newDistance) {
67 | throw new RuntimeException("역과 역 사이의 거리보다 좁은 거리를 입력해주세요");
68 | }
69 | this.downStation = station;
70 | this.distance -= newDistance;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/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 | public LineRequest() {
13 | }
14 |
15 | public LineRequest(String name, String color, Long upStationId, Long downStationId, int distance) {
16 | this.name = name;
17 | this.color = color;
18 | this.upStationId = upStationId;
19 | this.downStationId = downStationId;
20 | this.distance = distance;
21 | }
22 |
23 | public String getName() {
24 | return name;
25 | }
26 |
27 | public String getColor() {
28 | return color;
29 | }
30 |
31 | public Long getUpStationId() {
32 | return upStationId;
33 | }
34 |
35 | public Long getDownStationId() {
36 | return downStationId;
37 | }
38 |
39 | public int getDistance() {
40 | return distance;
41 | }
42 |
43 | public Line toLine() {
44 | return new Line(name, color);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/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.List;
8 |
9 | public class LineResponse {
10 | private Long id;
11 | private String name;
12 | private String color;
13 | private List stations;
14 | private LocalDateTime createdDate;
15 | private LocalDateTime modifiedDate;
16 |
17 | public LineResponse() {
18 | }
19 |
20 | public LineResponse(Long id, String name, String color, List stations, LocalDateTime createdDate, LocalDateTime modifiedDate) {
21 | this.id = id;
22 | this.name = name;
23 | this.color = color;
24 | this.stations = stations;
25 | this.createdDate = createdDate;
26 | this.modifiedDate = modifiedDate;
27 | }
28 |
29 | public static LineResponse of(Line line, List stations) {
30 | return new LineResponse(line.getId(), line.getName(), line.getColor(), stations, line.getCreatedDate(), line.getModifiedDate());
31 | }
32 |
33 | public Long getId() {
34 | return id;
35 | }
36 |
37 | public String getName() {
38 | return name;
39 | }
40 |
41 | public String getColor() {
42 | return color;
43 | }
44 |
45 | public List getStations() {
46 | return stations;
47 | }
48 |
49 | public LocalDateTime getCreatedDate() {
50 | return createdDate;
51 | }
52 |
53 | public LocalDateTime getModifiedDate() {
54 | return modifiedDate;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/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 | public SectionRequest() {
9 | }
10 |
11 | public SectionRequest(Long upStationId, Long downStationId, int distance) {
12 | this.upStationId = upStationId;
13 | this.downStationId = downStationId;
14 | this.distance = distance;
15 | }
16 |
17 | public Long getUpStationId() {
18 | return upStationId;
19 | }
20 |
21 | public Long getDownStationId() {
22 | return downStationId;
23 | }
24 |
25 | public int getDistance() {
26 | return distance;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/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.findLines());
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/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 | public class MemberService {
12 | private MemberRepository memberRepository;
13 |
14 | public MemberService(MemberRepository memberRepository) {
15 | this.memberRepository = memberRepository;
16 | }
17 |
18 | public MemberResponse createMember(MemberRequest request) {
19 | Member member = memberRepository.save(request.toMember());
20 | return MemberResponse.of(member);
21 | }
22 |
23 | public MemberResponse findMember(Long id) {
24 | Member member = memberRepository.findById(id).orElseThrow(RuntimeException::new);
25 | return MemberResponse.of(member);
26 | }
27 |
28 | public void updateMember(Long id, MemberRequest param) {
29 | Member member = memberRepository.findById(id).orElseThrow(RuntimeException::new);
30 | member.update(param.toMember());
31 | }
32 |
33 | public void deleteMember(Long id) {
34 | memberRepository.deleteById(id);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/member/domain/Member.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.member.domain;
2 |
3 | import nextstep.subway.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.LoginMember;
4 | import nextstep.subway.auth.domain.AuthenticationPrincipal;
5 | import nextstep.subway.auth.domain.LoginMember;
6 | import nextstep.subway.member.application.MemberService;
7 | import nextstep.subway.member.dto.MemberRequest;
8 | import nextstep.subway.member.dto.MemberResponse;
9 | import org.springframework.http.ResponseEntity;
10 | import org.springframework.web.bind.annotation.*;
11 |
12 | import java.net.URI;
13 |
14 | @RestController
15 | public class MemberController {
16 | private MemberService memberService;
17 |
18 | public MemberController(MemberService memberService) {
19 | this.memberService = memberService;
20 | }
21 |
22 | @PostMapping("/members")
23 | public ResponseEntity createMember(@RequestBody MemberRequest request) {
24 | MemberResponse member = memberService.createMember(request);
25 | return ResponseEntity.created(URI.create("/members/" + member.getId())).build();
26 | }
27 |
28 | @GetMapping("/members/{id}")
29 | public ResponseEntity findMember(@PathVariable Long id) {
30 | MemberResponse member = memberService.findMember(id);
31 | return ResponseEntity.ok().body(member);
32 | }
33 |
34 | @PutMapping("/members/{id}")
35 | public ResponseEntity updateMember(@PathVariable Long id, @RequestBody MemberRequest param) {
36 | memberService.updateMember(id, param);
37 | return ResponseEntity.ok().build();
38 | }
39 |
40 | @DeleteMapping("/members/{id}")
41 | public ResponseEntity deleteMember(@PathVariable Long id) {
42 | memberService.deleteMember(id);
43 | return ResponseEntity.noContent().build();
44 | }
45 |
46 | @GetMapping("/members/me")
47 | public ResponseEntity findMemberOfMine(LoginMember loginMember) {
48 | MemberResponse member = memberService.findMember(loginMember.getId());
49 | return ResponseEntity.ok().body(member);
50 | }
51 |
52 | @PutMapping("/members/me")
53 | public ResponseEntity updateMemberOfMine(LoginMember loginMember, @RequestBody MemberRequest param) {
54 | memberService.updateMember(loginMember.getId(), param);
55 | return ResponseEntity.ok().build();
56 | }
57 |
58 | @DeleteMapping("/members/me")
59 | public ResponseEntity deleteMemberOfMine(LoginMember loginMember) {
60 | memberService.deleteMember(loginMember.getId());
61 | return ResponseEntity.noContent().build();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/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 | public class StationService {
15 | private StationRepository stationRepository;
16 |
17 | public StationService(StationRepository stationRepository) {
18 | this.stationRepository = stationRepository;
19 | }
20 |
21 | public StationResponse saveStation(StationRequest stationRequest) {
22 | Station persistStation = stationRepository.save(stationRequest.toStation());
23 | return StationResponse.of(persistStation);
24 | }
25 |
26 | public List findAllStations() {
27 | List stations = stationRepository.findAll();
28 |
29 | return stations.stream()
30 | .map(station -> StationResponse.of(station))
31 | .collect(Collectors.toList());
32 | }
33 |
34 | public void deleteStationById(Long id) {
35 | stationRepository.deleteById(id);
36 | }
37 |
38 | public Station findStationById(Long id) {
39 | return stationRepository.findById(id).orElseThrow(RuntimeException::new);
40 | }
41 |
42 | public Station findById(Long id) {
43 | return stationRepository.findById(id).orElseThrow(RuntimeException::new);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/nextstep/subway/station/domain/Station.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.station.domain;
2 |
3 | import nextstep.subway.BaseEntity;
4 |
5 | import javax.persistence.*;
6 | import java.util.Objects;
7 |
8 | @Entity
9 | public class Station extends BaseEntity {
10 | @Id
11 | @GeneratedValue(strategy = GenerationType.IDENTITY)
12 | private Long id;
13 | @Column(unique = true)
14 | private String name;
15 |
16 | public Station() {
17 | }
18 |
19 | public Station(String name) {
20 | this.name = name;
21 | }
22 |
23 | public Long getId() {
24 | return id;
25 | }
26 |
27 | public String getName() {
28 | return name;
29 | }
30 |
31 | @Override
32 | public boolean equals(Object o) {
33 | if (this == o) return true;
34 | if (o == null || getClass() != o.getClass()) return false;
35 | Station station = (Station) o;
36 | return Objects.equals(id, station.id) &&
37 | Objects.equals(name, station.name);
38 | }
39 |
40 | @Override
41 | public int hashCode() {
42 | return Objects.hash(id, name);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/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 StationRequest() {
9 | }
10 |
11 | public StationRequest(String name) {
12 | this.name = name;
13 | }
14 |
15 | public String getName() {
16 | return name;
17 | }
18 |
19 | public Station toStation() {
20 | return new Station(name);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/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 | handlebars.suffix=.html
2 | handlebars.enabled=true
3 |
4 | spring.jpa.properties.hibernate.show_sql=true
5 | spring.jpa.properties.hibernate.format_sql=true
6 |
7 | logging.level.org.hibernate.type.descriptor.sql=trace
8 |
9 | security.jwt.token.secret-key= eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjJ9.ih1aovtQShabQ7l0cINw4k1fagApg3qLWiB8Kt59Lno
10 | security.jwt.token.expire-length= 3600000
--------------------------------------------------------------------------------
/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/atdd-subway-service/6b0b91c3cf22015ff7d519e3c96f7de0cc7b4692/src/main/resources/static/images/logo_small.png
--------------------------------------------------------------------------------
/src/main/resources/static/images/main_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/next-step/atdd-subway-service/6b0b91c3cf22015ff7d519e3c96f7de0cc7b4692/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 |
10 | @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
11 | public class AcceptanceTest {
12 | @LocalServerPort
13 | int port;
14 |
15 | @Autowired
16 | private DatabaseCleanup databaseCleanup;
17 |
18 | @BeforeEach
19 | public void setUp() {
20 | RestAssured.port = port;
21 | databaseCleanup.execute();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/test/java/nextstep/subway/SubwayApplicationTests.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.boot.test.context.SpringBootTest;
5 |
6 | @SpringBootTest
7 | class SubwayApplicationTests {
8 |
9 | @Test
10 | void contextLoads() {
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/src/test/java/nextstep/subway/auth/acceptance/AuthAcceptanceTest.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.auth.acceptance;
2 |
3 | import nextstep.subway.AcceptanceTest;
4 | import org.junit.jupiter.api.DisplayName;
5 | import org.junit.jupiter.api.Test;
6 |
7 | public class AuthAcceptanceTest extends AcceptanceTest {
8 |
9 | @DisplayName("Bearer Auth")
10 | @Test
11 | void myInfoWithBearerAuth() {
12 | }
13 |
14 | @DisplayName("Bearer Auth 로그인 실패")
15 | @Test
16 | void myInfoWithBadBearerAuth() {
17 | }
18 |
19 | @DisplayName("Bearer Auth 유효하지 않은 토큰")
20 | @Test
21 | void myInfoWithWrongBearerAuth() {
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/test/java/nextstep/subway/auth/application/AuthServiceTest.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.auth.application;
2 |
3 | import nextstep.subway.member.domain.Member;
4 | import nextstep.subway.member.domain.MemberRepository;
5 | import nextstep.subway.auth.dto.TokenRequest;
6 | import nextstep.subway.auth.dto.TokenResponse;
7 | import nextstep.subway.auth.infrastructure.JwtTokenProvider;
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 nextstep.subway.AcceptanceTest;
4 | import org.junit.jupiter.api.DisplayName;
5 |
6 | @DisplayName("즐겨찾기 관련 기능")
7 | public class FavoriteAcceptanceTest extends AcceptanceTest {
8 | }
--------------------------------------------------------------------------------
/src/test/java/nextstep/subway/line/acceptance/LineAcceptanceTest.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.line.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.line.dto.LineRequest;
8 | import nextstep.subway.line.dto.LineResponse;
9 | import nextstep.subway.station.StationAcceptanceTest;
10 | import nextstep.subway.station.dto.StationResponse;
11 | import org.junit.jupiter.api.BeforeEach;
12 | import org.junit.jupiter.api.DisplayName;
13 | import org.junit.jupiter.api.Test;
14 | import org.springframework.http.HttpStatus;
15 | import org.springframework.http.MediaType;
16 |
17 | import java.util.Arrays;
18 | import java.util.List;
19 | import java.util.stream.Collectors;
20 |
21 | import static org.assertj.core.api.Assertions.assertThat;
22 |
23 | @DisplayName("지하철 노선 관련 기능")
24 | public class LineAcceptanceTest extends AcceptanceTest {
25 | private StationResponse 강남역;
26 | private StationResponse 광교역;
27 | private LineRequest lineRequest1;
28 | private LineRequest lineRequest2;
29 |
30 | @BeforeEach
31 | public void setUp() {
32 | super.setUp();
33 |
34 | // given
35 | 강남역 = StationAcceptanceTest.지하철역_등록되어_있음("강남역").as(StationResponse.class);
36 | 광교역 = StationAcceptanceTest.지하철역_등록되어_있음("광교역").as(StationResponse.class);
37 |
38 | lineRequest1 = new LineRequest("신분당선", "bg-red-600", 강남역.getId(), 광교역.getId(), 10);
39 | lineRequest2 = new LineRequest("구신분당선", "bg-red-600", 강남역.getId(), 광교역.getId(), 15);
40 | }
41 |
42 | @DisplayName("지하철 노선을 생성한다.")
43 | @Test
44 | void createLine() {
45 | // when
46 | ExtractableResponse response = 지하철_노선_생성_요청(lineRequest1);
47 |
48 | // then
49 | 지하철_노선_생성됨(response);
50 | }
51 |
52 | @DisplayName("기존에 존재하는 지하철 노선 이름으로 지하철 노선을 생성한다.")
53 | @Test
54 | void createLineWithDuplicateName() {
55 | // given
56 | 지하철_노선_등록되어_있음(lineRequest1);
57 |
58 | // when
59 | ExtractableResponse response = 지하철_노선_생성_요청(lineRequest1);
60 |
61 | // then
62 | 지하철_노선_생성_실패됨(response);
63 | }
64 |
65 | @DisplayName("지하철 노선 목록을 조회한다.")
66 | @Test
67 | void getLines() {
68 | // given
69 | ExtractableResponse createResponse1 = 지하철_노선_등록되어_있음(lineRequest1);
70 | ExtractableResponse createResponse2 = 지하철_노선_등록되어_있음(lineRequest2);
71 |
72 | // when
73 | ExtractableResponse response = 지하철_노선_목록_조회_요청();
74 |
75 | // then
76 | 지하철_노선_목록_응답됨(response);
77 | 지하철_노선_목록_포함됨(response, Arrays.asList(createResponse1, createResponse2));
78 | }
79 |
80 | @DisplayName("지하철 노선을 조회한다.")
81 | @Test
82 | void getLine() {
83 | // given
84 | ExtractableResponse createResponse = 지하철_노선_등록되어_있음(lineRequest1);
85 |
86 | // when
87 | ExtractableResponse response = 지하철_노선_목록_조회_요청(createResponse);
88 |
89 | // then
90 | 지하철_노선_응답됨(response, createResponse);
91 | }
92 |
93 | @DisplayName("지하철 노선을 수정한다.")
94 | @Test
95 | void updateLine() {
96 | // given
97 | String name = "신분당선";
98 | ExtractableResponse createResponse = 지하철_노선_등록되어_있음(lineRequest1);
99 |
100 | // when
101 | ExtractableResponse response = 지하철_노선_수정_요청(createResponse, lineRequest2);
102 |
103 | // then
104 | 지하철_노선_수정됨(response);
105 | }
106 |
107 | @DisplayName("지하철 노선을 제거한다.")
108 | @Test
109 | void deleteLine() {
110 | // given
111 | ExtractableResponse createResponse = 지하철_노선_등록되어_있음(lineRequest1);
112 |
113 | // when
114 | ExtractableResponse response = 지하철_노선_제거_요청(createResponse);
115 |
116 | // then
117 | 지하철_노선_삭제됨(response);
118 | }
119 |
120 | public static ExtractableResponse 지하철_노선_등록되어_있음(LineRequest params) {
121 | return 지하철_노선_생성_요청(params);
122 | }
123 |
124 | public static ExtractableResponse 지하철_노선_생성_요청(LineRequest params) {
125 | return RestAssured
126 | .given().log().all()
127 | .contentType(MediaType.APPLICATION_JSON_VALUE)
128 | .body(params)
129 | .when().post("/lines")
130 | .then().log().all().
131 | extract();
132 | }
133 |
134 | public static ExtractableResponse 지하철_노선_목록_조회_요청() {
135 | return 지하철_노선_목록_조회_요청("/lines");
136 | }
137 |
138 | public static ExtractableResponse 지하철_노선_목록_조회_요청(ExtractableResponse response) {
139 | String uri = response.header("Location");
140 |
141 | return 지하철_노선_목록_조회_요청(uri);
142 | }
143 |
144 | private static ExtractableResponse 지하철_노선_목록_조회_요청(String uri) {
145 | return RestAssured
146 | .given().log().all()
147 | .accept(MediaType.APPLICATION_JSON_VALUE)
148 | .when().get(uri)
149 | .then().log().all()
150 | .extract();
151 | }
152 |
153 | public static ExtractableResponse 지하철_노선_조회_요청(LineResponse response) {
154 | return RestAssured
155 | .given().log().all()
156 | .accept(MediaType.APPLICATION_JSON_VALUE)
157 | .when().get("/lines/{lineId}", response.getId())
158 | .then().log().all()
159 | .extract();
160 | }
161 |
162 | public static ExtractableResponse 지하철_노선_수정_요청(ExtractableResponse response, LineRequest params) {
163 | String uri = response.header("Location");
164 |
165 | return RestAssured
166 | .given().log().all()
167 | .contentType(MediaType.APPLICATION_JSON_VALUE)
168 | .body(params)
169 | .when().put(uri)
170 | .then().log().all()
171 | .extract();
172 | }
173 |
174 | public static ExtractableResponse 지하철_노선_제거_요청(ExtractableResponse response) {
175 | String uri = response.header("Location");
176 |
177 | return RestAssured
178 | .given().log().all()
179 | .when().delete(uri)
180 | .then().log().all()
181 | .extract();
182 | }
183 |
184 | public static void 지하철_노선_생성됨(ExtractableResponse response) {
185 | assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value());
186 | assertThat(response.header("Location")).isNotBlank();
187 | }
188 |
189 | public static void 지하철_노선_생성_실패됨(ExtractableResponse response) {
190 | assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value());
191 | }
192 |
193 | public static void 지하철_노선_목록_응답됨(ExtractableResponse response) {
194 | assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
195 | }
196 |
197 | public static void 지하철_노선_응답됨(ExtractableResponse response, ExtractableResponse createdResponse) {
198 | assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
199 | assertThat(response.as(LineResponse.class)).isNotNull();
200 | }
201 |
202 | public static void 지하철_노선_목록_포함됨(ExtractableResponse response, List> createdResponses) {
203 | List expectedLineIds = createdResponses.stream()
204 | .map(it -> Long.parseLong(it.header("Location").split("/")[2]))
205 | .collect(Collectors.toList());
206 |
207 | List resultLineIds = response.jsonPath().getList(".", LineResponse.class).stream()
208 | .map(LineResponse::getId)
209 | .collect(Collectors.toList());
210 |
211 | assertThat(resultLineIds).containsAll(expectedLineIds);
212 | }
213 |
214 | public static void 지하철_노선_수정됨(ExtractableResponse response) {
215 | assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
216 | }
217 |
218 | public static void 지하철_노선_삭제됨(ExtractableResponse response) {
219 | assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value());
220 | }
221 | }
222 |
--------------------------------------------------------------------------------
/src/test/java/nextstep/subway/line/acceptance/LineSectionAcceptanceTest.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.line.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.line.dto.LineRequest;
8 | import nextstep.subway.line.dto.LineResponse;
9 | import nextstep.subway.line.dto.SectionRequest;
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.Arrays;
19 | import java.util.List;
20 | import java.util.stream.Collectors;
21 |
22 | import static org.assertj.core.api.Assertions.assertThat;
23 |
24 | @DisplayName("지하철 구간 관련 기능")
25 | public class LineSectionAcceptanceTest extends AcceptanceTest {
26 | private LineResponse 신분당선;
27 | private StationResponse 강남역;
28 | private StationResponse 양재역;
29 | private StationResponse 정자역;
30 | private StationResponse 광교역;
31 |
32 | @BeforeEach
33 | public void setUp() {
34 | super.setUp();
35 |
36 | 강남역 = StationAcceptanceTest.지하철역_등록되어_있음("강남역").as(StationResponse.class);
37 | 양재역 = StationAcceptanceTest.지하철역_등록되어_있음("양재역").as(StationResponse.class);
38 | 정자역 = StationAcceptanceTest.지하철역_등록되어_있음("정자역").as(StationResponse.class);
39 | 광교역 = StationAcceptanceTest.지하철역_등록되어_있음("광교역").as(StationResponse.class);
40 |
41 | LineRequest lineRequest = new LineRequest("신분당선", "bg-red-600", 강남역.getId(), 광교역.getId(), 10);
42 | 신분당선 = LineAcceptanceTest.지하철_노선_등록되어_있음(lineRequest).as(LineResponse.class);
43 | }
44 |
45 | @DisplayName("지하철 구간을 등록한다.")
46 | @Test
47 | void addLineSection() {
48 | // when
49 | 지하철_노선에_지하철역_등록_요청(신분당선, 강남역, 양재역, 3);
50 |
51 | // then
52 | ExtractableResponse response = LineAcceptanceTest.지하철_노선_조회_요청(신분당선);
53 | 지하철_노선에_지하철역_등록됨(response);
54 | 지하철_노선에_지하철역_순서_정렬됨(response, Arrays.asList(강남역, 양재역, 광교역));
55 | }
56 |
57 | @DisplayName("지하철 노선에 여러개의 역을 순서 상관 없이 등록한다.")
58 | @Test
59 | void addLineSection2() {
60 | // when
61 | 지하철_노선에_지하철역_등록_요청(신분당선, 강남역, 양재역, 2);
62 | 지하철_노선에_지하철역_등록_요청(신분당선, 정자역, 강남역, 5);
63 |
64 | // then
65 | ExtractableResponse response = LineAcceptanceTest.지하철_노선_조회_요청(신분당선);
66 | 지하철_노선에_지하철역_등록됨(response);
67 | 지하철_노선에_지하철역_순서_정렬됨(response, Arrays.asList(정자역, 강남역, 양재역, 광교역));
68 | }
69 |
70 | @DisplayName("지하철 노선에 이미 등록되어있는 역을 등록한다.")
71 | @Test
72 | void addLineSectionWithSameStation() {
73 | // when
74 | ExtractableResponse response = 지하철_노선에_지하철역_등록_요청(신분당선, 강남역, 광교역, 3);
75 |
76 | // then
77 | 지하철_노선에_지하철역_등록_실패됨(response);
78 | }
79 |
80 | @DisplayName("지하철 노선에 등록되지 않은 역을 기준으로 등록한다.")
81 | @Test
82 | void addLineSectionWithNoStation() {
83 | // when
84 | ExtractableResponse response = 지하철_노선에_지하철역_등록_요청(신분당선, 정자역, 양재역, 3);
85 |
86 | // then
87 | 지하철_노선에_지하철역_등록_실패됨(response);
88 | }
89 |
90 | @DisplayName("지하철 노선에 등록된 지하철역을 제외한다.")
91 | @Test
92 | void removeLineSection1() {
93 | // given
94 | 지하철_노선에_지하철역_등록_요청(신분당선, 강남역, 양재역, 2);
95 | 지하철_노선에_지하철역_등록_요청(신분당선, 양재역, 정자역, 2);
96 |
97 | // when
98 | ExtractableResponse removeResponse = 지하철_노선에_지하철역_제외_요청(신분당선, 양재역);
99 |
100 | // then
101 | 지하철_노선에_지하철역_제외됨(removeResponse);
102 | ExtractableResponse response = LineAcceptanceTest.지하철_노선_조회_요청(신분당선);
103 | 지하철_노선에_지하철역_순서_정렬됨(response, Arrays.asList(강남역, 정자역, 광교역));
104 | }
105 |
106 | @DisplayName("지하철 노선에 등록된 지하철역이 두개일 때 한 역을 제외한다.")
107 | @Test
108 | void removeLineSection2() {
109 | // when
110 | ExtractableResponse removeResponse = 지하철_노선에_지하철역_제외_요청(신분당선, 강남역);
111 |
112 | // then
113 | 지하철_노선에_지하철역_제외_실패됨(removeResponse);
114 | }
115 |
116 | public static ExtractableResponse 지하철_노선에_지하철역_등록_요청(LineResponse line, StationResponse upStation, StationResponse downStation, int distance) {
117 | SectionRequest sectionRequest = new SectionRequest(upStation.getId(), downStation.getId(), distance);
118 |
119 | return RestAssured
120 | .given().log().all()
121 | .contentType(MediaType.APPLICATION_JSON_VALUE)
122 | .body(sectionRequest)
123 | .when().post("/lines/{lineId}/sections", line.getId())
124 | .then().log().all()
125 | .extract();
126 | }
127 |
128 | public static void 지하철_노선에_지하철역_등록됨(ExtractableResponse response) {
129 | assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
130 | }
131 |
132 | public static void 지하철_노선에_지하철역_등록_실패됨(ExtractableResponse response) {
133 | assertThat(response.statusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.value());
134 | }
135 |
136 | public static void 지하철_노선에_지하철역_순서_정렬됨(ExtractableResponse response, List expectedStations) {
137 | LineResponse line = response.as(LineResponse.class);
138 | List stationIds = line.getStations().stream()
139 | .map(it -> it.getId())
140 | .collect(Collectors.toList());
141 |
142 | List expectedStationIds = expectedStations.stream()
143 | .map(it -> it.getId())
144 | .collect(Collectors.toList());
145 |
146 | assertThat(stationIds).containsExactlyElementsOf(expectedStationIds);
147 | }
148 |
149 | public static ExtractableResponse 지하철_노선에_지하철역_제외_요청(LineResponse line, StationResponse station) {
150 | return RestAssured
151 | .given().log().all()
152 | .when().delete("/lines/{lineId}/sections?stationId={stationId}", line.getId(), station.getId())
153 | .then().log().all()
154 | .extract();
155 | }
156 |
157 | public static void 지하철_노선에_지하철역_제외됨(ExtractableResponse response) {
158 | assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
159 | }
160 |
161 | public static void 지하철_노선에_지하철역_제외_실패됨(ExtractableResponse response) {
162 | assertThat(response.statusCode()).isEqualTo(HttpStatus.INTERNAL_SERVER_ERROR.value());
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/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.MemberRequest;
8 | import nextstep.subway.member.dto.MemberResponse;
9 | import org.junit.jupiter.api.DisplayName;
10 | import org.junit.jupiter.api.Test;
11 | import org.springframework.http.HttpStatus;
12 | import org.springframework.http.MediaType;
13 |
14 | import static org.assertj.core.api.Assertions.assertThat;
15 |
16 | public class MemberAcceptanceTest extends AcceptanceTest {
17 | public static final String EMAIL = "email@email.com";
18 | public static final String PASSWORD = "password";
19 | public static final String NEW_EMAIL = "newemail@email.com";
20 | public static final String NEW_PASSWORD = "newpassword";
21 | public static final int AGE = 20;
22 | public static final int NEW_AGE = 21;
23 |
24 | @DisplayName("회원 정보를 관리한다.")
25 | @Test
26 | void manageMember() {
27 | // when
28 | ExtractableResponse createResponse = 회원_생성을_요청(EMAIL, PASSWORD, AGE);
29 | // then
30 | 회원_생성됨(createResponse);
31 |
32 | // when
33 | ExtractableResponse findResponse = 회원_정보_조회_요청(createResponse);
34 | // then
35 | 회원_정보_조회됨(findResponse, EMAIL, AGE);
36 |
37 | // when
38 | ExtractableResponse updateResponse = 회원_정보_수정_요청(createResponse, NEW_EMAIL, NEW_PASSWORD, NEW_AGE);
39 | // then
40 | 회원_정보_수정됨(updateResponse);
41 |
42 | // when
43 | ExtractableResponse deleteResponse = 회원_삭제_요청(createResponse);
44 | // then
45 | 회원_삭제됨(deleteResponse);
46 | }
47 |
48 | @DisplayName("나의 정보를 관리한다.")
49 | @Test
50 | void manageMyInfo() {
51 |
52 | }
53 |
54 | public static ExtractableResponse 회원_생성을_요청(String email, String password, Integer age) {
55 | MemberRequest memberRequest = new MemberRequest(email, password, age);
56 |
57 | return RestAssured
58 | .given().log().all()
59 | .contentType(MediaType.APPLICATION_JSON_VALUE)
60 | .body(memberRequest)
61 | .when().post("/members")
62 | .then().log().all()
63 | .extract();
64 | }
65 |
66 | public static ExtractableResponse 회원_정보_조회_요청(ExtractableResponse response) {
67 | String uri = response.header("Location");
68 |
69 | return RestAssured
70 | .given().log().all()
71 | .accept(MediaType.APPLICATION_JSON_VALUE)
72 | .when().get(uri)
73 | .then().log().all()
74 | .extract();
75 | }
76 |
77 | public static ExtractableResponse 회원_정보_수정_요청(ExtractableResponse response, String email, String password, Integer age) {
78 | String uri = response.header("Location");
79 | MemberRequest memberRequest = new MemberRequest(email, password, age);
80 |
81 | return RestAssured
82 | .given().log().all()
83 | .contentType(MediaType.APPLICATION_JSON_VALUE)
84 | .body(memberRequest)
85 | .when().put(uri)
86 | .then().log().all()
87 | .extract();
88 | }
89 |
90 | public static ExtractableResponse 회원_삭제_요청(ExtractableResponse response) {
91 | String uri = response.header("Location");
92 | return RestAssured
93 | .given().log().all()
94 | .when().delete(uri)
95 | .then().log().all()
96 | .extract();
97 | }
98 |
99 | public static void 회원_생성됨(ExtractableResponse response) {
100 | assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value());
101 | }
102 |
103 | public static void 회원_정보_조회됨(ExtractableResponse response, String email, int age) {
104 | MemberResponse memberResponse = response.as(MemberResponse.class);
105 | assertThat(memberResponse.getId()).isNotNull();
106 | assertThat(memberResponse.getEmail()).isEqualTo(email);
107 | assertThat(memberResponse.getAge()).isEqualTo(age);
108 | }
109 |
110 | public static void 회원_정보_수정됨(ExtractableResponse response) {
111 | assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
112 | }
113 |
114 | public static void 회원_삭제됨(ExtractableResponse response) {
115 | assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value());
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/test/java/nextstep/subway/path/PathAcceptanceTest.java:
--------------------------------------------------------------------------------
1 | package nextstep.subway.path;
2 |
3 | import nextstep.subway.AcceptanceTest;
4 | import org.junit.jupiter.api.DisplayName;
5 |
6 |
7 | @DisplayName("지하철 경로 조회")
8 | public class PathAcceptanceTest extends AcceptanceTest {
9 | }
10 |
--------------------------------------------------------------------------------
/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.StationRequest;
8 | import nextstep.subway.station.dto.StationResponse;
9 | import org.junit.jupiter.api.DisplayName;
10 | import org.junit.jupiter.api.Test;
11 | import org.springframework.http.HttpStatus;
12 | import org.springframework.http.MediaType;
13 |
14 | import java.util.Arrays;
15 | import java.util.List;
16 | import java.util.stream.Collectors;
17 |
18 | import static org.assertj.core.api.Assertions.assertThat;
19 |
20 | @DisplayName("지하철역 관련 기능")
21 | public class StationAcceptanceTest extends AcceptanceTest {
22 | private static final String 강남역 = "강남역";
23 | private static final String 역삼역 = "역삼역";
24 |
25 | @DisplayName("지하철역을 생성한다.")
26 | @Test
27 | void createStation() {
28 | // when
29 | ExtractableResponse response = 지하철역_생성_요청(강남역);
30 |
31 | // then
32 | 지하철역_생성됨(response);
33 | }
34 |
35 | @DisplayName("기존에 존재하는 지하철역 이름으로 지하철역을 생성한다.")
36 | @Test
37 | void createStationWithDuplicateName() {
38 | //given
39 | 지하철역_등록되어_있음(강남역);
40 |
41 | // when
42 | ExtractableResponse response = 지하철역_생성_요청(강남역);
43 |
44 | // then
45 | 지하철역_생성_실패됨(response);
46 | }
47 |
48 | @DisplayName("지하철역을 조회한다.")
49 | @Test
50 | void getStations() {
51 | // given
52 | ExtractableResponse createResponse1 = 지하철역_등록되어_있음(강남역);
53 | ExtractableResponse createResponse2 = 지하철역_등록되어_있음(역삼역);
54 |
55 | // when
56 | ExtractableResponse response = 지하철역_목록_조회_요청();
57 |
58 | // then
59 | 지하철역_목록_응답됨(response);
60 | 지하철역_목록_포함됨(response, Arrays.asList(createResponse1, createResponse2));
61 | }
62 |
63 | @DisplayName("지하철역을 제거한다.")
64 | @Test
65 | void deleteStation() {
66 | // given
67 | ExtractableResponse createResponse = 지하철역_등록되어_있음(강남역);
68 |
69 | // when
70 | ExtractableResponse response = 지하철역_제거_요청(createResponse);
71 |
72 | // then
73 | 지하철역_삭제됨(response);
74 | }
75 |
76 | public static ExtractableResponse 지하철역_등록되어_있음(String name) {
77 | return 지하철역_생성_요청(name);
78 | }
79 |
80 | public static ExtractableResponse 지하철역_생성_요청(String name) {
81 | StationRequest stationRequest = new StationRequest(name);
82 |
83 | return RestAssured
84 | .given().log().all()
85 | .body(stationRequest)
86 | .contentType(MediaType.APPLICATION_JSON_VALUE)
87 | .when().post("/stations")
88 | .then().log().all()
89 | .extract();
90 | }
91 |
92 | public static ExtractableResponse 지하철역_목록_조회_요청() {
93 | return RestAssured
94 | .given().log().all()
95 | .when().get("/stations")
96 | .then().log().all()
97 | .extract();
98 | }
99 |
100 | public static ExtractableResponse 지하철역_제거_요청(ExtractableResponse response) {
101 | String uri = response.header("Location");
102 |
103 | return RestAssured
104 | .given().log().all()
105 | .when().delete(uri)
106 | .then().log().all()
107 | .extract();
108 | }
109 |
110 | public static void 지하철역_생성됨(ExtractableResponse response) {
111 | assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value());
112 | assertThat(response.header("Location")).isNotBlank();
113 | }
114 |
115 | public static void 지하철역_생성_실패됨(ExtractableResponse response) {
116 | assertThat(response.statusCode()).isEqualTo(HttpStatus.BAD_REQUEST.value());
117 | }
118 |
119 | public static void 지하철역_목록_응답됨(ExtractableResponse response) {
120 | assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
121 | }
122 |
123 | public static void 지하철역_삭제됨(ExtractableResponse response) {
124 | assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value());
125 | }
126 |
127 | public static void 지하철역_목록_포함됨(ExtractableResponse response, List> createdResponses) {
128 | List expectedLineIds = createdResponses.stream()
129 | .map(it -> Long.parseLong(it.header("Location").split("/")[2]))
130 | .collect(Collectors.toList());
131 |
132 | List resultLineIds = response.jsonPath().getList(".", StationResponse.class).stream()
133 | .map(StationResponse::getId)
134 | .collect(Collectors.toList());
135 |
136 | assertThat(resultLineIds).containsAll(expectedLineIds);
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/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 nextstep.subway.station.domain.StationRepository;
10 | import org.junit.jupiter.api.DisplayName;
11 | import org.junit.jupiter.api.Test;
12 | import org.junit.jupiter.api.extension.ExtendWith;
13 | import org.mockito.Mock;
14 | import org.mockito.junit.jupiter.MockitoExtension;
15 |
16 | import java.util.List;
17 |
18 | import static org.assertj.core.api.Assertions.assertThat;
19 | import static org.mockito.Mockito.when;
20 |
21 | @DisplayName("단위 테스트 - mockito의 MockitoExtension을 활용한 가짜 협력 객체 사용")
22 | @ExtendWith(MockitoExtension.class)
23 | public class MockitoExtensionTest {
24 | @Mock
25 | private LineRepository lineRepository;
26 | @Mock
27 | private StationService stationService;
28 |
29 | @Test
30 | void findAllLines() {
31 | // given
32 | when(lineRepository.findAll()).thenReturn(Lists.newArrayList(new Line()));
33 | LineService lineService = new LineService(lineRepository, stationService);
34 |
35 | // when
36 | List responses = lineService.findLines();
37 |
38 | // then
39 | assertThat(responses).hasSize(1);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/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 nextstep.subway.station.domain.StationRepository;
10 | import org.junit.jupiter.api.DisplayName;
11 | import org.junit.jupiter.api.Test;
12 |
13 | import java.util.List;
14 |
15 | import static org.assertj.core.api.Assertions.assertThat;
16 | import static org.mockito.Mockito.mock;
17 | import static org.mockito.Mockito.when;
18 |
19 | @DisplayName("단위 테스트 - mockito를 활용한 가짜 협력 객체 사용")
20 | public class MockitoTest {
21 | @Test
22 | void findAllLines() {
23 | // given
24 | LineRepository lineRepository = mock(LineRepository.class);
25 | StationService stationService = mock(StationService.class);
26 |
27 | when(lineRepository.findAll()).thenReturn(Lists.newArrayList(new Line()));
28 | LineService lineService = new LineService(lineRepository, stationService);
29 |
30 | // when
31 | List responses = lineService.findLines();
32 |
33 | // then
34 | assertThat(responses).hasSize(1);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/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 nextstep.subway.station.domain.StationRepository;
10 | import org.junit.jupiter.api.DisplayName;
11 | import org.junit.jupiter.api.Test;
12 | import org.junit.jupiter.api.extension.ExtendWith;
13 | import org.springframework.boot.test.mock.mockito.MockBean;
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("단위 테스트 - SpringExtension을 활용한 가짜 협력 객체 사용")
21 | @ExtendWith(org.springframework.test.context.junit.jupiter.SpringExtension.class)
22 | public class SpringExtensionTest {
23 | @MockBean
24 | private LineRepository lineRepository;
25 | @MockBean
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.findLines();
36 |
37 | // then
38 | assertThat(responses).hasSize(1);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/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 | @Test
13 | void update() {
14 | // given
15 | String newName = "구분당선";
16 |
17 | Station upStation = new Station("강남역");
18 | Station downStation = new Station("광교역");
19 | Line line = new Line("신분당선", "RED", upStation, downStation, 10);
20 | Line newLine = new Line(newName, "GREEN");
21 |
22 | // when
23 | line.update(newLine);
24 |
25 | // then
26 | assertThat(line.getName()).isEqualTo(newName);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------