├── .gitignore
├── .gitlab-ci.yml
├── LICENSE
├── README.md
├── babel.config.js
├── deliverables
├── BangJjin_ServiceArchitecture.png
├── BangJjin_Subscribe.png
├── chatting.gif
├── commitchart.png
├── logo.png
├── logo_origin.png
├── meeting1.jpg
├── meeting2.jpg
├── pipelinechart.png
├── pullrequest.png
├── randomnickname.gif
├── realtimemarker.gif
├── realtimeroadview.gif
├── schedule.png
├── service-architecture.png
└── serviceflow.png
├── firebase.json
├── meeting-log
├── 20191015.md
├── 20191016.md
├── 20191021.md
├── 20191022.md
├── 20191023.md
├── 20191025.md
├── 20191029.md
├── 20191031.md
├── 20191101.md
└── 20191105.md
├── package-lock.json
├── package.json
├── public
└── index.html
├── src
├── App.vue
├── api
│ ├── firebaseApi.js
│ └── kakaomapApi.js
├── assets
│ ├── another_resources.dummy
│ └── logo.svg
├── components
│ ├── BackButton.vue
│ ├── Background.vue
│ ├── ChatRoom.vue
│ ├── ChatRoomDetail.vue
│ ├── ChatRoomInfo.vue
│ ├── ChatRoomList.vue
│ ├── LoginForm.vue
│ ├── MapMarker.vue
│ ├── RoadView.vue
│ └── SignUpForm.vue
├── fonts
│ └── yg-jalnan.woff
├── images
│ ├── cancel.png
│ ├── car.png
│ ├── compass.png
│ ├── globe.png
│ ├── location.png
│ ├── locked.png
│ ├── logo1.png
│ ├── power.png
│ ├── scooter.png
│ └── unlocked.png
├── main.js
├── plugins
│ └── vuetify.js
├── routes
│ └── index.js
├── store
│ └── index.js
├── utils
│ ├── hashMap.js
│ ├── nicknamedata.js
│ ├── randomName.js
│ ├── storage.js
│ └── time.js
└── views
│ ├── Auth.vue
│ ├── Map.vue
│ └── Travel.vue
└── wiki
├── ESlint.md
├── JSON.md
├── about_MVVMpattern.md
├── about_callback_function.md
├── about_firebase.md
├── about_firebase_auth.md
├── about_firebase_auth
└── googleLogin_emailLogin.png
├── about_markdown.md
├── about_promise_async_await.md
├── about_realtime_database_chat.md
├── about_rest.md
├── about_roadview.md
├── about_vscode.md
├── cicd.md
├── es6-for-vue.md
├── exception_handling.md
├── gitlab-ci.md
├── gitlab-ci
├── merge-request-policy.PNG
├── merge-to-develop.PNG
├── pipeline.PNG
├── protect-branch.PNG
├── push-to-feature.PNG
├── runner-info.PNG
└── runner-status.PNG
├── javascript-modularization.md
├── lifecycle-vue.md
├── this.md
├── var_let_const.md
└── vuex-vue.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io/api/vue,java,vuejs,macos,windows,firebase,visualstudiocode
2 | # Edit at https://www.gitignore.io/?templates=vue,java,vuejs,macos,windows,firebase,visualstudiocode
3 |
4 | ### Firebase ###
5 | .idea
6 | **/node_modules/*
7 | **/.firebaserc
8 |
9 | ### Firebase Patch ###
10 | .runtimeconfig.json
11 | .firebase/
12 |
13 | ### Java ###
14 | # Compiled class file
15 | *.class
16 |
17 | # Log file
18 | *.log
19 |
20 | # BlueJ files
21 | *.ctxt
22 |
23 | # Mobile Tools for Java (J2ME)
24 | .mtj.tmp/
25 |
26 | # Package Files #
27 | *.jar
28 | *.war
29 | *.nar
30 | *.ear
31 | *.zip
32 | *.tar.gz
33 | *.rar
34 |
35 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
36 | hs_err_pid*
37 |
38 | ### macOS ###
39 | # General
40 | .DS_Store
41 | .AppleDouble
42 | .LSOverride
43 |
44 | # Icon must end with two \r
45 | Icon
46 |
47 | # Thumbnails
48 | ._*
49 |
50 | # Files that might appear in the root of a volume
51 | .DocumentRevisions-V100
52 | .fseventsd
53 | .Spotlight-V100
54 | .TemporaryItems
55 | .Trashes
56 | .VolumeIcon.icns
57 | .com.apple.timemachine.donotpresent
58 |
59 | # Directories potentially created on remote AFP share
60 | .AppleDB
61 | .AppleDesktop
62 | Network Trash Folder
63 | Temporary Items
64 | .apdisk
65 |
66 | ### VisualStudioCode ###
67 | .vscode/*
68 | !.vscode/settings.json
69 | !.vscode/tasks.json
70 | !.vscode/launch.json
71 | !.vscode/extensions.json
72 |
73 | ### VisualStudioCode Patch ###
74 | # Ignore all local history of files
75 | .history
76 |
77 | ### Vue ###
78 | .DS_*
79 | logs
80 | **/*.backup.*
81 | **/*.back.*
82 |
83 | node_modules
84 | bower_componets
85 |
86 | *.sublime*
87 |
88 | psd
89 | thumb
90 | sketch
91 |
92 | ### Vuejs ###
93 | # Recommended template: Node.gitignore
94 |
95 | node_modules/
96 | dist/
97 | npm-debug.log
98 | yarn-error.log
99 |
100 | ### Windows ###
101 | # Windows thumbnail cache files
102 | Thumbs.db
103 | Thumbs.db:encryptable
104 | ehthumbs.db
105 | ehthumbs_vista.db
106 |
107 | # Dump file
108 | *.stackdump
109 |
110 | # Folder config file
111 | [Dd]esktop.ini
112 |
113 | # Recycle Bin used on file shares
114 | $RECYCLE.BIN/
115 |
116 | # Windows Installer files
117 | *.cab
118 | *.msi
119 | *.msix
120 | *.msm
121 | *.msp
122 |
123 | # Windows shortcuts
124 | *.lnk
125 |
126 | # End of https://www.gitignore.io/api/vue,java,vuejs,macos,windows,firebase,visualstudiocode
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | # 배포 작업
2 | deploy-to-server:
3 | stage: deploy
4 | only:
5 | - master
6 | script:
7 | - echo 'DEPLOY RUNNING'
8 | - npm install --progress=false
9 | - npm run build
10 | - pwd
11 | - firebase deploy
12 |
13 | tags:
14 | - deploy
15 |
16 | # 빌드 테스트 작업
17 | build-test:
18 | stage: build
19 | script:
20 | - echo 'BUILD TEST RUNNING'
21 | - npm install --progress=false
22 | - npm run build
23 | tags:
24 | - build
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 방찐
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## 🌎 웹으로 여행하자!
4 | - ### 서비스 소개 영상
5 |
6 | - ### 일상에 지친 당신!
7 |
8 | - ### 여행갈 형편이 되지않는 당신!
9 |
10 | - ### 심심한 당신!
11 |
12 | ### 바로 당신을 위한 가상 여행 서비스 '방구석 여행'
13 |
14 |
15 |
16 | ## 💡 주요 기능
17 |
18 | - ### 랜덤 닉네임
19 |
20 |
21 |
22 |
23 |
24 | - ### 실시간 마커 이동
25 |
26 |
27 |
28 |
29 |
30 | - ### 실시간 공유 로드뷰
31 |
32 |
33 |
34 |
35 |
36 | - ### 채팅
37 |
38 |
39 |
40 |
41 |
42 | ## 라이센스
43 |
44 | ### This is released under the MIT license. See [LICENSE](./LICENSE) for details.
45 |
46 |
47 |
48 |
49 |
50 | ## 프로젝트에 관하여
51 |
52 | > 삼성 청년 소프트웨어 아카데미 1기 / 2학기 심화프로젝트 18조
53 |
54 |
55 |
56 | ## 🙌 팀
57 |
58 | - #### 조우현 : Project Manager
59 |
60 | - `CI/CD`, `UI`, `Firebase Authentication`
61 |
62 | - #### 곽빛나라 : Map part Developer
63 |
64 | - `Kakao map`, `chat room & map connector`
65 |
66 | - #### 채윤병 : Map part Developer
67 |
68 | - `Kakao roadview`, `chat room & roadview connector`
69 |
70 | - #### 최승미 : Chat part Developer
71 |
72 | - `Firestore Database design & implement`, `chat room design`
73 |
74 | - #### 강민 : Chat part Developer
75 |
76 | - `Firestore Database design & implement`, `random nickname`
77 |
78 |
79 |
80 | ## 📆 프로젝트 일정
81 |
82 |
83 |
84 |
85 |
86 | ## 💡 브랜치 전략
87 |
88 | - CI / CD를 활용한 빌드 테스팅 / 배포 자동화
89 |
90 | - Git Branch 전략
91 |
92 | - `Master Branch`
93 |
94 | 배포용 브랜치로 해당 브랜치로 merge되면 자동으로 배포된다.
95 |
96 | - `Develop Branch`
97 |
98 | 개발중 가장 최신화된 버전의 소스
99 |
100 | - `Feature Branch`
101 |
102 | 기능 개발시 `Develop Branch`로 부터 생성, 개발 완료후 `Develop Branch`로 merge
103 |
104 |
105 |
106 | - Commit Chart
107 |
108 |
109 |
110 |
111 |
112 | - Pipeline Chart
113 |
114 |
115 |
116 |
117 |
118 | ## 🧱 프로젝트 아키텍쳐
119 |
120 | - Service Architecture
121 |
122 |
123 |
124 |
125 |
126 |
127 | - Service Flow
128 |
129 |
130 |
131 |
132 |
133 |
134 | ## 📚 문제해결 및 도움되는 문서
135 |
136 | - **문서 작성**
137 |
138 | - 마크다운 기본 문법
139 |
140 |
141 |
142 | - **개발환경**
143 |
144 | - CI / CD
145 | - CI / CD 개념정리
146 | - GitLab CI/CD 도입하기
147 | - VScode 팁 모음
148 | - ESlint
149 | - MVVM패턴
150 |
151 |
152 |
153 | - **Chatting**
154 |
155 | - Firebase Realtime database chatting app
156 | - 분석
157 | - Firebase Firestore chatting app
158 | - 분석
159 |
160 |
161 |
162 | - **Vue**
163 |
164 | - 실무에서 사용하는 Vue.js 프로젝트 구조
165 | - Vue.js 개발 생산성을 높여주는 도구 3가지
166 | - pakage.json에 대하여
167 | - Vuex에 대하여
168 | - Vue.js에 필요한 es6 문법
169 | - Lifecycle에 대하여
170 |
171 |
172 |
173 | - **JavaScript**
174 |
175 | - 비동기 개념 정리(Promise, Async/Await)
176 | - ES6 Import & Export
177 | - JavaScript의 변수선언(var, let, const)
178 | - 상황에 따른 this
179 | - Javascript의 callback 개념
180 | - 예외 처리
181 | - JSON
182 |
183 |
184 |
185 | - **API**
186 |
187 | - REST API 개념정리
188 | - Kakao Roadview
189 |
190 |
191 |
192 | - **Server**
193 |
194 | - Session/Cookie, JWT 인증 방식
195 | - Access Token + Refresh Token 인증 방식
196 | - SNS 로그인, OAuth
197 | - Firebase Auth
198 |
199 |
200 |
201 | ## 🤘 회의록
202 |
203 | - 2019년 10월 15일
204 | - 2019년 10월 16일
205 | - 2019년 10월 21일
206 | - 2019년 10월 22일
207 | - 2019년 10월 23일
208 | - 2019년 10월 25일
209 | - 2019년 10월 29일
210 | - 2019년 10월 31일
211 | - 2019년 11월 01일
212 | - 2019년 11월 05일
213 |
214 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@vue/cli-plugin-babel/preset']
3 | }
4 |
--------------------------------------------------------------------------------
/deliverables/BangJjin_ServiceArchitecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/deliverables/BangJjin_ServiceArchitecture.png
--------------------------------------------------------------------------------
/deliverables/BangJjin_Subscribe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/deliverables/BangJjin_Subscribe.png
--------------------------------------------------------------------------------
/deliverables/chatting.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/deliverables/chatting.gif
--------------------------------------------------------------------------------
/deliverables/commitchart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/deliverables/commitchart.png
--------------------------------------------------------------------------------
/deliverables/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/deliverables/logo.png
--------------------------------------------------------------------------------
/deliverables/logo_origin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/deliverables/logo_origin.png
--------------------------------------------------------------------------------
/deliverables/meeting1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/deliverables/meeting1.jpg
--------------------------------------------------------------------------------
/deliverables/meeting2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/deliverables/meeting2.jpg
--------------------------------------------------------------------------------
/deliverables/pipelinechart.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/deliverables/pipelinechart.png
--------------------------------------------------------------------------------
/deliverables/pullrequest.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/deliverables/pullrequest.png
--------------------------------------------------------------------------------
/deliverables/randomnickname.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/deliverables/randomnickname.gif
--------------------------------------------------------------------------------
/deliverables/realtimemarker.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/deliverables/realtimemarker.gif
--------------------------------------------------------------------------------
/deliverables/realtimeroadview.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/deliverables/realtimeroadview.gif
--------------------------------------------------------------------------------
/deliverables/schedule.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/deliverables/schedule.png
--------------------------------------------------------------------------------
/deliverables/service-architecture.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/deliverables/service-architecture.png
--------------------------------------------------------------------------------
/deliverables/serviceflow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/deliverables/serviceflow.png
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosting": {
3 | "public": "./dist",
4 | "ignore": [
5 | "firebase.json",
6 | "**/.*",
7 | "**/node_modules/**"
8 | ],
9 | "rewrites": [
10 | {
11 | "source": "**",
12 | "destination": "/index.html"
13 | }
14 | ]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/meeting-log/20191015.md:
--------------------------------------------------------------------------------
1 | # 2019년 10월 15일 화요일 회의록
2 |
3 | ### Server
4 |
5 | - Spring(API)
6 | - Spring Boot, JPA
7 | - Exception Handling 중요
8 |
9 | - Chat
10 | - Redis: Publish, Subscribe 편리, 처음 접하는 거라 부담
11 | - Firebase: 예제가 잘 되어 있음
12 |
13 | ### Client
14 |
15 | - Front
16 | - Vue.js
17 |
18 |
19 |
20 | ### Git
21 |
22 | - feature에 기능 구현 후 develop에 merge요청
23 |
24 | - merge제한 -> CI/CD, 코드리뷰
25 |
26 |
27 |
28 | ### 개인별 프로젝트 목적
29 |
30 | - 채윤병
31 | - 배포를 해보고 싶다.
32 | - 강민
33 | - Spring, API 사용
34 | - 곽빛나라, 최승미
35 | - Git Flow를 제대로 알고 싶다.
36 | - 조우현
37 | - API docs를 찾아보는 것, 인증키 얻어보는 등의 경험
38 | - 프로젝트의 Flow를 제대로 알고싶다.
39 | - 제대로 된 결과물을 만들고 싶다.
40 |
41 |
42 |
43 | ### 목표
44 |
45 | - VSCode에서 Spring, Vue.js 쓸 수 있도록 환경 구축
46 |
47 | - 문서화 집중적으로
48 | - 자신이 공부한 것은 팀원들에게 강의해주기
49 |
50 |
51 |
52 | ### 역할 분담
53 |
54 | - Project Manager: 조우현
55 |
56 | - Spring, API
57 | - 곽빛나라
58 | - 최승미
59 | - Firebase, JavaScript, Vue.js
60 | - 강민
61 | - 채윤병
62 | - 기록
63 | - 곽빛나라
64 |
65 |
66 |
67 | ### 기능 정의
68 |
69 | 1. 로그인 / 회원가입(뒷배경 배경 뿌옇게, 모달창을 통한 로그인화면)
70 | 2. .메인뷰 / 전체뷰(Map)
71 | - 채팅방 만들기
72 | 1. 채팅방 제목
73 | 2. 채팅방 목록: 현재 만들어져 있는 방 목록
74 | 4. 방장이 보는 로드뷰를 다른 사람이 똑같이 보이게 함
75 | 4. 부가기능: 채팅방 검색, 비밀번호 방, 제한 방
76 | - 채팅방 참여하기
77 | 1. 참여할 수 있는 채팅방은 하나
78 | 2. 방장은 View를 조작 가능
79 | 3. 사용자는 ReadOnly
80 | 4. Message Chat
81 | 5. 부가기능: 사진 첨부
82 |
83 |
84 |
85 | ### UI
86 |
87 | - Mock up 툴 사용
88 |
89 |
90 |
91 | ### 과제
92 |
93 | - 개인 GitHub에 Today I Learned 기록
94 |
95 | - 간단하게 API 사용해보기
96 | - RESTful API가 무엇인지 정확히 공부해보기
97 |
98 | - Coding Convention
99 | - 문서 이름은 영어, 모두 소문자, underbar로 구성, MarkDown
100 | - Java는 Camel Case
101 | - 변수명은 길어지더라도 줄임말없이 어떤 변수인지 정확하게 표현
102 | - 상수 선언 잘하기
103 | - Doc Branch 따로
104 |
105 |
--------------------------------------------------------------------------------
/meeting-log/20191016.md:
--------------------------------------------------------------------------------
1 | # 2019년 10월 16일 수요일 회의록
2 |
3 | ### 오늘 할 일
4 |
5 | - Rest API 공부(최승미)
6 | - Firebase 공부(강민)
7 | - Map API 공부(곽빛나라)
8 | - 로드뷰 API 공부(채윤병)
9 | - CI/CD (조우현)
10 |
11 |
--------------------------------------------------------------------------------
/meeting-log/20191021.md:
--------------------------------------------------------------------------------
1 | # 2019년 10월 21일 월요일 회의록
2 |
3 | ### 리소스 정의
4 |
5 | ##### 화면1. 첫 화면
6 |
7 | - 지도는 현재 위치를 기반
8 |
9 | ##### 화면2. 로그인 후
10 |
11 | - Firebase로 채팅을 만들 때, 채팅의 정보에 좌표 포함
12 |
13 | - 좌표 리스트를 받아와서 화면에 핀을 표시해주는 API 존재
14 |
15 | - 핀에 대한 CRUD 필요
16 |
17 | ##### 화면3. 채팅방 입장
18 |
19 | - 로드뷰 화면을 좌표 값으로 불러올 수 있음
20 | - 그러나 실시간으로 적용은 조금 어려울 수도?
21 |
22 | ### 오늘의 할일
23 |
24 | - API 실습
25 |
26 |
--------------------------------------------------------------------------------
/meeting-log/20191022.md:
--------------------------------------------------------------------------------
1 | # 2019년 10월 22일 화요일 회의록
2 |
3 | ### 오늘 할 일
4 |
5 | - 채팅 개발 팀(강민, 최승미)
6 |
7 | - Firebase Chatting 구조 파악 및 다이어그램 그리기
8 | - **금일 4시 세미나 예정**
9 | - 채팅방 추가 정보(좌표 값 등) 추가를 위한 데이터 구조 파악 및 제시
10 | - Firebase Realtime Database
11 |
12 | - Map API 개발 팀(곽빛나라, 채윤병)
13 |
14 | - Kakao Map API 기능 파악 및 사용할 기능 정리
15 |
16 |
17 |
18 | ### 변경사항
19 |
20 | - Spring Framwork 사용 중단
21 | - 회의를 계속 진행하여보니 Spring까지 사용할 필요가 없음
22 | - 프로젝트를 합치고 Vue.js + Firebase로 진행하기로 함.
23 |
24 |
--------------------------------------------------------------------------------
/meeting-log/20191023.md:
--------------------------------------------------------------------------------
1 | # 2019년 10월 23일 수요일 회의록
2 |
3 | ### 오늘 할 일
4 |
5 | - 채팅 개발 팀(강민, 최승미)
6 |
7 | - 데이터베이스 선정
8 | - RealTime DB
9 | - FIreStore
10 |
11 | - Map API 개발 팀(곽빛나라, 채윤병)
12 |
13 | - 화면조작 방지 기능 구현
14 |
15 |
16 |
17 | ### 변경사항
18 |
19 | - DB사용은 최종적으로 firebase Cloud FireStore로 선정
20 | - 그 이후 vuex기능과 연계하여 DB구조 설계
21 |
22 |
--------------------------------------------------------------------------------
/meeting-log/20191025.md:
--------------------------------------------------------------------------------
1 | # 2019년 10월 25일 금요일 회의록
2 |
3 | ### 오늘 할 일
4 |
5 | - 채팅방 정보 vuex에서 관리(최승미)
6 | - Firestore 데이터 업데이트 시 vuex에 바로 들어오는지 확인(최승미)
7 | - 마커 드래그 시 생기는 버그 수정(곽빛나라)
8 | - 마커 정보 vuex에서 관리(곽빛나라)
9 |
10 | - 로드뷰 정보 vuex에서 관리(채윤병)
11 |
12 |
13 |
14 | ### 변경사항
15 |
16 | - 분리된 vuex 통합
--------------------------------------------------------------------------------
/meeting-log/20191029.md:
--------------------------------------------------------------------------------
1 | # 2019년 10월 29일 화요일 회의록
2 |
3 | ### 오늘 할 일
4 |
5 | - Firebase Auth, User Session 정리(강민)
6 | - Firestore onSnapShot -> vuex 연동 테스트(최승미)
7 | - 로드뷰가 불가능한 영역 알림(채윤병, 곽빛나라)
8 | - 채팅방 component 추가(조우현)
9 |
10 |
--------------------------------------------------------------------------------
/meeting-log/20191031.md:
--------------------------------------------------------------------------------
1 | # 2019년 10월 31일 목요일 회의록
2 |
3 | ### 오늘 할 일
4 |
5 | - 마커 클릭시 채팅방 디테일 표현(곽빛나라)
6 | - Store watch 찾아보기(채윤병)
7 | - 채팅방 리스트 주시하기(최승미)
8 | - 닉네임 랜덤 생성 기능 만들기(강민)
9 | - 배포 준비(조우현)
10 |
11 |
12 |
13 | ### 변경사항
14 |
15 | - 마커 버튼 클릭시 방 이름 설정하는 창 추가
16 | - 방장이 아니면 로드뷰 이동 불가능하게 만들기
17 | - Kakao API 분리
--------------------------------------------------------------------------------
/meeting-log/20191101.md:
--------------------------------------------------------------------------------
1 | # 2019년 11월 1일 금요일 회의록
2 |
3 | ### 오늘 할 일
4 |
5 | - User Flow 정리(곽빛나라)
6 | - 채팅 API 구현 마무리(강민, 최승미)
7 | - Map API 팀과 합쳐 test 필요(조우현)
8 | - JSON형식으로 정보 표현(강민)
9 | - 로드뷰 화면 각도 제어 추가(채윤병)
10 | - 로드뷰 변화시 Firestore 정보 또한 변경(채윤병)
11 | - 채팅방 UI 변경(조우현)
12 | - 채팅방 입퇴장시 로그 출력(조우현)
13 |
14 |
15 |
16 | ### 변경사항
17 |
18 | - 방장이 채팅방을 나가면 채팅방이 삭제되는 것으로 변경
19 |
20 |
--------------------------------------------------------------------------------
/meeting-log/20191105.md:
--------------------------------------------------------------------------------
1 | # 2019년 11월 5일 화요일 회의록
2 |
3 | ### 오늘 할 일
4 |
5 | - Github, Readme 정리(조우현)
6 | - Architecture 정리, 시연 녹화(채윤병)
7 | - 회의록 정리(최승미)
8 |
9 | - UCC 만들기(곽빛나라)
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build"
8 | },
9 | "dependencies": {
10 | "core-js": "^3.1.2",
11 | "firebase": "^7.2.2",
12 | "sass": "^1.23.1",
13 | "sass-loader": "^8.0.0",
14 | "vue": "^2.6.10",
15 | "vue-router": "^3.0.6",
16 | "vuetify": "^2.1.0",
17 | "vuex": "^3.0.1"
18 | },
19 | "devDependencies": {
20 | "@vue/cli-plugin-babel": "^4.0.0",
21 | "@vue/cli-service": "^4.0.0",
22 | "vue-cli-plugin-vuetify": "^1.1.1",
23 | "vue-template-compiler": "^2.6.10",
24 | "vuetify-loader": "^1.3.0"
25 | },
26 | "postcss": {
27 | "plugins": {
28 | "autoprefixer": {}
29 | }
30 | },
31 | "browserslist": [
32 | "> 1%",
33 | "last 2 versions"
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | 방구석 여행
11 |
12 |
13 |
14 | We're sorry but client doesn't work properly without JavaScript enabled. Please enable it to continue.
15 |
16 |
17 |
18 |
23 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
28 |
51 |
--------------------------------------------------------------------------------
/src/api/firebaseApi.js:
--------------------------------------------------------------------------------
1 | import firebase from 'firebase/app';
2 | import 'firebase/firestore';
3 | import 'firebase/auth';
4 | import storage from '../utils/storage.js';
5 | import randomName from '../utils/randomName.js';
6 | import { HashMap } from '../utils/hashMap.js';
7 |
8 | // Firebase config
9 | const config = {
10 | apiKey: "AIzaSyDTUkBmOKTMBSyanz8aE9lVpTZ7N_arbT4",
11 | authDomain: "vue-fb-chat-test.firebaseapp.com",
12 | databaseURL: "https://vue-fb-chat-test.firebaseio.com",
13 | projectId: "vue-fb-chat-test",
14 | storageBucket: "vue-fb-chat-test.appspot.com",
15 | messagingSenderId: "294332183075",
16 | appId: "1:294332183075:web:b897ddecb6df108a2ef796"
17 | };
18 |
19 | firebase.initializeApp(config);
20 | const firestore = firebase.firestore();
21 |
22 | firebase.auth().onAuthStateChanged(function(user) {
23 | if (user) {
24 | //console.log("Log ON");
25 | } else {
26 | //console.log("Log Off");
27 | storage.logout();
28 | storage.clear();
29 | }
30 | });
31 |
32 | export default {
33 | /* Firebase Auth */
34 | async signup(email, password, nickname) {
35 | try {
36 | const result = await firebase.auth().createUserWithEmailAndPassword(email, password);
37 | await result.user.updateProfile({
38 | displayName: nickname
39 | });
40 | await this.addNicknameInPool(nickname);
41 | return result;
42 | } catch (error) {
43 | throw error;
44 | }
45 | },
46 | async loginWithEmail(email, password) {
47 | try {
48 | const result = await firebase.auth().signInWithEmailAndPassword(email, password);
49 | return result;
50 | } catch (error) {
51 | throw error;
52 | }
53 | },
54 | async loginWithGoogle(){
55 | let provider = new firebase.auth.GoogleAuthProvider();
56 |
57 | try {
58 | const result = await firebase.auth().signInWithPopup(provider);
59 | const confirm = await this.userNameConfirm(result.user.email);
60 | if(confirm == true){
61 | await result.user.updateProfile({
62 | displayName: await this.googleRandomizeName()
63 | });
64 | }
65 | return result;
66 | } catch (error) {
67 | throw error;
68 | }
69 | },
70 | async logout() {
71 | try {
72 | await firebase.auth().signOut();
73 | } catch (error) {
74 | throw error;
75 | }
76 | },
77 |
78 | /* Chat Room */
79 | async createChatRoom(chatRoom) {
80 | try {
81 | const docRef = await firestore.collection('chatRooms').add(chatRoom);
82 | await firestore.collection('chatRooms').doc(docRef.id).update({id: docRef.id});
83 | return docRef.id;
84 | } catch (error) {
85 | throw error;
86 | }
87 | },
88 | async setRoomLocation(roomId, changedInfo){
89 | await firestore.collection('chatRooms').doc(roomId).update({
90 | location: {
91 | latitude: changedInfo.latitude,
92 | longitude: changedInfo.longitude
93 | }
94 | });
95 | },
96 | async setViewPoint(roomId, changedViewPoint){
97 | await firestore.collection('chatRooms').doc(roomId).update({
98 | viewPoint: changedViewPoint
99 | });
100 | },
101 | async joinChatRoom(chatRoom, user) {
102 | await firestore.collection('chatRooms').doc(chatRoom.id).set(
103 | { guest: [
104 | user
105 | ]
106 | },
107 | { merge: true }
108 | );
109 | await this.sendMessage(chatRoom.id, {
110 | sender: "system",
111 | content: user.nickname + "님이 입장했어요!",
112 | createdAt: ""
113 | });
114 | },
115 | // 현재는 컬렉션으로 되어있어서 지우기 힘듬, array로 바꾼후 시도할 예정
116 | // deleteChat(chatRoom) {
117 | // firestore.collection('room').doc(chatRoom.id)
118 | // .collection('chatLog').doc(chatRoom.id).delete().then(function() {
119 | // console.log("Document successfully deleted!");
120 | // }).catch(function(error) {
121 | // console.error("Error removing document: ", error);
122 | // });
123 | // },
124 | async outChatRoom(chatRoom, user){
125 | await firestore.collection('chatRooms').doc(chatRoom.id).update({
126 | guest: firebase.firestore.FieldValue.arrayRemove({
127 | ...user
128 | })
129 | });
130 |
131 | await this.sendMessage(chatRoom.id, {
132 | sender: "system",
133 | content: user.nickname + "님이 퇴장했어요!",
134 | createdAt: ""
135 | });
136 | },
137 | breakRoom(chatRoom){
138 | firestore.collection('chatRooms').doc(chatRoom.id).delete().then(function() {
139 | console.log("Document successfully deleted!");
140 | }).catch(function(error) {
141 | console.error("Error removing document: ", error);
142 | });
143 | },
144 | fetchChatRooms(state) {
145 | return new Promise((resolve, reject) => {
146 | const unsubscribe =
147 | firestore.collection('chatRooms').onSnapshot((chatRoomsData) => {
148 | chatRoomsData.docChanges().forEach(async function(change) {
149 | const id = change.doc.id;
150 | const chatRoom = change.doc.data();
151 | if (change.type === "added") {
152 | state.dispatch('addChatRoom', {id, chatRoom});
153 | }
154 | if (change.type === "modified") {
155 | state.dispatch('editChatRoom', {id, chatRoom});
156 | }
157 | if (change.type === "removed") {
158 | state.dispatch('deleteChatRoom', id);
159 | }
160 | });
161 | });
162 | resolve(unsubscribe);
163 | });
164 | },
165 | fetchChatRoom(state, id) {
166 | return new Promise((resolve, reject) => {
167 | const unsubscribe =
168 | firestore.collection('chatRooms').doc(id).onSnapshot((chatRoomData) => {
169 | const id = chatRoomData.id;
170 | const chatRoom = chatRoomData.data();
171 | state.commit('updateOnlineChatRoom', chatRoom);
172 | state.commit('editChatRoom', {id, chatRoom});
173 | });
174 | resolve(unsubscribe);
175 | });
176 | },
177 |
178 | /* Chatting */
179 | sendMessage(id, message){
180 | message.createdAt = firebase.firestore.FieldValue.serverTimestamp();
181 | firestore.collection('room').doc(id).collection('chatLog').add(message);
182 | },
183 | fetchChatLog(id, _this){
184 | firestore.collection('room').doc(id).collection('chatLog').orderBy('createdAt').onSnapshot((querySnapshot) => {
185 | let chatLog = [];
186 | querySnapshot.forEach(doc => {
187 | let data = doc.data();
188 | data.id = doc.id;
189 | chatLog.push(data);
190 | });
191 | _this.chatLog = chatLog;
192 | });
193 | },
194 |
195 | /* User Nickname */
196 | async emailRandomizeName(){
197 | while(true){
198 | const randomNickname = randomName.randomizeName()
199 | let confirm = await this.nameConfirm(randomNickname)
200 | if(confirm == true){
201 | return randomNickname
202 | }
203 | }
204 | },
205 | async googleRandomizeName(){
206 | while(true){
207 | const randomNickname = randomName.randomizeName()
208 | let confirm = await this.nameConfirm(randomNickname)
209 | if(confirm == true){
210 | await this.addNicknameInPool(randomNickname)
211 | return randomNickname
212 | }
213 | }
214 | },
215 | async addNicknameInPool(randomNickname){
216 | await firestore.collection('nicknamePool').doc(randomNickname).set({
217 | nickname : randomNickname,
218 | user : firebase.auth().currentUser.email
219 | })
220 | .then( () => {
221 | // console.log("nickname setting complete")
222 | }).catch( (error) => {
223 | console.log(error)
224 | })
225 | },
226 | async nameConfirm(randomNickname){
227 | let confirm = false
228 | await firestore.collection('nicknamePool').doc(randomNickname).get().then(
229 | (doc) => {
230 | if(doc.exists){
231 | confirm = false
232 | } else{
233 | confirm = true
234 | }
235 | return confirm
236 | }
237 | )
238 | return confirm
239 | },
240 | async userNameConfirm(email){
241 | let confirm = false
242 | await firestore.collection('nicknamePool').where('user', '==', email).get().then(
243 | (doc) => {
244 | if(doc.empty){
245 | confirm = true
246 | }else{
247 | confirm = false
248 | }
249 | return confirm
250 | }
251 | )
252 | return confirm
253 | },
254 | }
255 |
--------------------------------------------------------------------------------
/src/api/kakaomapApi.js:
--------------------------------------------------------------------------------
1 | /* global kakao */
2 | export default {
3 | // RoadView API
4 | initRoadview (_this) {
5 | _this.roadview = new kakao.maps.Roadview(_this.roadviewContainer);
6 | const roadviewPosition = new kakao.maps.LatLng(
7 | _this.roomInfo.location.latitude,
8 | _this.roomInfo.location.longitude
9 | );
10 |
11 | // roadviewClient : 좌표로부터 로드뷰 파노ID를 가져올 로드뷰 helper객체
12 | // 특정 위치의 좌표와 가까운 로드뷰의 panoId를 추출하여 로드뷰를 띄운다. 반경 50미터 이내
13 | _this.roadviewClient = new kakao.maps.RoadviewClient();
14 | _this.roadviewClient.getNearestPanoId(roadviewPosition, 50, function (panoId) {
15 | _this.roadview.setPanoId(panoId, roadviewPosition); // panoId와 중심좌표를 통해 로드뷰 실행
16 | });
17 | _this.roadview.setViewpoint(_this.roomInfo.viewPoint);
18 |
19 | if(!_this.isHost) return;
20 |
21 | kakao.maps.event.addListener(_this.roadview, 'position_changed', () => {
22 | const changedLocation = _this.roadview.getPosition();
23 | const changedLocationInfo = {
24 | latitude: changedLocation.Ha,
25 | longitude: changedLocation.Ga
26 | }
27 | _this.$store.dispatch('setRoomLocation', changedLocationInfo);
28 | });
29 |
30 | kakao.maps.event.addListener(_this.roadview, 'viewpoint_changed', () => {
31 | const changedViewPoint = _this.roadview.getViewpoint();
32 | const viewPoint = {
33 | pan: changedViewPoint.pan,
34 | tilt: changedViewPoint.tilt,
35 | zoom: changedViewPoint.zoom
36 | }
37 |
38 | const threshold = 5; //이미지가 변화되어야 되는(각도가 변해야되는) 임계 값
39 | for(let i=0; i<72; i++){ //각도에 따라 변화되는 앵글 이미지의 수가 16개
40 | if(viewPoint.pan > (threshold * i) && viewPoint.pan < (threshold * (i + 1))){
41 | if(_this.viewpoint!==i){
42 | _this.viewpoint = i;
43 | _this.$store.dispatch('setViewPoint', viewPoint);
44 | }
45 | }
46 | }
47 | });
48 | },
49 | roadviewChangedEventHandler(_this, chatRoom) {
50 | _this.roadview.setViewpoint(chatRoom.viewPoint);
51 | const roadviewPosition = new kakao.maps.LatLng(
52 | chatRoom.location.latitude,
53 | chatRoom.location.longitude
54 | );
55 | _this.roadviewClient.getNearestPanoId(roadviewPosition, 50, function (panoId) {
56 | _this.roadview.setPanoId(panoId, roadviewPosition); // panoId와 중심좌표를 통해 로드뷰 실행
57 | });
58 | },
59 | // Map API
60 | drawMap (mapContainer, position) {
61 | return new Promise(resolve => {
62 | const mapOption = {
63 | // 지도 중심 좌표
64 | center: new kakao.maps.LatLng(position.coords.latitude, position.coords.longitude),
65 | // 지도의 확대 레벨
66 | level: 3,
67 | // 지도종류
68 | mapTypeId: kakao.maps.MapTypeId.ROADMAP
69 | };
70 |
71 | const map = new kakao.maps.Map(mapContainer, mapOption);
72 |
73 | resolve(map);
74 | })
75 | },
76 | createMarker(location){
77 | return new Promise(resolve => {
78 | const rvClient = new kakao.maps.RoadviewClient();
79 | const position = new kakao.maps.LatLng(location.latitude, location.longitude);
80 |
81 | // 로드뷰 가능지점인지 체크
82 | rvClient.getNearestPanoId(position, 50, function (panoId) {
83 | const marker = new kakao.maps.Marker({
84 | position,
85 | clickable: true
86 | });
87 | marker.setDraggable(false);
88 | resolve(marker);
89 | });
90 | });
91 | },
92 | createSelectionMarker(location){
93 | return new Promise(resolve => {
94 | const rvClient = new kakao.maps.RoadviewClient();
95 | const position = new kakao.maps.LatLng(location.Ha, location.Ga);
96 |
97 | const imageSrc = 'http://t1.daumcdn.net/localimg/localimages/07/mapapidoc/marker_red.png';
98 | // const imageSrc = '../images/car.png';
99 | const imageSize = new kakao.maps.Size(64, 64);
100 | const imageOption = {offset: new kakao.maps.Point(27, 69)};
101 |
102 | // 마커의 이미지정보를 가지고 있는 마커이미지를 생성합니다
103 | var markerImage = new kakao.maps.MarkerImage(imageSrc, imageSize, imageOption);
104 |
105 | // 로드뷰 가능지점인지 체크
106 | rvClient.getNearestPanoId(position, 50, function (panoId) {
107 | const marker = new kakao.maps.Marker({
108 | position,
109 | clickable: true,
110 | image: markerImage
111 | });
112 | marker.setDraggable(true);
113 | resolve(marker);
114 | });
115 | });
116 | },
117 | getAddress(location){
118 | return new Promise(resolve =>{
119 | const geocoder = new kakao.maps.services.Geocoder();
120 | const coord = new kakao.maps.LatLng(location.latitude, location.longitude);
121 | geocoder.coord2Address(coord.getLng(), coord.getLat(), (result, status) => {
122 | if (status === kakao.maps.services.Status.OK) {
123 | resolve(result[0].address.address_name);
124 | }
125 | });
126 | });
127 | }
128 | }
--------------------------------------------------------------------------------
/src/assets/another_resources.dummy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/src/assets/another_resources.dummy
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 | Artboard 46
2 |
--------------------------------------------------------------------------------
/src/components/BackButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 | mdi-chevron-double-left
10 |
11 | 나가기
12 |
16 |
17 | 🎈 채팅방을 나가시겠습니까?
18 |
19 |
20 |
25 | Yes
26 |
27 |
32 | No
33 |
34 |
35 |
36 |
37 |
38 |
39 |
61 |
--------------------------------------------------------------------------------
/src/components/Background.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
149 |
--------------------------------------------------------------------------------
/src/components/ChatRoom.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
15 |
16 |
17 |
22 |
26 | {{ msg.content }}
27 |
28 |
32 | {{ msg.content }}
33 |
35 | {{ msg.sender }}
36 |
37 |
39 | {{ timestampToTime(msg.createdAt) }}
40 |
41 |
42 |
43 |
44 |
45 |
64 |
65 |
66 |
67 |
137 |
328 |
--------------------------------------------------------------------------------
/src/components/ChatRoomDetail.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
{{ this.getOnlineChatRoom.title }}
5 | 🙋방장
6 | {{ this.getOnlineChatRoom.host.nickname }}
7 | 🏃♀️참가자
8 | {{ this.guests }}
9 | 🧭현재위치
10 |
11 | {{ this.setAddress }}
12 | {{ this.address }}
13 |
14 |
15 |
16 |
17 |
18 |
방구석 여행
19 | 웹으로 여행하자!
20 |
21 |
22 |
23 |
24 |
25 |
61 |
62 |
94 |
--------------------------------------------------------------------------------
/src/components/ChatRoomInfo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
19 |
20 |
34 |
--------------------------------------------------------------------------------
/src/components/ChatRoomList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
함께할 수 있는 여행
4 |
5 |
11 |
14 |
19 |
20 | {{ room.title }}
21 |
22 |
23 | {{ room.host.nickname }}
24 |
25 |
26 | 혼자 여행중입니다.
27 |
28 |
29 | {{ room.guest.length }} 명이 함께 여행중입니다.
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
55 |
56 |
118 |
--------------------------------------------------------------------------------
/src/components/LoginForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 😎 방구석 여행 😎
9 |
10 |
15 |
19 |
25 |
32 |
33 |
42 | 로그인
43 |
44 |
51 |
54 | mdi-google
55 |
56 | 구글로 로그인하기
57 |
58 |
65 | 메일로 회원가입
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
140 |
141 |
--------------------------------------------------------------------------------
/src/components/MapMarker.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
리스트할거야
6 |
7 |
8 |
마커 만들기
9 |
10 |
11 |
12 |
144 |
145 |
148 |
--------------------------------------------------------------------------------
/src/components/RoadView.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 | 🚗 로드뷰 이동은 방장만 가능합니다.
10 |
11 |
12 |
17 | OK
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
88 |
--------------------------------------------------------------------------------
/src/components/SignUpForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 | 회원가입
9 |
10 |
15 |
19 |
25 |
32 |
33 |
34 |
39 |
42 | 닉네임 : "{{nickname}}"
43 |
44 |
47 | 닉네임 : 설정 중......
48 |
49 |
50 |
56 |
57 | mdi-refresh
58 |
59 | 바꾸기
60 |
61 |
62 |
63 |
68 |
69 |
78 | 가입
79 |
80 |
81 |
82 |
89 | 취소
90 |
91 |
92 |
93 |
94 |
95 |
96 |
159 |
160 |
--------------------------------------------------------------------------------
/src/fonts/yg-jalnan.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/src/fonts/yg-jalnan.woff
--------------------------------------------------------------------------------
/src/images/cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/src/images/cancel.png
--------------------------------------------------------------------------------
/src/images/car.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/src/images/car.png
--------------------------------------------------------------------------------
/src/images/compass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/src/images/compass.png
--------------------------------------------------------------------------------
/src/images/globe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/src/images/globe.png
--------------------------------------------------------------------------------
/src/images/location.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/src/images/location.png
--------------------------------------------------------------------------------
/src/images/locked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/src/images/locked.png
--------------------------------------------------------------------------------
/src/images/logo1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/src/images/logo1.png
--------------------------------------------------------------------------------
/src/images/power.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/src/images/power.png
--------------------------------------------------------------------------------
/src/images/scooter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/src/images/scooter.png
--------------------------------------------------------------------------------
/src/images/unlocked.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/src/images/unlocked.png
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import {router} from '@/routes';
4 | import store from '@/store';
5 | import vuetify from '@/plugins/vuetify';
6 |
7 | Vue.config.productionTip = false
8 |
9 | new Vue({
10 | router,
11 | store,
12 | vuetify,
13 | render: h => h(App)
14 | }).$mount('#app')
15 |
--------------------------------------------------------------------------------
/src/plugins/vuetify.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuetify from 'vuetify/lib';
3 |
4 | Vue.use(Vuetify);
5 |
6 | export default new Vuetify({
7 | icons: {
8 | iconfont: 'mdi',
9 | },
10 | });
11 |
--------------------------------------------------------------------------------
/src/routes/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 | import store from '@/store';
4 | import Auth from '@/views/Auth.vue'
5 | import Map from '@/views/Map.vue'
6 | import Travel from '@/views/Travel.vue'
7 | import LoginForm from '@/components/LoginForm.vue';
8 | import SignUpForm from '@/components/SignUpForm.vue';
9 |
10 | Vue.use(VueRouter)
11 |
12 | const routes = [
13 | {
14 | path: '/auth',
15 | component: Auth,
16 | children: [
17 | {
18 | name: 'login',
19 | path: 'login',
20 | component: LoginForm,
21 | meta: {
22 | authRequired: false
23 | }
24 | },
25 | {
26 | name: 'signup',
27 | path: 'signup',
28 | component: SignUpForm,
29 | meta: {
30 | authRequired: false
31 | }
32 | }
33 | ]
34 | },
35 | {
36 | path: '/',
37 | name: 'map',
38 | component: Map,
39 | meta: {
40 | authRequired: true
41 | }
42 | },
43 | {
44 | path: '/travel',
45 | name: 'travel',
46 | component: Travel,
47 | meta: {
48 | authRequired: true
49 | }
50 | }
51 | ];
52 |
53 | export const router = new VueRouter({
54 | mode: 'history',
55 | base: process.env.BASE_URL,
56 | routes: routes
57 | });
58 |
59 | router.beforeEach(function (to, from, next) {
60 | if(to.matched.some(function (routeInfo) {
61 | return routeInfo.meta.authRequired;
62 | })) {
63 | // 로그인이 필요한 페이지
64 | const loginState = store.getters.getLoginState;
65 | if(loginState){
66 | next();
67 | } else {
68 | next('auth/login');
69 | }
70 | } else {
71 | // 로그인이 필요없는 페이지
72 | const loginState = store.getters.getLoginState;
73 | if(loginState){
74 | next(from);
75 | } else {
76 | next();
77 | }
78 | }
79 | });
80 |
--------------------------------------------------------------------------------
/src/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import Vuex from 'vuex';
3 |
4 | // Store에서는 '@'로 src 접근이 불가하다.
5 | import firebaseApi from '../api/firebaseApi.js';
6 | import storage from '../utils/storage.js';
7 | import { HashMap } from '../utils/hashMap.js';
8 | import kakaomapApi from '../api/kakaomapApi.js';
9 |
10 |
11 | Vue.use(Vuex);
12 |
13 | export default new Vuex.Store({
14 | state: {
15 | nicknameByDraw: '',
16 | loginState: storage.fetchLoginState(),
17 | loginUser: storage.fetchLoginUser(),
18 |
19 | chatRooms: new HashMap(),
20 | markers: new HashMap(),
21 | selectedId: null,
22 | onlineChatRoom: storage.fetchOnlineChatRoom(),
23 |
24 | chatMessages: null
25 | },
26 | getters: {
27 | // User Auth Getters
28 | getLoginState(state) {
29 | return state.loginState;
30 | },
31 | getLoginUser(state) {
32 | return state.loginUser;
33 | },
34 | getNicknameByDraw(state){
35 | return state.nicknameByDraw;
36 | },
37 |
38 | // Chat Room Getters
39 | getChatRooms(state) {
40 | return state.chatRooms;
41 | },
42 | getMarkers(state) {
43 | return state.markers;
44 | },
45 | getMarker(state, id){
46 | // id: marker id (=chatRoom id)
47 | return state.markers.get(id);
48 | },
49 |
50 | // TODO: 제거 후 getRoomIdList가 필요한 부분에 직접 구현하기
51 | getRoomIdList(state) {
52 | return state.chatRooms.keys();
53 | },
54 |
55 |
56 | getOnlineChatRoom(state) {
57 | return state.onlineChatRoom;
58 | },
59 | getSelectedId(state) {
60 | return state.selectedId;
61 | }
62 | },
63 | mutations: {
64 | // User Nickname Mutations
65 | setNicknameByDraw(state, payload){
66 | state.nicknameByDraw = payload;
67 | },
68 | // User Auth Mutations
69 | updateLoginState(state, payload){
70 | state.loginState = payload;
71 | },
72 | updateLoginUser(state, payload){
73 | state.loginUser = payload;
74 | },
75 | // Room Mutations
76 | addChatRoom(state, payload) {
77 | // payload: {
78 | // id: chatRoomId,
79 | // chatRoom: chatRoom obj
80 | // }
81 | const map = state.chatRooms.getAll();
82 | const addedMap = { ...map, [payload.id]: payload.chatRoom};
83 | state.chatRooms.map = addedMap;
84 | },
85 | editChatRoom(state, payload) {
86 | // payload: {
87 | // id: chatRoomId,
88 | // chatRoom: chatRoom obj
89 | // }
90 | state.chatRooms.put(payload.id, payload.chatRoom);
91 | },
92 | deleteChatRoom(state, id) {
93 | state.chatRooms.remove(id);
94 | },
95 |
96 | // Marker Mutations
97 | addMarker(state, marker){
98 | // payload: {
99 | // id: chatRoomid,
100 | // marker: marker object
101 | // }
102 | const origin = state.markers.getAll();
103 | const updated = {...origin, [marker.getTitle()]: marker};
104 | state.markers.map = updated;
105 | },
106 | deleteMarker(state, id){
107 | // id: marker id (= chatRoom id)
108 | state.markers.remove(id);
109 | },
110 | editMarker(state, marker) {
111 | // marker: marker object
112 | state.markers.put(marker.getTitle(), marker);
113 | },
114 | updateOnlineChatRoom(state, chatRoom){
115 | state.onlineChatRoom = chatRoom;
116 | },
117 | updateSelectedId(state, id){
118 | state.selectedId = id;
119 | state.onlineChatRoom = state.chatRooms.get(id);
120 | }
121 | },
122 | actions: {
123 | // User Auth Actions
124 | async ramdomNickname(state){
125 | try{
126 | const result = await firebaseApi.emailRandomizeName();
127 | state.commit('setNicknameByDraw',result);
128 |
129 | return result;
130 | } catch(error) {
131 | throw error;
132 | }
133 | },
134 | async signup(state, payload){
135 | try {
136 | const result = await firebaseApi.signup(payload.email, payload.password, payload.nickname);
137 | const loginUser = {
138 | uid: result.user.uid,
139 | email: result.user.email,
140 | nickname: result.user.displayName
141 | };
142 | state.commit('updateLoginUser', loginUser);
143 | state.commit('updateLoginState', true);
144 | storage.login(loginUser);
145 | return;
146 | } catch (error) {
147 | throw error;
148 | }
149 | },
150 | async loginWithEmail(state, payload){
151 | try {
152 | const result = await firebaseApi.loginWithEmail(payload.email, payload.password);
153 | const loginUser = {
154 | uid: result.user.uid,
155 | email: result.user.email,
156 | nickname: result.user.displayName
157 | };
158 | state.commit('updateLoginUser', loginUser);
159 | state.commit('updateLoginState', true);
160 | storage.login(loginUser);
161 | return;
162 | } catch (error) {
163 | throw error;
164 | }
165 | },
166 | async loginWithGoogle(state){
167 | try {
168 | const result = await firebaseApi.loginWithGoogle();
169 | const loginUser = {
170 | uid: result.user.uid,
171 | email: result.user.email,
172 | nickname: result.user.displayName
173 | };
174 | state.commit('updateLoginUser', loginUser);
175 | state.commit('updateLoginState', true);
176 | storage.login(loginUser);
177 | return;
178 | } catch (error) {
179 | throw error;
180 | }
181 | },
182 | async logout(state){
183 | try {
184 | await firebaseApi.logout();
185 | const loginUser = null;
186 | state.commit('updateLoginUser', loginUser);
187 | state.commit('updateLoginState', false);
188 | storage.logout();
189 | return;
190 | } catch (error) {
191 | throw error;
192 | }
193 | },
194 | async fetchChatRooms(state){
195 | try {
196 | const unsubscribe = await firebaseApi.fetchChatRooms(state);
197 | return unsubscribe;
198 | } catch (error) {
199 |
200 | }
201 | },
202 | async fetchChatRoom(state, id){
203 | try {
204 | const unsubscribe = await firebaseApi.fetchChatRoom(state, id);
205 | return unsubscribe;
206 | } catch (error) {
207 |
208 | }
209 | },
210 | async addChatRoom(state, payload) {
211 | // payload: {
212 | // id: chatRoomId,
213 | // chatRoom: chatRoom obj
214 | // }
215 | await state.dispatch('addMarker', payload);
216 | await state.commit('addChatRoom', payload);
217 | },
218 |
219 | async deleteChatRoom(state, id) {
220 | await state.commit('deleteChatRoom', id);
221 | await state.dispatch('deleteMarker', id);
222 | },
223 |
224 | async editChatRoom(state, payload) {
225 | // payload: {
226 | // id: chatRoomId,
227 | // chatRoom: chatRoom obj
228 | // }
229 | await state.commit('editChatRoom', payload);
230 | await state.dispatch('editMarker', payload);
231 | },
232 |
233 | //Marker Actions
234 | async addMarker(state, payload){
235 | // payload: {
236 | // id: chatRoomid
237 | // chatRoom: chatRoom object
238 | // }
239 |
240 | try {
241 | const marker = await kakaomapApi.createMarker(payload.chatRoom.location);
242 | marker.setTitle(payload.id);
243 | state.commit('addMarker', marker);
244 | } catch (error) {
245 |
246 | }
247 | },
248 | async deleteMarker(state, id){
249 | // id: marker id (= chatRoom id)
250 |
251 | try {
252 | state.commit('deleteMarker', id);
253 | } catch (error) {
254 |
255 | }
256 | },
257 | async editMarker(state, payload){
258 | // payload: {
259 | // id:
260 | // chatRoom: chatRoom object
261 | // }
262 | try {
263 | const latitude = payload.chatRoom.location.latitude;
264 | const longitude = payload.chatRoom.location.longitude;
265 | const marker = state.getters.getMarkers.get(payload.id);
266 | marker.setPosition(new kakao.maps.LatLng(latitude, longitude));
267 | state.commit('editMarker', marker);
268 | } catch (error) {
269 |
270 | }
271 | },
272 |
273 | async setRoomLocation({getters},changedInfo){
274 | await firebaseApi.setRoomLocation(getters.getSelectedId, changedInfo);
275 | },
276 | async setViewPoint(state, changedViewPoint) {
277 | await firebaseApi.setViewPoint(state.getters.getSelectedId, changedViewPoint);
278 | },
279 | // Chat Room Actions
280 | async createChatRoom(state, payload) {
281 | // payload: {
282 | // title:
283 | // location:{
284 | // latitude:
285 | // longitude:
286 | // },
287 | // viewPoint:
288 | // }
289 | const chatRoom = {
290 | id: '',
291 | title: payload.title,
292 | location: {
293 | latitude: payload.location.latitude,
294 | longitude: payload.location.longitude
295 | },
296 | viewPoint: payload.viewPoint,
297 | host: state.getters.getLoginUser,
298 | guest: []
299 | }
300 | // Upload Firestore
301 | const id = await firebaseApi.createChatRoom(chatRoom);
302 | state.commit('updateSelectedId', id);
303 | state.commit('updateOnlineChatRoom', state.getters.getChatRooms.get(id));
304 | return;
305 | }
306 | }
307 | });
308 |
--------------------------------------------------------------------------------
/src/utils/hashMap.js:
--------------------------------------------------------------------------------
1 | const HashMap = function(){
2 | this.map = new Array();
3 | };
4 |
5 | HashMap.prototype = {
6 | put: function(key, value){
7 | this.map[key] = value;
8 | },
9 | get: function(key){
10 | return this.map[key];
11 | },
12 | getAll: function(){
13 | return this.map;
14 | },
15 | remove: function (key) {
16 | delete this.map[key];
17 | },
18 | clear: function(){
19 | this.map = new Array();
20 | },
21 | keys: function(){
22 | const keys = new Array();
23 | for(let i in this.map){
24 | keys.push(i);
25 | }
26 | return keys;
27 | },
28 | values : function(){
29 | const values = new Array();
30 | for(let prop in this.map){
31 | values.push(this.map[prop]);
32 | }
33 | return values;
34 | },
35 | containsKey: function (key) {
36 | return key in this.map;
37 | },
38 | length: function(){
39 | return this.map.length;
40 | }
41 | }
42 |
43 | export { HashMap };
--------------------------------------------------------------------------------
/src/utils/nicknamedata.js:
--------------------------------------------------------------------------------
1 | export default {
2 | adjective : ['지혜로운', '용맹스러운', '탐구적인', '전설적인', '피의', '밀림의',
3 | '팀의 체력을 책임지는', '팔랑귀를 가진', '사진 잘 찍는', '화염의', '냉기의',
4 | '역병의', '자연의', '피에 굶주린', '기다릴 줄 아는', '황혼의', '죽음의',
5 | '여명의', '산산조각난', '평온의', '탐욕스러운', '꿈이 많은', '배부른',
6 | '용맹한', '명예로운', '영광의', '영원한', '굳건한', '한강에 도착한',
7 | '황사바람의', '불굴의', '허기진', '붉은', '푸른', '골방의', '12시의',
8 | '술에 취한', '고운', '명상하는', '차가운 도시의', '열정적인', '도전적인',
9 | '인스타하는', '페북하는', '인증된', '폰 없는', '모험의', '월요일의',
10 | '화요일의', '수요일의', '목요일의', '금요일의', '토요일의', '일요일의',
11 | '넓은 시야의', '뜨거운', '애국적인', '코딩 잘 하는', '문제적인', '땀 많은',
12 | '마음의', '격려하는', '만기제대의', '복학한', '깨끗한', '무서운', '계획적인',
13 | '잘 먹는', '잘 자는', '운동 잘하는', '윤리적인', '귀여운', '사랑스러운',
14 | '민머리의', '복고풍의', '밥 짓는', '작업하는', '자유의', '군단의', '공허의',
15 | '파도의', '인성의', '자소서쓰는', '잘 자는', '물먹는', '독한', '끈질긴',
16 | '비용 절감의', '정신나간', '맛이 간', '웃긴', '놀래키는', '천국의', '지옥의',
17 | '파리의', '서울의', '단호한', '바람의', '복면 쓴', '대박난', '별빛의',
18 | '소름돋는', '기막힌', '놀라운', '철두철미한', '특이한', '이상한',
19 | '주황색의', '바람잡는', '방금 도착한', '빠른', '느린', '가벼운',
20 | '무거운', '기적의', '광속의', '뚝배기의', '튤립의', '신원 미상의',
21 | '알 수 없는', '잘 모르는', '외국물 먹은', '도를 아는', '불멸의', '규칙적인',
22 | '달리는', '뛰고 있는', '씻고 있는', '꿀 먹는', '명상중인', '오늘만 사는',
23 | '굳건한', '우월한', '경이로운', '뜨거운 영혼의', '공격적인', '수비적인',
24 | '하얀 마음의', '현실적인', '동화에 나오는', '실화에 나오는', '기침하는',
25 | '지리산의', '서울의', '부산의', '대구의', '대전의', '전주의', '순천의', '광주의',
26 | '인천의', '울산의', '포항의', '남원의', '백두산의', '속리산의', '한라산의', '제주의',
27 | '남산의', '삽질하는', '코딩하는', '농구하는', '축구하는', '야구하는', '포근한',
28 | '혼자있는', '라면먹는', '취직한', '붉은 수염의', '푸른 화염의', '노란 빛의', '운전 잘하는',
29 | '고기먹는', '글 쓰는', '춤 잘 추는', '맨탈 강한', '반가운', '귀여운 척하는', '귀신 잡는', '둥근',
30 | '네모난', '영화 좋아하는', '길 잘 아는', '아름다운', '죽지 않는', '땀 흘리는', '붕대 감은',
31 | '재주 많은', '열나는', '영리한', '총명한', '인심좋은', '키가 큰', '키가 작은',
32 | '묻고 떠블로 가는', '투덜거리는', '엄격한', '자상한', '웃는', '모르는 게 없는','거침없는',
33 | '겁나게 멋진', '허벌나게 멋진', '귀 얇은', '보통의', '궁금한 게 많은', '인사 잘하는', '최강의',
34 | '길 찾는', '길 잃은', '우아한', '비밀의', '사랑의', '완벽한', '웃기게 말 잘하는', '예의바른',
35 | '어여쁜', '진실된', '멋진', '눈물없는', '잘 노는', '박학다식한', '비싼', '엘레강스한', '사륜안의',
36 | '분리수거하는', '문서화 잘하는', '침묵의', '분전하는', '예수님을 섬기는', '하느님을 섬기는',
37 | '뭉쳐야 산다는', '화장실가고픈', '급한', '시험 잘 치는', '우월한 DNA의', '파도를 즐기는', '게임하는',
38 | '새로운', '신비한', '경청하는', '만렙의', '스펙 좋은', '천천히 걷는', '개성있는'],
39 |
40 |
41 | noun : ['모험가', '탐험가', '애국자', '리더', '방랑자', '인간', '뿌뿌뿡',
42 | '자동차', '반도체', '공장장', '공대장', '살모사', '강아지', '고양이',
43 | '낚시꾼', '등반가', '마에스트로', '돌격장수', '아기장수', '스프링',
44 | '전문가', '한국인', '중국인', '일본인', '까르보나라', '초콜렛', '후레이크',
45 | '라면', '냅킨', '은행원' ,'김흥국', '유재석', '아이', '트럼프', '쓰랄',
46 | '제이나', '하마', '기린', '사자', '맹수', '팽수', '뽀로로', '크롱', '사장님',
47 | '회장님', '후라이드', '무' ,'양파', '당근', '대파' ,'파', '톨스토이' ,'흡혈귀',
48 | '김수로', '잡스', '아이폰' ,'갤럭시', '드래곤', '김밥', '떡볶이', '순대', '퀸',
49 | '젠켄스', '자바', 'C언어', '장인', '굴삭기', '철마', '원빈', '장동건' ,'송혜교',
50 | '현빈', '사랑꾼', '성기사', '전사', '사냥꾼', '수도사', '도적', '주술사', '마법사',
51 | '사제', '치유사', '피카츄', '4달라', '심영', '워태커' ,'김두한', '마담', '파이터',
52 | '아들', '딸', '김태희', '환경운동가', '술꾼', '타짜', '곽철용', '이소룡', '미성년자',
53 | '짱구', '짱아', '철수', '훈이', '유리', '맹구', '흰둥이', '진돗개', '백구', '순교자',
54 | '볼트', '조조', '유비', '손권', '명예회장', '대통령', '형', '동생', '첩자', '도둑',
55 | '조커', '배트맨', '아이언맨', '슈퍼맨', '원더우먼', '스티브', '광신자', '옥수수',
56 | '감자', '토마토', '고구마', '상추', '배추', '존재', '외계인', '둘리', '또치',
57 | '마이콜', '희동이', '술꾼', '이소룡', '이연걸', '성룡', '한조', '겐지', '트레이서',
58 | '모박불', '용광로', '과학자', '수학자', '연예인', '건물주', '짝귀', '아귀', '고니',
59 | '티모', '가렌', '앨리스', '페이커', '랩퍼', '비룡', '파이프', '황새', '참새', '고라니',
60 | '귀신', '해병대', '베컴', '삼겹살', '산악인', '자연인', '아기', '대장군', '미라',
61 | '강호동', '유재석', '아이돌', '예술가', '대길', '약초꾼', '관우', '장비', '여포', '깍두기',
62 | '살쾡이', '손오공', '삼장법사', '저팔계', '사오정', '항우', '죽창', '마인 부우', '배지터',
63 | '크리링', '야무치', '바람잡이', '앞잡이', '정치가', '무사', '궁수', '저격수', '장고', '공격수',
64 | '스트라이커', '수비수', '우주인', '고추잠자리', '매미', '허수아비', '황소', '나무늘보', '책벌레',
65 | '포병', '보병', '통신병', '공병', '호랑이', '코끼리', '봉황', '청룡', '주작', '현무', '라이츄',
66 | '피츄', '구구', '토게피', '악어', '뿡뿡이', '천리마', '복슬이', '삽살개', '백로', '거북이',
67 | '형님', '용병', '지휘관', '이과생', '문과생', '보노보노', '스펀지밥', '뚱이', '선수',
68 | '군인', '밥장수', '파수꾼', '나무꾼', '선녀', '이리', '여우', '늑대', '토끼', '자라',
69 | '오골계', '장닭', '남자', '여자', '현모양처', '오리', '거위', '바다코끼리', '네티즌', '시민',
70 | '거인', '소인', '태양인', '태음인', '소양인', '소음인', '붉개미', '각시탈', '하회탈', '이브이',
71 | '두돈반', '부두술사', '강령술사', '기술자', '별자리', '사관생도', '복분자', '구황작물',
72 | '콜럼버스', '소년', '소녀', '만화가', '마술사', '예비군', '주임', '대리', '과장', '차장',
73 | '행보관', '주임원사', '대대장', '중대장', '소대장', '다스베이더', '제다이', '추노꾼']
74 | }
--------------------------------------------------------------------------------
/src/utils/randomName.js:
--------------------------------------------------------------------------------
1 | import name from './nicknamedata.js'
2 |
3 | export default {
4 | randomizeName() {
5 | const name1 = name.adjective[Math.floor(Math.random() * name.adjective.length)]
6 | const name2 = name.noun[Math.floor(Math.random() * name.noun.length)]
7 | const randomNickname = name1 + " " + name2
8 | return randomNickname
9 | }
10 | }
--------------------------------------------------------------------------------
/src/utils/storage.js:
--------------------------------------------------------------------------------
1 | export default {
2 | fetchOnlineChatRoom() {
3 | const onlineChatRoom = sessionStorage.getItem('onlineChatRoom');
4 | if(onlineChatRoom){
5 | return JSON.parse(onlineChatRoom);
6 | } else {
7 | return null;
8 | }
9 | },
10 | join(chatRoom) {
11 | sessionStorage.setItem('onlineChatRoom', JSON.stringify(chatRoom));
12 | },
13 | disJoin() {
14 | sessionStorage.setItem('onlineChatRoom', null);
15 | },
16 | fetchLoginState() {
17 | const loginState = sessionStorage.getItem('loginState');
18 | if(loginState === 'true') return true;
19 | else return false;
20 | },
21 | fetchLoginUser() {
22 | return JSON.parse(sessionStorage.getItem('loginUser'));
23 | },
24 | login(loginUser) {
25 | sessionStorage.setItem('loginUser', JSON.stringify(loginUser));
26 | sessionStorage.setItem('loginState', true);
27 | },
28 | logout() {
29 | sessionStorage.setItem('loginUser', null);
30 | sessionStorage.setItem('loginState', false);
31 | },
32 | clear() {
33 | if(sessionStorage.length > 0) {
34 | for(let i = 0 ; i < sessionStorage.length ; ++i){
35 | const name = sessionStorage.key(i);
36 | if(name === 'loginUser' || name === 'loginState' || name === 'selectedId') continue;
37 | sessionStorage.removeItem(name);
38 | }
39 | }
40 | }
41 | };
--------------------------------------------------------------------------------
/src/utils/time.js:
--------------------------------------------------------------------------------
1 | export default {
2 | getDate(timestamp) {
3 | const date = new Date(timestamp);
4 | const week = ['일', '월', '화', '수', '목', '금', '토'];
5 | const current = {
6 | date: `${date.getMonth()}월 ${date.getDate()}일 ${week[date.getDay()]}요일`,
7 | time: `${date.getHours()} : ${date.getMinutes()}`,
8 | detailTime: `${date.getHours()} : ${date.getMinutes()} : ${date.getSeconds()}`
9 | };
10 | return current;
11 | },
12 | };
--------------------------------------------------------------------------------
/src/views/Auth.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
36 |
37 |
--------------------------------------------------------------------------------
/src/views/Map.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 🎈 채팅방에 참여하시겠습니까?
9 |
10 |
11 |
16 | Yes
17 |
18 |
23 | No
24 |
25 |
26 |
27 |
28 |
29 |
30 | Open Dialog
31 |
32 |
33 | 🚀 채팅방 이름을 입력하세요.
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | Cancel
46 | Create
47 |
48 |
49 |
50 |
51 |
57 |
63 |
68 | {{this.setAddress}}
69 |
70 |
71 |
72 |
234 |
235 |
--------------------------------------------------------------------------------
/src/views/Travel.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
35 |
--------------------------------------------------------------------------------
/wiki/ESlint.md:
--------------------------------------------------------------------------------
1 | ### ESLint
2 |
3 | - 마지막 `;` 의 사용
4 |
5 | - trailing comma `,` 의 사용
6 |
7 | ```js
8 | ...
9 | components: {
10 | '컴포넌트 이름': 컴포넌트 내용,
11 | }
12 | ...
13 | ```
14 |
15 | - 등...
16 |
17 |
18 |
19 | ### ESLint disable
20 |
21 | - 컴포넌트 내 주석 처리
22 |
23 | ```js
24 | ...
25 | /* eslint-disable */
26 | ...
27 | ```
28 |
29 | - 파일 생성
30 |
31 | - `src/vue.config.js` 생성
32 |
33 | ```js
34 | module.exports = {
35 | lintOnSave: false
36 | }
37 | ```
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/wiki/JSON.md:
--------------------------------------------------------------------------------
1 | # JSON
2 |
3 | > 2019-11-01 (작성자: 강민)
4 |
5 |
6 |
7 | ### preview
8 |
9 | vue와 firebase, 그리고 javascript를 사용하면서 데이터 전송을 실시했다. 하지만 파이어베이스에 객체 데이터를 넣으려 할 때 형식이 안맞아 오류가 생겼다. 그래서 이 객체를 JSON으로 고쳐셔 보내는 생각을 하였다.
10 |
11 |
12 |
13 | ### JSON
14 |
15 | JSON은 JavaScript Object Notation의 줄인말로, 키-값 쌍으로 이루어진 데이터 오브젝트를 전달하기 위해 인간이 읽을 수 있는 텍스트를 사용할 수 있는 표준 포맷이다. 아래는 JSON형태로 이루어진 데이터 포맷 예제이다.
16 |
17 | ```json
18 | {
19 | "이름": "홍길동",
20 | "나이": 25,
21 | "성별": "여",
22 | "주소": "서울특별시 양천구 목동",
23 | "특기": ["농구", "도술"],
24 | "가족관계": {"#": 2, "아버지": "홍판서", "어머니": "춘섬"},
25 | "회사": "경기 수원시 팔달구 우만동"
26 | }
27 | ```
28 |
29 |
30 |
31 | - 장점: JSON은 텍스트로 이루어져 있으므로, 사람과 기계 모두 읽고 쓰기 쉽다.
32 |
33 | - 단점: 문법 오류에 민감하다. 사소한 부분이라도 잘 못되면, 전체가 망가진다. 그리고 주석 지원이 불가.
34 |
35 |
36 |
37 | ### JSON.stringify() & JSON.parse()
38 |
39 | json은 텍스트로 구조화되어 이루어진 객체이다. 이를 텍스트화 (스트링화) 할 수도 있고, 역으로 JSON에 맞춰서 이루어진 텍스트를 객체화할 수 있다.
40 |
41 | - JSON.stringify()
42 | - JSON객체를 String객체로 변환
43 | - JSON.parse()
44 | - JSON형태로 된 String객체를 JSON객체로 변환
45 |
46 |
47 |
48 | ***여기서 주의할 점***
49 |
50 | - ***JSON.stringify()시, JSON객체에 담겨진 method는 삭제가 된다.***
51 | - ***이를 JSON.parse()로 되살려도 원래 객체의 method를 사용못하게 된다.***
52 |
53 |
54 |
55 | 위 주의점 때문에 firebase에 넣는 완전한 객체를 사용할 수 없었다. 이는 명심해두자.
--------------------------------------------------------------------------------
/wiki/about_MVVMpattern.md:
--------------------------------------------------------------------------------
1 | ## MVVM 패턴
2 |
3 | > 2019-11-03 작성자 곽빛나라
4 |
5 | 
6 |
7 |
8 |
9 | 그림 출처 : https://wnstkdyu.github.io
10 |
11 |
12 |
13 | ##### MVVM 패턴 정의
14 |
15 | MVVM은 `View` - `ViewModel` - `Model` 을 이용해 각각의 역할을 분리하여 가독성과 재사용성을 높인 디자인 패턴이다.
16 |
17 | * View : 사용자의 눈에 보이는 인터페이스(디자이너가 만듦), 유저 인터랙션을 받아 ViewModel에게 명령을 내린다.
18 |
19 | * Model : 데이터 처리, 즉 데이터베이스와 통신, ViewModel이 소유하고 갱신하며 가공하여 View에 표시한다.
20 |
21 | * ViewModel : View와는 Binding, Command로 연결하고, Model과는 데이터를 주고 받는 역할,
22 |
23 | Model을 가공해 View에 전달하거나, 유저 인터랙션이 올 경우 그에 따른 작업을 수행한다. 작업이 끝난 후 View를 이에 맞춰 바꿔줘야 하는데 Data Binding을 통해 이를 달성한다.
24 |
25 |
26 |
27 | ##### MVVM 패턴의 처리 플로우
28 |
29 | 1. View에 요청이 들어오면 Command를 통해 ViewModel로 보낸다.
30 |
31 | 2. ViewModel은 Model에 데이터를 요청한다. 그리고 Model은 데이터를 응답한다.
32 |
33 | 3. 이를 받은 ViewModel은 필요한만큼 가공한다.
34 |
35 | 4. View는 ViewModel과의 Data Binding을 통해 데이터를 자동으로 갱신한다.
36 |
37 | 
38 |
39 | 그림 출처 : http://blog.yena.io/
40 |
41 | View에서 ViewModel로, ViewModel에서 Model로 작업을 처리하며, View에서 Model을 직접 참조하지 않음
42 |
43 | 대신, View에서 ViewModel을 관찰하며 data의 변경사항을 감지한다.
44 |
45 | ##### MVC와의 차이점
46 |
47 | * MVVM은 MVC의 단점인 View와 Model의 의존성이 없음
48 | * Model, View는 동일하나, Controller가 ViewModel로 바뀐 것이라고 보면 된다. 그리고 이 ViewModel은 UI단에 위치한다.(어디는 UI레이어 아래에 위차한다던데 흠)
49 |
50 | ##### MVVM의 큰 특징
51 |
52 | * Command와 Data Binding을 통해 View의 의존성을 끊어버렸고 그럼으로써 View와 Model의 분리가 이루어짐
53 |
54 | * Data Binding :
55 |
56 | * Model과 UI 요소 간의 싱크를 맞춰주는 것, 이 패턴을 통해 View와 로직이 분리되어 있어도 한쪽이 바뀌면 다른 쪽도 업데이트가 이루어져 데이터의 일관성을 유지할 수 있다.
57 | * MVVM패턴에서는 작업 흐름 제어보다는 view와 ViewModel의 상태를 동기화 해줄 구성요소가 필요함, 이 것이 data binding
58 | * 데이터 바인딩으로 인해 뷰모델 상태가 변경되면 뷰의 상태가 함께 변경된다. (그 역도 보장이 된다.)
59 | * MVVM 패턴은 data binding에 의존함
60 |
61 | * Command : 쉽게 말하면 해당 이벤트에 대한 명령
62 |
63 | View에 입력이 들어오면 Command 패턴을 통해 ViewModel에 명령을 내리게 되고, ViewModel은 Model에게 필요한 data를 요청한다.
64 |
65 | Command를 통하여 Behavior를 View의 특정한 ViewAction(Event)와 연결할 수 있다.
66 |
67 | ##### MVVM 장점
68 |
69 | 1. View가 Data를 실시간으로 관찰한다.
70 | * Observable(식별할수있는?관찰할수있는?) 패턴을 이용하기 때문에 database를 관찰하고 자동으로 UI를 갱신한다. 직접 View를 바꾸어 주는 번거로움이 사라지며, data와 불일치할 확률이 줄어든다.
71 | 2. 생명주기로 부터 안전, Memory Leak 방지
72 | * ViewModel을 통해 data를 참조하기 때문에 액티비티/프래그먼트의 생명주기를 따르지 않는다. 화면전환과 같이 액티비티가 파괴된 후 재구성 되어도 ViewModel이 data를 홀드하고 있기때문에 영향을 받지 않는다.
73 | * View가 활성화 되어있을 경우에만 작동하기 때문에 불필요한 메모리 사용을 줄일 수 있다.
74 | 3. 역할 분리와 모듈화
75 | * UI, 비지니스 로직, 데이터베이스가 기능별로 모듈화되어있어 유닛테스트에 용이하다.
76 |
77 | ##### MVVM 단점
78 |
79 | * 기존에 비해 추가로 만들어주어야 하는 클래스가 많아지고, 이들을 연결해주어야 한다. 이 과정이 복잡해지면 기존의 프로젝트에 적용하는 시간적, 인적 자원이 많이 요구된다.
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | 그림 출처 :https://medium.com/@lgvalle/firebase-viewmodels-livedata-cb64c5ee4f95
88 |
89 | 그림 출처 : https://android.jlelse.eu/android-architecture-components-now-with-100-more-mvvm-11629a630125?gi=d84490235325
90 |
91 |
92 |
93 | https://www.slideshare.net/DongHoLee23/acrhitecture-deisign-patternmvcmvpmvvm
94 |
95 |
96 |
97 | -----------
98 |
99 | 출처
100 |
101 | [MVVM 패턴이란?]( https://sarc.io/index.php/development/1332-mvvm)
102 |
103 | [[Android] MVVM & 안드로이드 아키텍쳐 컴포넌트 시작하기](https://blog.yena.io/studynote/2019/03/16/Android-MVVM-AAC-1.html)
104 |
105 | [[디자인패턴] MVC, MVP, MVVM 비교](https://beomy.tistory.com/43)
--------------------------------------------------------------------------------
/wiki/about_callback_function.md:
--------------------------------------------------------------------------------
1 | ### 개발하면서 접한 Javascript의 callback 문제
2 |
3 | > 2019-10-25 (작성자: 곽빛나라)
4 |
5 | ----------
6 |
7 | 1. 문제점
8 |
9 | ``````````````````javascript
10 | mounted() {
11 | console.log("mounted() 실행");
12 | this.loadMap();
13 | this.getList()
14 | console.log("mounted 실행 끝 : "+this.map)
15 | },
16 | ``````````````````
17 |
18 | 1. 처음 mounted(){}에서 getList() 내 drawMarker()를 호출할 때, map 객체 정보가 없음, 하지만 mounted 이후 addMarker() 내 drawMarker()를 호출할 경우 map객체정보가 있음
19 |
20 | ###### 처음 map이 로드될 때 마커의 정보가 맵에 뿌려지지 않음
21 |
22 | 
23 |
24 |
25 |
26 | 2. loadMap()의 실행이 끝나기 전에 drawMarker()가 실행됨
27 |
28 | ###### drawMarker()를 할때 map의 객체가 필요
29 |
30 | ###### 하지만 loadMap()에서 map객체를 받아오기 때문에, loadMap()의 실행이 끝나기 전에 drawMarker()가 실행되면 문제가 발생(맵에 마커를 뿌릴 수가 없음)
31 |
32 | 
33 |
34 | 
35 |
36 |
37 |
38 | 2. 해결
39 |
40 | 1. loadMap() 코드 수정
41 |
42 | * 원래 코드
43 |
44 | ``````````javascript
45 | loadMap() {
46 | const _this = this;
47 | if (navigator.geolocation) { // GPS를 지원하면
48 | navigator.geolocation.getCurrentPosition(function (position) {
49 | var mapContainer = document.getElementById('map')
50 | // 지도를 생성한다
51 | var map = new kakao.maps.Map(mapContainer, mapOption);
52 | _this.currentlatlng = map.getCenter();
53 | kakao.maps.event.addListener(map, 'dragend', function () {
54 | _this.currentlatlng = map.getCenter();
55 | });
56 | _this.map = map;
57 | }, function (error) {
58 | console.error(error);
59 | })
60 | console.log("loadMap() if문 끝 : " + _this.map)
61 | }
62 | else {
63 | alert('GPS를 지원하지 않습니다');
64 | }
65 | console.log("loadMap() 실행 끝 : " + _this.map)
66 | },
67 | ``````````
68 |
69 | ###### map의 객체를 받아오기도 전에 loadMap() 함수가 끝나버림 -> 다음 실행할 method 에 영향이 감
70 |
71 | * 수정 후 코드
72 |
73 | `````````````javascript
74 | getCurrentGPS() {
75 | const _this = this;
76 | if (navigator.geolocation) { // GPS를 지원하면
77 | navigator.geolocation.getCurrentPosition(function (position) {
78 | _this.loadMap(position);
79 | _this.getList();
80 | _this.drawMarker();
81 | }, function (error) {
82 | console.error(error);
83 | })
84 | console.log("getCurrentGPS() if문 끝 : " + _this.map)
85 | } else {
86 | alert('GPS를 지원하지 않습니다');
87 | }
88 | console.log("getCurrentGPS() 실행 끝 : " + _this.map)
89 | },
90 | `````````````
91 |
92 | ###### 콜백함수 내에서(현재 좌표가 성공적으로 받아와졌을 경우) 필요한 함수를 차례로 호출
93 |
94 | ###### 콜백함수내에서 필요한 함수를 호출하기 때문에 getCurrentGPS() 함수가 먼저 끝나도 영향이 없음
95 |
96 | 
97 |
98 | ##### 결론 : JavaScript의 call back 에 대한 이해 필요
99 |
100 |
101 |
102 | 3. 필요한 지식
103 |
104 | ###### JavaScript의 CallBack
105 |
106 | * 콜백함수 : 어떤 이벤트가 발생한 후, 수행될 함수를 의미함
107 |
108 | 다른 함수가 실행을 끝낸 뒤 실행되는(call back)되는 함수
109 |
110 | * 콜백함수가 필요한 이유
111 |
112 | * 자바스크립트는 이벤트 기반 언어이기 때문->자바스크립트는 다음 명령어를 실행하기 전 이전 명렁어의 응답을 기다리기보단, 다른 이벤트들을 기다리며 계속 명령을 수행함
113 |
114 | * 콜백함수 사용시 this객체 유의해야함 : 콜백함수 내에서 this는 전역 객체인 window가 되기 때문
115 |
116 | ##### 하지만 자바스크립트가 점점 더 복잡해지면서 최근에는 콜백함수를 인자로 넘겨 비동기 처리를 하는 스타일을 피하는 추세
117 |
118 | ##### 왜? 콜백함수를 중첩해서 사용하게 되면 계속해서 코드를 들여쓰기 해야하는 '콜백 지옥'현상 발생
119 |
120 | #### 따라서 Promis 나 async/await를 이용하여 해결 ->심화학습 필요 ㅠ.ㅠ
121 |
--------------------------------------------------------------------------------
/wiki/about_firebase.md:
--------------------------------------------------------------------------------
1 | # FireBase
2 |
3 | > 2019-10-16 (작성자: 강민), 2019-10-22 (작성자: 강민),
4 | >
5 | > 2019-10-23 (작성자: 강민), 2019-10-31 (작성자: 강민)
6 |
7 | ### 환경 설정과정
8 |
9 | 1. https://console.firebase.google.com/ 에서 프로젝트 생성
10 |
11 | 2. 프로젝트 생성 후 web 앱 추가 생성
12 |
13 | 3. 앱 생성 후 프로젝트 설정(톱니바퀴 아이콘)에서 `Firebase SDK snippet` 에서 환경 설정 코드를 복사
14 |
15 | 4. 복사된 코드를 main.js (혹은 따로 firebase service js 파일을 생성)에 붙여 넣기
16 |
17 | ```javascript
18 | /*
19 | * FireBase의 설정 부분
20 | */
21 | import firebase from 'firebase'
22 |
23 | require("firebase/firestore");
24 |
25 | const firebaseConfig = {
26 | apiKey: "여기에 api키가 자동 생성되있음",
27 | authDomain: "여기에 firebase domain이 자동 생성 되있음",
28 | databaseURL: "여기에 DB URL이 자동 생성 되있음",
29 | projectId: "여기에 프로젝트ID가 자동 생성 되있음",
30 | storageBucket: "여기에 storageBucket이 자동 생성 되있음",
31 | messagingSenderId: "여기에 messagingSenderId가 자동 생성 되있음",
32 | appId: "여기에 appID가 자동 생성 되있음"
33 | };
34 |
35 | //파이어베이스 초기 환경 설정
36 | firebase.initializeApp(firebaseConfig)
37 |
38 | //파이어베이스의 cloud firestore를 사용
39 | const db = firebase.firestore();
40 |
41 | ```
42 |
43 |
44 |
45 |
46 | ### firebase 로그인 인증
47 |
48 | - https://firebase.google.com/docs/auth/web/start?hl=ko
49 |
50 | - firebase Authentification 항목의 '로그인 방법'으로 인증 방법들을 설정이 가능.
51 |
52 | - **이메일 / 비밀번호**를 이용한 **신규 사용자 가입** (가입 후에는 자동으로 로그인이 된다)
53 |
54 | ```javascript
55 | firebase.auth().createUserWithEmailAndPassword(this.email, this.password).then(()=>{
56 | /*
57 | * email과 비밀번호만을 user로 만들어 주는 것을 확장하는 파트,
58 | * 여기서는 user의 이름을 update하는 형식
59 | */
60 | var user = firebase.auth().currentUser;
61 | user.updateProfile({
62 | displayName: this.userName
63 | });
64 | this.$router.push('/');
65 | }).catch(function(error) {
66 | // Handle Errors here.
67 | var errorCode = error.code;
68 | var errorMessage = error.message;
69 | // ...
70 | alert(errorMessage);
71 | });
72 | ```
73 |
74 | - **이메일 / 비밀번호**를 이용한 **로그인**
75 |
76 | ```javascript
77 | firebase.auth().signInWithEmailAndPassword(this.email, this.password).then(()=>{
78 | this.$router.push('/');
79 | }).catch(function(error) {
80 | // Handle Errors here.
81 | var errorCode = error.code;
82 | var errorMessage = error.message;
83 | // ...
84 | alert(errorMessage);
85 | });
86 | ```
87 |
88 | - **구글 계정**을 이용한 **로그인**
89 |
90 | ```javascript
91 | var provider = new firebase.auth.GoogleAuthProvider();
92 | provider.addScope('https://www.googleapis.com/auth/contacts.readonly');
93 |
94 | firebase.auth().signInWithPopup(provider).then(result => {
95 | // This gives you a Google Access Token. You can use it to access the Google API.
96 | var token = result.credential.accessToken;
97 | // The signed-in user info.
98 | var user = result.user;
99 | //
100 | this.$router.push('/');
101 | }).catch(function(error) {
102 | // Handle Errors here.
103 | var errorCode = error.code;
104 | var errorMessage = error.message;
105 | // The email of the user's account used.
106 | var email = error.email;
107 | // The firebase.auth.AuthCredential type that was used.
108 | var credential = error.credential;
109 | // ...
110 | alert(errorCode);
111 | });
112 | ```
113 |
114 |
115 |
116 | ### firebase DB
117 |
118 | - firebase DB는 `realtime database`와 `cloud firestore` 두 가지 버전의 실시간 DB가 존재하는 데, 이 중 `cloud firestore`를 사용할 예정
119 | - 이 둘의 차이점은 https://firebase.google.com/docs/database/rtdb-vs-firestore?hl=ko 을 참고.
120 | - `cloud firestore`는 NoSQL로 **`Collection`** - **`Document`** - **`(key: value)`** 구조로 되어있다
121 | - **`Collection`** : 데이터 파일들의 집합
122 | - **`Document`** : 데이터 파일
123 | - **`(Key: value)`** : 데이터 파일의 세부 데이터
124 | - (예) **message(Document)**는 **보낸 사람(Key)이 강민(Value)**이고, **보낸 메세지(Key)는 안녕하세요(Value)**로 구성되다. 이러한 message 다큐먼트들이 모여있는 공간이 **Chat(Collection)**에 있다.
125 |
126 |
127 |
128 | ### Firestore database Function
129 |
130 | - https://firebase.google.com/docs/reference/js?hl=ko
131 |
132 | - Document 추가 함수
133 |
134 | ```javascript
135 | db.collection('chat').add({
136 | msg:this.msg,
137 | createdAt: new Date()
138 | })
139 | ```
140 |
141 | chat이란 collection에 msg와 createdAt이 담긴 document저장
142 |
143 | - Document 불러오는 함수
144 |
145 | ```javascript
146 | db.collection('chat').orderBy('createdAt').onSnapshot((querySnapshot)=>{
147 | let allMessages=[];
148 | querySnapshot.forEach(doc=>{
149 | allMessages.push(doc.data())
150 | })
151 | this.msgs = allMessages;
152 | })
153 | ```
154 |
155 | chat이란 collection에서 createdAt기준으로 정렬된 Document들을 allMessages란 변수에 push하고 이를 this.msgs에 반영
156 |
157 |
158 |
159 | ### Firestore 데이터 가져오기
160 |
161 | - **메소드를 호출하는 get()** & **데이터 변경 이벤트를 수신하는 리스너 Onsnapshot()**
162 |
163 | - get()
164 |
165 | - 문서를 가져오는 메소드
166 |
167 | - 문서가 변경이 되었을 시 다시 get()을 호출해야 함. 즉, get() 호출 이후의 변경 사항들은 반영이 안됨.
168 |
169 | - 쿼리를 실행하여 QuerySnapshot(해당 쿼리의 결과물) 형태로 return
170 |
171 | ```javascript
172 | db.collection("cities").doc("SF").get().then(function(doc) {
173 | if (doc.exists) {
174 | console.log("Document data:", doc.data());
175 | } else {
176 | // doc.data() will be undefined in this case
177 | console.log("No such document!");
178 | }
179 | }).catch(function(error) {
180 | console.log("Error getting document:", error);
181 | });
182 | ```
183 |
184 | - onSnapshot()
185 |
186 | - 데이터 변경 이벤트를 수신하는 리스너
187 |
188 | - 문서가 변경시 콜백이 호출되어 변경사항을 업데이트 됨
189 |
190 | - 리스너 방식으로 데이터 변경사항을 주시하므로, 실시간으로 업데이트가 되는 구조
191 |
192 | ```javascript
193 | db.collection("cities").doc("SF").onSnapshot(function(doc) {
194 | console.log("Current data: ", doc.data());
195 | }, function(error){
196 | console.log("Error getting document:", error);
197 | });
198 | ```
199 |
200 |
201 |
202 |
203 | ### Firestore Query
204 |
205 | - Firestore에서 특정한 데이터를 가져오는 쿼리기능을 제공한다
206 |
207 | - 앞서 데이터를 가져오는 get()과 onSnapshot()을 함께 사용이 가능하다
208 |
209 | - where("*fieldPath*", "*fillterOption*","*value*")
210 |
211 | - 어떤 document의 '*field(Key)*'가 '*filterOption*'에 맞는 '*value*'인 것을 파악하고 query를 반환
212 | - filterOption은 비교 연산자이며 총 6가지만 있다
213 | - `<`, `<=`, `==`, `>=`, `>` : 단순 비교 연산자
214 | - `array-contains` : field에 value가 포함되는지 확인하는 연산자, 배열 값 기준 필터링
215 | - 위 6가지 의외 다른 연산자 사용( !=, OR 등)은 지원되지 않는다.
216 |
217 | ```javascript
218 | firestore.collection('nicknamePool').where('user', '==', email).get().then(
219 | (doc) => {
220 | if(doc.empty){
221 | confirm = true
222 | }else{
223 | confirm = false
224 | }
225 | return confirm
226 | }
227 | )
228 | ```
229 |
230 |
231 |
232 |
233 |
234 | ### 참고
235 |
236 | - https://www.youtube.com/watch?v=ifOzAyR1cG4
237 | - https://firebase.google.com/docs/reference/js?hl=ko
238 |
239 |
--------------------------------------------------------------------------------
/wiki/about_firebase_auth.md:
--------------------------------------------------------------------------------
1 | # Firebase Auth & User Session
2 |
3 | > 2019-10-29 (작성자: 강민)
4 |
5 |
6 |
7 | ### preview
8 |
9 | - Firebase Login 과정에서 Google Login과 Email Login을 제공하고 있다. 이 두 방식으로 로그인한후 return값을 살펴보니 차이점이 존재한다. 바로 credential의 유무이다. credential이 무엇인지, 왜 그러한 것인지, 차이점이 무엇인지 알아보자.
10 |
11 | 
12 |
13 |
14 |
15 | ### credential
16 |
17 | - 우선 credential이란 정보 시스템의 특정 운용에서 사옹하는 암호학적인 개인 정보를 뜻한다고는 한다. firebase에서 적용한다면, credential엔 개인정보가 담긴 토큰이 들어있다는 이야기이다.
18 |
19 | - 근데 위 그림에서 보면, Google Login에는 credential이 들어있는 반면에 Email Login에는 credential이 null이다. 그렇다면, Google Login에는 로그인 토큰이 존재하고 그냥 Email Login에는 로그인 토큰이 존재하지 않는 것일까?
20 |
21 | - 위 질문에 답은 **''NO''** 라고 생각한다. Google이나 Email이나 다른 방법으로 로그인이 되었다. 즉, 로그인에 대한 세션은 두 방법 다 유지되어 있다는 것이다.
22 |
23 | - 그리고 Credential에 대한 정보를 찾아본 결과, firebase reference 문서에 이러한 문구가 적혀져 있다.
24 |
25 | > *Interface that represents the credentials returned by an **auth provider**.*
26 |
27 | 이는 firebase.auth.AuthCredential 에 대한 설명으로, 위 그림의 credential 항목에 AuthCredential이란 형태를 표시한 것이다. 즉 auth provider에 의해 반환된 Credential 객체로 담겨져 있는 것이다. Google Login은 Google Provider를 이용하여 credential 정보를 제공하고 있지만, Email Login은 provider가 없기 때문에 credential 항목에 null이 된 것이었다.
28 |
29 | - 그럼 Google Login에 대한 로그인 정보는 확인할 수는 있다. 근데 Email Login의 로그인 정보는 어떻게 확인 할 수 있는 것일까?
30 |
31 |
32 |
33 | ### firebase Auth
34 |
35 | - 파이어베이스를 이용한 로그인, 회원가입, 로그아웃 등 인증과정을 거치는 인터페이스는 firebase.auth 이다.
36 |
37 | - 파이어베이스 인증의 작동원리는 다음과 같다
38 |
39 | 1. 로그인시 사용자에게 인증 정보를 받는다. **이때 인증 정보는이메일주소와 비밀번호일 수도 있고, Google이나 Facebook과 같은 제휴 인증업체에서 받은 OAuth 토큰일 수도 있다.**
40 | 2. 받은 인증 정보를 firebase 인증 SDK로 전달된다.
41 | 3. Firebase 백앤드 서비스가 정보를 확인하여 클라이언트에 응답을 반환한다.
42 |
43 | - 여기서 주목해야할 점은 인증정보가 방법에 따라 다른 것이다. 그렇기 때문에 앞서 credential항목에서도 Google Login과 Email Login의 차이가 생기는 것이었다.
44 |
45 | - 그리고 인증시 다루는 토큰으로는 3가지 유형이 존재한다
46 |
47 | | 토큰 종류 | 설명 |
48 | | ---------------------- | -------- |
49 | | Firebase ID 토큰 | 사용자가 앱에 로그인할 때 Firebase가 만드는 토큰입니다. 이 토큰은 서명된 JWT로, Firebase 프로젝트에서 사용자의 신원을 안전하게 식별합니다. 이 토큰은 Firebase 프로젝트에 고유한 사용자의 ID 문자열을 비롯하여, 사용자의 기본 프로필 정보를 담고 있습니다. [ID 토큰의 무결성은 검증이 가능하므로](https://firebase.google.com/docs/auth/admin/verify-id-tokens) 이 토큰을 백엔드 서버로 전송하여 현재 로그인한 사용자의 신원을 식별할 수 있습니다. |
50 | | ID 제공업체 토큰 | Google과 Facebook 등의 제휴 ID 제공업체가 생성하는 토큰입니다. 토큰의 형식은 다양하지만 대개는 OAuth 2.0 액세스 토큰입니다. 앱은 이 토큰을 통해 사용자가 ID 공급업체에서 정상적으로 인증을 거쳤음을 확인하고, 토큰을 Firebase 서비스가 사용할 수 있는 사용자 인증 정보로 변환합니다. |
51 | | Firebase 맞춤 토큰 | 사용자가 자체 인증 시스템을 통해 앱에 로그인할 수 있게 해 주는 맞춤 인증 시스템이 만드는 토큰입니다. 맞춤 토큰은 [서비스 계정의 비공개 키를 사용해 서명한](https://firebase.google.com/docs/auth/admin/create-custom-tokens) JWT입니다. 앱은 제휴 ID 공급업체에서 반환한 토큰을 사용할 때와 유사한 방식으로 이 토큰을 사용합니다. |
52 |
53 |
54 |
55 | ### firebase user
56 |
57 | - '*우리가 왜 firebase auth를 자세히 살펴볼까?*' 란 의문을 갖는 배경은 단순히 로그인, 회원가입, 로그아웃이 아니라, **현재 user를 확인하고 이를 어떻게 지속되는가**에서 출발하고 있다. 이러한 배경을 가지고 조사한 결과 firebase로 인증하는 방식에 따라 받는 정보가 다르다는 것을 알았고, 이제는 받은 정보를 어떻게 활용될 것이가에 대해 이야기한다.
58 |
59 | - 우선은 받은 인증 정보, 즉 현재 사용자는 로그인 할때와 가입한 직후 현재 사용자가 되는 것이다.
60 |
61 | - 이 현재 사용자를 확인하는 기능도 firebase.auth에 담겨져 있다. 크게 두가지 방법이 있다.
62 |
63 | 1. **Auth개체에 리스너를 설정하기 (권장)**
64 |
65 | ```javascript
66 | firebase.auth().onAuthStateChanged(function(user) {
67 | if (user) {
68 | // User is signed in.
69 | } else {
70 | // No user is signed in.
71 | }
72 | });
73 | ```
74 |
75 | 2. **currentUser속성을 이용하기**
76 |
77 | ```javascript
78 | let user = firebase.auth().currentUser;
79 |
80 | if (user) {
81 | // User is signed in.
82 | } else {
83 | // No user is signed in.
84 | }
85 | ```
86 |
87 | - 현재 사용자에 대한 정보는 읽을 수는 있으나, 이를 언제까지 읽을 수 있는가에 대한 대답으로는 세션 관리 분분에 속해있다. **기본적으로 웹 어플리케이션 경우, firebase auth는 사용자가 브라우저를 닫은 후에도 세션을 유지**하고 있다. 이 방식은 웹페이지 방문할 대마다 매번 로그인하지 않아도 되는 간편한 방식이기는 하지만, 웹 페이지 사용에 대한 시나리오는 이것 말고도 다양한 상황이 존재한다.
88 |
89 | - 아래의 시나리오 는 firebase.auth의 기본 세션 유지값이 적합하지 않는 케이스들이다.
90 |
91 | - 민감한 데이터가 있는 애플리케이션은 창이나 탭을 닫을 때마다 상태를 삭제하는 것이 좋다. 사용자가 로그아웃하는 것을 잊은 경우를 대비해서 필요.
92 | - 여러 사용자가 공유하는 기기에서 사용되는 애플리케이션에는 적합하지 않다. 일반적인 예로는 도서관 컴퓨터에서 실행한 앱같은 경우.
93 | - 여러 사용자가 액세스할 수 있는 공유 기기의 애플리케이션에도 적합하지 않다. 개발자가 이 애플리케이션의 액세스 방식을 알 수 없으며 사용자에게 세션 유지 여부를 선택할 수 있는 기능을 제공하고자 할 수도 있다. 로그인 과정에 '계정정보 기억' 옵션을 추가하면 이 기능을 제공할 수 있다.
94 | - 경우에 따라 사용자가 익명이 아닌 계정(제휴, 비밀번호, 전화번호 등)으로 업그레이드할 때까지 개발자가 익명 사용자를 유지하지 않으려고 할 수 있다.
95 | - 개발자가 여러 탭에서 서로 다른 사용자가 애플리케이션에 로그인할 수 있도록 허용하고자 할 수 있다. 기본 동작은 출처가 동일한 여러 탭에서 상태를 유지하는 것.
96 |
97 | - 이러한 시나리오 때문에 인증 상태를 지속하는 부분은 재정의해야 하는 상황이 존재한다. 그리고 이를 firebase.auth에서 제공하고 있다. **제공되는 인증 상태 지속 유형**은 3가지 이다.
98 |
99 | | 열거형 | 값 | 설명 |
100 | | ---------------------------------------- | --------- | ------------------------------------------------------------ |
101 | | `firebase.auth.Auth.Persistence.LOCAL` | 'local' | 브라우저 창이 닫히거나 React Native에서 활동이 폐기된 경우에도 상태가 유지됨을 나타냅니다. 이 상태를 삭제하려면 명시적으로 로그아웃해야 합니다. Firebase 인증 웹 세션은 단일 호스트 출처이며 단일 도메인의 경우에만 유지된다는 점에 유의하세요. |
102 | | `firebase.auth.Auth.Persistence.SESSION` | 'session' | 현재의 세션이나 탭에서만 상태가 유지되며 사용자가 인증된 탭이나 창이 닫히면 삭제됨을 나타냅니다. 웹 앱에만 적용됩니다. |
103 | | `firebase.auth.Auth.Persistence.NONE` | 'none' | 상태가 메모리에만 저장되며 창이나 활동이 새로고침되면 삭제됨을 나타냅니다. |
104 |
105 | - 그리고 위의 값과 ` firebase.auth().setPersistence ` 메소드를 이용하여 인증 상태 지속 유형을 변경할 수 있다.
106 |
107 | ```javascript
108 | firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION)
109 | .then(function() {
110 | // Existing and future Auth states are now persisted in the current
111 | // session only. Closing the window would clear any existing state even
112 | // if a user forgets to sign out.
113 | // ...
114 | // New sign-in will be persisted with session persistence.
115 | return firebase.auth().signInWithEmailAndPassword(email, password);
116 | })
117 | .catch(function(error) {
118 | // Handle Errors here.
119 | var errorCode = error.code;
120 | var errorMessage = error.message;
121 | });
122 | ```
123 |
124 |
125 |
126 | ### 문서 정리하면서 내린 결론...?
127 |
128 | - 로그인 방식마다 인증 세션 정보도 다르다. 다만, Email Login 토큰 정보를 보는 것은 과제이다...
129 | - firebase.auth의 내용을 바탕으로 로그인, 로그아웃, 회원가입, 그리고 현재 사용자 확인을 할 수 있다.
130 | - 그리고 현재 사용자에 대한 세션도 페이지 특성에 맞게 재조정 할 수 있다.
131 |
132 | - 우리가 firebase.auth를 사용하면서 onAuthStateChange란 리스너(매서드)를 통해 회원상태관리를 할 수는 있지만, 추가적으로 여기에 Vue.router와 관련된 개념인 네비게이션 가드 를 사용한다면 현재 외원에 따라 페이지 변경 제어를 유용하게 할 수 있을 것이다. (예를 든다면, 로그인 했을때 다시 로그인 페이지가 안 들어가게 만들기 같은거)
--------------------------------------------------------------------------------
/wiki/about_firebase_auth/googleLogin_emailLogin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/wiki/about_firebase_auth/googleLogin_emailLogin.png
--------------------------------------------------------------------------------
/wiki/about_markdown.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | MarkDown 문법 정리 (with Typora)
4 |
5 | > 2019.10.14 - 강민 작성
6 | >
7 | > 2019.10.31 - 강민 업데이트
8 |
9 |
10 |
11 | ## 문법
12 |
13 | - 제목
14 | - `제목 `
15 | - `제목 `
16 | - `제목 `
17 | - `제목 `
18 | - `제목 `
19 | - `제목 `
20 |
21 | - 이미지 첨부
22 | - ``
23 | - ` `를 이용하여 링크걸기
29 |
30 | 예) `README ` = > README
31 |
32 | - 코드 펜스
33 |
34 | - Typora에서의 단축키는 shift+ctrl+k
35 |
36 | - 코드로 짠 부분을 넣어주고 해당 언어를 설정하면 해당 언어 방식으로 보여줌
37 |
38 | - ```c++
39 | printf("Hello world!");
40 | ```
41 |
42 | - 표
43 |
44 | - ```
45 | | 항목1 | 항목2 | 항목3 |
46 | | ------ | ------ | ------ |
47 | | 내용1-1 | 내용1-2 | 내용1-3 |
48 | | 내용2-1 | 내용2-2 | 내용2-3 |
49 | | 내용3-1 | 내용3-2 | 내용3-3 |
50 | ```
51 |
52 | - | 항목1 | 항목2 | 항목3 |
53 | | ------- | ------- | ------- |
54 | | 내용1-1 | 내용1-2 | 내용1-3 |
55 | | 내용2-1 | 내용2-2 | 내용2-3 |
56 | | 내용3-1 | 내용3-2 | 내용3-3 |
57 |
58 |
--------------------------------------------------------------------------------
/wiki/about_promise_async_await.md:
--------------------------------------------------------------------------------
1 | # JavaScript 비동기
2 |
3 | > [자바스크립트 헷갈리는 개념 바로알기]: https://goo.gl/PXuiDB
4 | > [자바스크립트 Promise 쉽게 이해하기]: https://joshua1988.github.io/web-development/javascript/promise-for-beginners/
5 |
6 |
7 |
8 | ## Promise
9 |
10 | >- 자바스크립트 비동기 처리에 사용되는 객체
11 | >
12 | >- resolve를 통해 Promise 객체에 반환하고자하는 값을 담을 수 있고, reject를 통해 예외를 발생시킬수 있다.
13 |
14 |
15 |
16 | #### 일반적인 callback 패턴과 Promise 패턴 비교
17 |
18 | ###### 일반적인 callback 패턴
19 |
20 | > 비동기 함수를 순차적으로 호출하기 위해서는 아래와 같이 복잡한 코드 구조를 가져야된다.
21 |
22 | ```javascript
23 | function delay(sec, callback){
24 | setTimeout(() => {
25 | callback(new Date());
26 | }, sec * 1000);
27 | }
28 | ```
29 |
30 | ```javascript
31 | delay(1, (result) => {
32 | console.log(1, result);
33 | delay(1, (result) => {
34 | console.log(2, result);
35 | dealy(1, (result) => {
36 | console.log(3, result);
37 | });
38 | });
39 | });
40 | ```
41 |
42 |
43 |
44 | ###### Promise 패턴
45 |
46 | > .then()을 사용해서 코드 구조가 명료해진다.
47 |
48 | ```javascript
49 | function delayPromise(sec){
50 | return new Promise((resolve, reject) => {
51 | setTimeout(() => {
52 | resolve(new Date());
53 | }, sec * 1000);
54 | });
55 | }
56 | ```
57 |
58 | ```javascript
59 | delayPromise(1).then((result) => {
60 | console.log(1, result);
61 | return delayPromise(1);
62 | }).then((result) => {
63 | console.log(2, result);
64 | return delayPromise(1);
65 | }).then((result) => {
66 | console.log(3, result);
67 | });
68 | ```
69 |
70 |
71 |
72 | #### Promise 예외처리
73 |
74 | > Promise 객체의 catch()를 통해 예외를 처리한다.
75 |
76 | ```javascript
77 | function wait(sec) {
78 | return new Promise((resolve, reject) => {
79 | setTimeout(() => {
80 | reject('error!');
81 | }, sec * 1000);
82 | });
83 | }
84 |
85 | wait(3)
86 | .then(()=>{
87 | // 비동기 로직이 정상적으로 완료되었을 때
88 | })
89 | .catch(()=>{
90 | // 비동기 로직에서 예외가 발생했을 때
91 | })
92 | ```
93 |
94 |
95 |
96 | ## async
97 |
98 | ###### async로 선언된 함수는 Promise 객체를 반환한다
99 |
100 | ```javascript
101 | // 두 함수 모두 Promise 객체를 반환
102 | async function myAsync(){
103 | return 'async';
104 | }
105 |
106 | function myPromise(){
107 | return new Promise((resolve, reject) => {
108 | resolve('async');
109 | });
110 | }
111 | ```
112 |
113 | ###### Promise 객체에 담긴 resolved 값은 .then의 callback 함수의 인자로 접근 할 수 있다
114 |
115 | ```javascript
116 | myAsync().then((result) => {
117 | // result가 Promise 객체에 담긴 resolved value
118 | // 위 예제에서 result === 'async'
119 | })
120 | ```
121 |
122 |
123 |
124 | ## await
125 |
126 | ###### await는 비동기 로직이 이행(fulfilled) 또는 실패(rejected) 할 때까지 기다리기(pending) 위해 사용되는 문법으로, async로 선언된 함수내에서만 사용가능하다.
127 |
128 | ```javascript
129 | // delay없이 바로 'test' 출력
130 | function test(){
131 | delayPromise(3);
132 | console.log('test');
133 | }
134 |
135 | // 3초 후 'test' 출력
136 | async function testAwait(){
137 | await delayPromise(3);
138 | console.log('test');
139 | }
140 | ```
141 |
142 |
143 |
144 | ## async/await 예외처리
145 |
146 | ###### async/await는 동기로직에서처럼 try/catch 구문으로 처리가능하다.
147 |
148 | ```javascript
149 | function wait(sec){
150 | return new Promise((resolve, reject) => {
151 | setTimeout(() => {
152 | reject('wait Error!!!');
153 | }, sec * 1000);
154 | });
155 | }
156 | ```
157 |
158 | ```javascript
159 | async function myAsync(){
160 | console.log(new Date());
161 | try{
162 | await wait(3);
163 | }catch(e){
164 | console.error(e);
165 | }
166 | console.log(new Date());
167 | }
168 | ```
169 |
170 | ###### Promise 객체의 catch()를 사용해서도 예외처리가 가능하다.
171 |
172 | ```javascript
173 | async function myAsync(){
174 | console.log(new Date());
175 | await wait(3).catch(e => {
176 | console.error(e);
177 | });
178 | console.log(new Date());
179 | }
180 | ```
181 |
182 | ###### 단, 예외가 발생하면 예외 Promise 객체가 반환되므로 조심해야한다.
183 |
184 | ```javascript
185 | async function myAsync(){
186 | console.log(new Date());
187 | const result = await wait(3).catch(e => {
188 | console.error(e);
189 | });
190 | console.log(result); // result === 예외 promise 객체
191 | // 예외가 발생하지 않으면 wait에서 resolved 된 값이 반환된다.
192 | console.log(new Date());
193 | }
194 | ```
195 |
196 |
--------------------------------------------------------------------------------
/wiki/about_realtime_database_chat.md:
--------------------------------------------------------------------------------
1 | # 채팅 Realtime Database 기반 구현
2 |
3 | > 2019.10.22 - 최승미 작성
4 |
5 | ### Firebase Realtime Databse 특징
6 |
7 | - JSON형태의 NoSQL 데이터베이스
8 | - 연결된 모든 클라이언트에 실시간 동기화
9 | - 데이터베이스 구조를 미리 정의하지 않는다.
10 | - 실시간 동기화 되기 때문에 최대한 중첩을 배제해야 한다. (JSON Object에 JSON Object가 중첩이 되는 형태)
11 | - 권한 관리가 가장 중요하다
12 |
13 | ### 실습 코드의 데이터베이스 구조
14 |
15 | - FcmId = Firbase Messaing을 통해 웹 푸시기능 구현 시 필요한 유저별 FCM ID 값
16 | - 유저UID
17 | - FCM ID값
18 | - Messages = 채팅방 별 메시지 데이터
19 | - 채팅방ID
20 | - 메시지ID
21 | - 메시지 세부데이터(내용, 프로필이미지, timestamp, 유저UID, 유저이름)
22 | - 채팅방 이름
23 | - 좌표 값
24 | - 방장UID
25 | - RoomUsers = 채팅방 별 유저 리스트
26 | - 채팅방ID
27 | - 유저UID
28 | - UserRooms = 유저 별 채팅방 리스트 // 필요는 할듯? 어떤 유저가 가지고 있는지 확인해야할듯
29 | - 유저UID
30 | - 채팅방ID
31 | - 채팅방 세부데이터(마지막 메시지, 프로필이미지, 채팅방ID, timestamp 등)
32 | - Users = 가입한 유저 데이터
33 | - 유저UID
34 | - 유저 세부데이터(이메일, 프로필이미지, 유저 이름)
35 | - UserConnection = 유저별 접속여부 기록(우리가 구현할 채팅방에서는 필요 없음)
36 | - 유저UID
37 | - 접속여부
38 | - 마지막 접속 timestamp
39 |
40 | ### Realtime Database 권한 설정
41 |
42 | - 기본 권한: Authentication 인증 받았을 때 읽기, 쓰기 허용
43 | - 채팅방은 인원으로 등록되어 있을 때만 접근 가능(참여중일 때만)
44 | - 채팅방 목록은 본인것만 읽을 수 있음
45 | - 추후 우리는 좌표를 방장만 조절할 수 있도록 권한 설정 해야함
46 |
47 |
--------------------------------------------------------------------------------
/wiki/about_rest.md:
--------------------------------------------------------------------------------
1 | ## REST API
2 |
3 | > 2019.10.16 - 최승미 작성
4 |
5 | ##### REST란
6 |
7 | - Representational State Transfer
8 | - 분산 네트워크 프로그래밍 아키텍처 스타일
9 |
10 | ##### REST가 필요한 이유
11 |
12 | - 애플리케이션 분리 및 통합
13 | - 다양한 클라이언트의 등장
14 |
15 | ##### REST의 구성
16 |
17 | - 자원(Resource) - URI
18 | - 행위(Verb) - HTTP METHOD
19 | - 표현(Representations)
20 |
21 | ##### REST의 특성
22 |
23 | - 유니폼 인터페이스(Uniform)
24 | - URI로 지정한 리소스에 대한 조작 통일, 한정적인 인터페이스로 수행하는 아키텍처 스타일
25 | - 무상태성(Stateless)
26 | - 상태정보를 따로 저장하거나 관리하지 않음
27 | - 세션정보나 쿠키정보를 별도로 저장하고 관리하지 않기 떄문에 들어오는 요청만 처리하면 됨
28 | - 때문에 자유도가 높아지고 불필요한 정보를 서버가 관리하지 않기 때문에 구현이 단순해짐
29 | - 캐시 가능(Cacheable)
30 | - HTTP 기존 웹 표준을 그대로 사용하기 때문에 웹에서 사용하는 기존 인프라를 그대로 활용 가능
31 | - Last-Modified 태그나 E-Tag를 이용하면 캐싱 구현 가능
32 | - 자체 표현 구조(Self-descriptiveness)
33 | - REST API 메시지만 보고도 쉽게 이해할 수 있는 자체 표현 구조
34 | - 클라이언트와 서버가 독립적으로 구분
35 | - 계층형 구조
36 | - 다중 계층으로 구성될 수 있음
37 | - 보안, 로드 밸런싱, 암호화 계층을 추가해 유연성을 둘 수 있음
38 | - PROXY, Gateway같은 네트워크 기반의 중간매체도 사용할 수 있음
39 |
40 | ##### REST API 디자인 가이드
41 |
42 | - URI는 정보의 자원을 표현해야한다
43 |
44 | - URI는 정보의 자원을 표현해야한다.
45 |
46 | > GET /members/delete/1
47 |
48 | 잘못된 표현이다. delete같은 행위에 대한 표현이 들어가면 안됨
49 |
50 | - 자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE)로 표현한다.
51 |
52 | - 위의 잘못된 표현을 수정하면
53 |
54 | > DELETE /members/1
55 |
56 | - Example
57 |
58 | > GET /members/show/1 (X)
59 | >
60 | > GET / members/1 (O)
61 |
62 | > GET /members/insert/2 (X)
63 | >
64 | > POST /members/2 (O)
65 |
66 | - URI 설계 시 주의할 점
67 |
68 | - 슬래시 구분자(/)는 계층 관계를 나타내는 데 사용
69 |
70 | > /houses/apartments
71 | >
72 | > /animals/mammals/whales
73 |
74 | - URI 마지막문자로 슬래시(/)를 포함하지 않는다.
75 |
76 | - 하이픈(-)은 URI 가독성을 높이는데 사용할 수 있다.
77 |
78 | - 밑줄(_)은 URI에 사용하지 않는다.
79 |
80 | - URI경로에는 소문자가 적합하다.
81 |
82 | - 대문자는 피해야 한다. 대소문자에 따라 다른 리소스로 인식하게 되기 때문
83 |
84 | - 파일확장자는 URI에 포함시키지 않는다.
85 |
86 | - 리소서 간의 관계를 표현하는 방법
87 |
88 | - 리소스 간에는 연관 관계가 있을 수 있다.
89 |
90 | > /리소스명/리소스 ID/관계가 있는 다른 리소스명
91 | >
92 | > GET : /users/{userid}/devices (일반적으로 소유 'has'의 관계를 표현할 때)
93 |
94 | - 관계명이 복잡하다면 서브 리소스에 명시적으로 표현,
95 |
96 | > ex) 사용자가 '좋아하는' 디바이스 목록
97 | >
98 | > GET : /users/{userid}/likes/devices (관계명이 애매하거나 구체적 표현이 필요할 때)
99 |
100 | ##### HTTP METHOD의 알맞은 역할
101 |
102 | | METHOD | 역할 |
103 | | ------ | ------------------------------------------------------------ |
104 | | POST | POST를 통해 해당 URI에 요청하면 리소스 생성 |
105 | | GET | GET을 통해 해당 리소스를 조회, 리소스를 조회하고 해당 도큐먼트에 대한 자세한 정보를 가져옴 |
106 | | PUT | PUT을 통해 해당 리소스를 수정 |
107 | | DELETE | DELETE를 통해 리소스를 삭제 |
108 |
109 | ##### HTTP 응답 상태 코드
110 |
111 | | 상태코드 | |
112 | | -------- | ------------------------------------------------------------ |
113 | | 200 | 클라이언트의 요청을 정상적으로 수행함 |
114 | | 201 | 클라이언트가 어떠한 리소스 생성을 요청, 해당 리소스가 성공적으로 생성됨(POST를 통한 리소스 생성 작업 시) |
115 |
116 | | 상태코드 | |
117 | | -------- | ------------------------------------------------------------ |
118 | | 400 | 클라이언트의 요청이 부적절할 경우 사용 |
119 | | 401 | 클라이언트가 인증되지 않은 상태에서 보호된 리소스를 요청했을 때 사용 |
120 | | | (로그인하지 않은 유저가 로그인 했을 때, 요청 가능한 리소스를 요청했을 때) |
121 | | 403 | 유저 인증상태와 관계없이 응답하고싶지 않은 리소스를 클라이언트가 요청했을 때 사용 |
122 | | | (403보다는 400이나 404를 사용할 것을 권고, 403 자체가 리소스가 존재한다는 뜻) |
123 | | 405 | 클라이언트가 요청한 리소스에서는 사용 불가능한 Method를 이용했을 경우 사용 |
124 |
125 | | 상태코드 | |
126 | | -------- | ---------------------------------------------------------- |
127 | | 301 | 클라이언트가 요청한 리소스에 대한 URI가 변경되었을 때 사용 |
128 | | | (응답 시 Location header에 변경된 URI를 적어줘야 함) |
129 | | 500 | 서버에 문제가 있을 경우 사용하는 응답 코드 |
130 |
131 |
--------------------------------------------------------------------------------
/wiki/about_roadview.md:
--------------------------------------------------------------------------------
1 | # Kakao Maps API - RoadView
2 |
3 | >
4 |
5 | ## 로드뷰 생성하기
6 |
7 | ```
8 | // 로드뷰를 표시할 div
9 | var roadviewContainer = document.getElementById('roadview');
10 |
11 | // 로드뷰 객체를 생성한다
12 | var roadview = new kakao.maps.Roadview(roadviewContainer);
13 |
14 | // 좌표로부터 로드뷰 파노ID를 가져올 로드뷰 helper객체
15 | var roadviewClient = new kakao.maps.RoadviewClient();
16 |
17 | // 로드뷰 위치
18 | var rvPosition = new kakao.maps.LatLng(37.56613, 126.97837);
19 |
20 | // rvPosition으로부터의 반경(미터)
21 | var radius = 50;
22 |
23 | // 특정 위치의 좌표와 가까운 로드뷰의 panoId를 추출하여 로드뷰를 띄운다.
24 | roadviewClient.getNearestPanoId(rvPosition, radius, function(panoId) {
25 | roadview.setPanoId(panoId, rvPosition); // panoId와 중심좌표를 통해 로드뷰 실행
26 | });
27 |
28 | ```
29 |
30 |
--------------------------------------------------------------------------------
/wiki/about_vscode.md:
--------------------------------------------------------------------------------
1 | # VScode 유용한 정보 모음
2 |
3 | > 2019.10.14 - 조우현 작성
4 | >
5 | > 2019.10.31 - 강민 작성
6 |
7 |
8 |
9 | ### VScode 관련 꿀팁을 업데이트 해주세요.
10 |
11 |
12 |
13 | ## 단축키
14 |
15 | - 터미널 열기 `CTRL` + `~`
16 |
17 |
18 |
19 | ## 참조
20 |
21 | - [https://www.vobour.com/%EA%B0%9C%EB%B0%9C-%EC%83%9D%EC%82%B0%EC%84%B1%EC%9D%84-%EC%98%AC%EB%A0%A4%EC%A3%BC%EB%8A%94-vscode%EC%9D%98-%EC%86%8C%EC%86%8C-%ED%95%9C-%EA%B8%B0%EB%8A%A5%EB%93%A4](https://www.vobour.com/개발-생산성을-올려주는-vscode의-소소-한-기능들)
22 | - https://github.com/Microsoft/vscode-tips-and-tricks?wt.mc_id=DX_881390&fbclid=IwAR36oPHQQVHSc_ff78FfJR8wjE7XMffREMdCDUsFd58V7g8gtpTu_EWWkMA#extension-recommendations
23 | - https://dohoons.com/blog/1617/
--------------------------------------------------------------------------------
/wiki/cicd.md:
--------------------------------------------------------------------------------
1 | # CI/CD 도입하기
2 |
3 | > 2019.10.14 - 조우현 최초 작성
4 |
5 |
6 |
7 | ## 정의
8 |
9 | CI/CD는 애플리케이션 개발 단계를 [자동화](https://www.redhat.com/ko/topics/automation/whats-it-automation)하여 애플리케이션을 보다 짧은 주기로 고객에게 제공하는 방법입니다. CI/CD의 기본 개념은 지속적인 통합, 지속적인 서비스 제공, 지속적인 배포입니다. CI/CD는 새로운 코드 통합으로 인해 개발 및 운영팀에 발생하는 문제를 해결하기 위한 솔루션입니다.
10 |
11 | 특히, CI/CD는 애플리케이션의 통합 및 테스트 단계에서부터 제공 및 배포에 이르는 애플리케이션의 라이프사이클 전체에 걸쳐 지속적인 자동화와 지속적인 모니터링을 제공합니다. 이러한 구축 사례는 “CI/CD 파이프라인”이라 부르며 개발 및 운영팀의 애자일 방식 협력을 통해 지원됩니다.
12 |
13 |
14 |
15 | ### CI (Continuous Integration)
16 |
17 | - 개발자를 위한 자동화 프로세스인 지속적인 통합
18 | - 코드 변경 사항이 정기적으로 빌드 및 테스트되어 공유 레포지토리에 병합 되어 충돌의 문제를 해결할 수 있음.
19 |
20 |
21 |
22 | ### CD(Continuous Deployment or Delivery)
23 |
24 | - 파이프라인의 추가 단계에 대한 자동화를 뜻함
25 | - Delivery는 변경 사항이 버그 테스트를 거쳐 레포지토리에 자동으로 업로드되는 것을 뜻한다. - 최소한의 노력으로 새로운 코드를 배포하는 것을 목표로 한다.
26 | - Deployment는 변경 사항을 레포지토리에서 고객이 사용 가능한 프로덕션 환경까지 자동으로 릴리스하는 것을 의미한다. - 파이프라인의 다음 단계를 자동화
27 |
28 |
29 |
30 | ### Pipeline
31 |
32 | - 코드 통합, 테스트, 릴리즈, 배포 등의 어플리케이션 라이프사이클의 연속된 과정
33 |
34 |
35 |
36 | > 참고자료 / 출처
37 | >
38 | > What is ci/cd
39 | >
40 | > CI/CD란 무엇인가?
41 |
42 |
43 |
44 | ## 왜 사용해야 하는가?
45 |
46 | - 애플리케이션 라이프사이클에 대한 확실한 이해
47 |
48 | - 불필요한 충돌 이슈를 방지한다.
49 |
50 | - 한 번 구축해놓으면 애플리케이션 업데이트가 굉장히 빨라진다.
51 |
52 |
53 |
54 | ## Gitlab CI/CD 사용하기
55 |
56 | > [Gitlab CI/CD 튜토리얼](https://velog.io/@wickedev/Gitlab-CICD-튜토리얼-bljzphditt)
57 | >
58 | > [Spring Boot + Gitlab + AWS 자동배포 - 1](https://velog.io/@kingcjy/AWS-GitLab-Spring-boot로-배포-자동화-구축하기1)
59 | >
60 | > [Spring Boot + Gitlab + AWS 자동배포 - 2](https://velog.io/@kingcjy/AWS-GitLab-Spring-boot로-배포-자동화-구축하기2)
61 |
62 |
63 |
64 | - **On-Premise**
65 |
66 | 소프트웨어 등 솔루션을 클라우드 같이 원격 환경이 아닌 자체적으로 보유한 전산실 서버에 직접 설치해 운영하는 방식을 말한다. 온프레미스는 클라우드 컴퓨팅 기술이 나오기 전까지 기업 인프라 구축의 일반적인 방식이었기 때문에 이전 또는 전통적인 이라는 단어와 함께 사용된다.
67 |
68 |
69 |
70 | 일반적으로 온프레미스 시스템을 구축하는데 시간이 수 개월 이상 걸렸고 비용 또한 많이 들어, 퍼블릭 클라우드가 나올 당시만 해도 온프레미스 환경이 금방이라도 모두 사라질 것 같았다. 하지만 보안적인 이유로 중요하고 보안이 필요한 서비스와 데이터는 온프레미스 환경에서, 덜 중요한 것은 퍼블릭 클라우드 환경을 사용하는 하이브리드 IT 인프라가 대세를 이루고 있다.
71 |
72 |
--------------------------------------------------------------------------------
/wiki/es6-for-vue.md:
--------------------------------------------------------------------------------
1 | ## ES6 란?
2 |
3 | - ECMAScript 2015와 동일한 용어
4 | - 2015년은 ES5(2009년)이래로 진행한 첫 메이저 업데이트가 승인된 해
5 | - 최신 Front-End Framework인 React, Angular, Vue에서 권고하는 언어 형식
6 | - ES5에 비해 문법이 간결해져서 익숙해지면 코딩을 훨씬 편하게 할 수 있음
7 |
8 |
9 |
10 | ## Babel
11 |
12 | - 구 버전 브라우저 중에서는 ES6의 기능을 지원하지 않는 브라우저가 있으므로 transpiling이 필요
13 | - ES6의 문법을 각 브라우저의 호환 가능한 ES5로 변환하는 컴파일러
14 |
15 |
16 |
17 | ## 개요
18 |
19 | - const & let
20 | - Arrow Function
21 | - Enhanced Object Literals
22 | - Modules
23 | - etc...
24 |
25 |
26 |
27 | ### ES5의 특징
28 |
29 | 1. 변수의 Scope
30 |
31 | - 기존 자바스크립트(ES5)는 `{ }`에 상관없이 Scope가 설정됨
32 |
33 | ```javascript
34 | var sum = 0;
35 | for (var i = 1 ; i <= 5 ; ++i){
36 | sum = sum + i;
37 | };
38 | console.log(sum); // 15
39 | console.log(i); // 6
40 | // for문 밖에서도 i에 대한 접근이 가능함 (`i` 선언시에 전역변수로 선언됨)
41 | ```
42 |
43 | 2. Hoisting
44 |
45 | - Hoisting 이란 선언한 함수와 변수를 해석기가 가장 상단에 있는 것처럼 인식한다.
46 |
47 | - JS 해석기는 코드의 라인 순서와 관계 없이 함수선언식과 변수를 위한 메모리 공간을 먼저 확보한다.
48 |
49 | - 따라서, `function a()`와 `var`는 코드의 최상단으로 끌어 올려진 것(hoisted) 처럼 보인다.
50 |
51 | ``` javascript
52 | function willBeOveridden() {
53 | return 10;
54 | };
55 | willBeOveridden(); // 5
56 | function willBeOveridden() {
57 | return 5;
58 | };
59 | ```
60 |
61 | ```javascript
62 | function sum() {
63 | // function statement
64 | // 선언식만 메모리 공간이 먼저 확보된다.
65 | return 10 + 20;
66 | };
67 |
68 | var sum = function() {
69 | // function expression
70 | return 10 + 20;
71 | };
72 | ```
73 |
74 | - 예제
75 |
76 | ```javascript
77 | var sum = 5;
78 | sum = sum + i;
79 |
80 | function sumAllNumbers() {
81 | // ...
82 | };
83 |
84 | var i = 10;
85 |
86 | //// Hoisting ////
87 |
88 | var sum;
89 | function sumAllNumbers() {
90 | // ...
91 | };
92 | var i;
93 |
94 | sum = 5;
95 | sum = sum + i;
96 | i = 10;
97 | ```
98 |
99 |
100 |
101 | ### const & let - 새로운 변수 선언 방식
102 |
103 | - 블록 단위 `{}` 로 변수의 범위가 제한되었음
104 |
105 | - `const` : 한번 선언한 값에 대해서 변경할 수 없음 (상수 개념)
106 |
107 | - `let` : 한번 선언한 값에 대해서 다시 선언할 수 없음
108 |
109 | ```javascript
110 | let sum = 0;
111 | for (let i = 1 ; i <= 5; ++i){
112 | sum = sum + i;
113 | };
114 | console.log(sum); // 10
115 | console.log(i); // Uncaught ReferenceError: i is not defined
116 | ```
117 |
118 | ```javascript
119 | const a = 10;
120 | a = 20; // Uncaught TypeError: Assignment to constant variable
121 |
122 | // 하지만, 객체나 배열의 내부는 변경할 수 있다.
123 |
124 | const a = {};
125 | a.num = 10;
126 | console.log(a); // { num: 10 }
127 |
128 | const a = [];
129 | a.push(20);
130 | console.log(a); // [20]
131 | ```
132 |
133 | ```javascript
134 | function f() {
135 | {
136 | let x;
137 | {
138 | // 새로운 블록안에 새로운 x의 Scope가 생김
139 | const x = "sneaky";
140 | x = "foo"; // 이미 const로 x를 선언했으므로 다시 값을 대입하면 에러
141 | }
142 | // 이전 블록 범위로 돌아왔기 때문에 'let x'에 해당하는 메모리에 값을 대입
143 | x = "bar";
144 | let x = "inner"; // Uncaught SyntaxError: Identifier 'x' has
145 | // already been declared
146 | }
147 | }
148 | ```
149 |
150 |
151 |
152 | ### Arrow Function - 화살표 함수
153 |
154 | - 함수를 정의할 때 `function` 이라는 키워드를 사용하지 않고 `=>`로 대체
155 |
156 | - 흔히 사용하는 콜백 함수의 문법을 간결화
157 |
158 | - Scope의 변화
159 |
160 | ```javascript
161 | // ES5 함수 정의 방식
162 | var sum = function(a, b){
163 | return a + b;
164 | };
165 |
166 | // ES6 함수 정의 방식
167 | let sum = (a, b) => {
168 | return a + b;
169 | };
170 |
171 | sum(10, 20);
172 | ```
173 |
174 | ```javascript
175 | // ES5
176 | var arr = ["a", "b", "c"];
177 | arr.forEach(function(value) {
178 | console.log(value); // a, b, c
179 | });
180 |
181 | // ES6
182 | let arr = ["a", "b", "c"];
183 | arr.forEach(value => console.log(value)); // a, b, c
184 | ```
185 |
186 |
187 |
188 | ### Enhanced Object Literals - 향상된 객체 리터럴
189 |
190 | - 객체의 속성을 메서드로 사용할 때 `function` 예약어를 생략하고 생성 가능
191 |
192 | ```javascript
193 | var dictionary = {
194 | words: 100,
195 | // ES5
196 | lookup: function() {
197 | console.log("find words");
198 | },
199 |
200 | // ES6
201 | lookup() {
202 | console.log("find words");
203 | }
204 | };
205 | ```
206 |
207 | - 객체의 속성명과 값 명이 동일할 때 아래와 같이 축약 가능
208 |
209 | ```javascript
210 | let figures = 10;
211 | let dictionary = {
212 | // figures: figures
213 | figures
214 | };
215 | ```
216 |
217 |
218 |
219 | ### Modules - 자바스크립트 모듈화 방법
220 |
221 | - 자바스크립트 모듈 로더 라이브러리(AMD, Commons JS)기능을 js 언어 자체에서 지원
222 |
223 | - ES5 에서는 자체 모듈화가 없었음
224 | - ES5는 파일을 나눠도 Scope를 공유함
225 | - 모듈화는 재사용성이 높은 코드를 묶어서 사용하기 위함
226 |
227 | - 호출되기 전까지는 코드 실행과 동작을 하지 않는 특징이 있음
228 |
229 | ```javascript
230 | // libs/math.js
231 | export function sum(x, y) {
232 | return x + y;
233 | }
234 | export var pi = 3.141593;
235 |
236 | // main.js
237 | import {sum} from 'libs/math.js'
238 | sum(1, 2);
239 | ```
240 |
241 | - Vue.js에서 마주칠 `default` export
242 |
243 | - 한 개의 파일에서 한 개의 `default`만 export 된다.
244 | - encapsulation
245 |
246 | ```javascript
247 | // util.js
248 | export default function(x) {
249 | return console.log(x);
250 | }
251 |
252 | // main.js
253 | import util from 'util.js';
254 | console.log(util); // function (x) { return console.log(x)}
255 | util("hi");
256 |
257 | // app.js
258 | import log from 'util.js';
259 | console.log(log);
260 | log("hi");
261 | ```
--------------------------------------------------------------------------------
/wiki/exception_handling.md:
--------------------------------------------------------------------------------
1 | # 예외 & 에러 처리
2 |
3 | > 2019-11-03 (작성자: 강민)
4 |
5 |
6 |
7 | ### 프리뷰
8 |
9 | - 어떠한 웹 페이지에서 예기치 못한 오류나 예외 상황들이 발생할 수 있다. 이러한 상황도 사용자 입장에서도 알아야 하기 때문에 오류/예외 상황을 알려주는 페이지를 보여줌으로써 사용자 기반의 웹 서비스를 제공하고 있다. 그렇다면 지금 프로젝트를 진행하고 있는 시점에서 어떠한 오류가 났고 이 오류 상황을 어떻게 처리되어야 할 지 생각해 보자
10 |
11 |
12 |
13 | ### 예외처리
14 |
15 | - 예외처리란 프로그램이 실행되는 동안 문제가 발생할 때 대처할 수 있게 처리하는 것이다.
16 | - 발생하는 문제는 예외(Exception)과 에러(Error)로 분류된다.
17 | - 예외(Exception): 프로그램 실행 중 발생하는 오류
18 | - 오류(Error): 프로그래밍 언어의 문법적인 오류
19 | - 문제가 생기는 이유는 일반적으로 잘못된 코드를 작성했거나, 사용자가 개발자의 의도와 다르게 프로그램을 사용한 이유 등 여러가지로 존재한다.
20 | - 이를 기본 예외 처리와 고급 예외 처리로 해결할 수 있다.
21 | - 기본 예외 처리: 예외가 발생하지 않게 사전에 해결, 조건문으로 처리할 수 있음.
22 | - 고급 예외 처리: 예외가 발생하면 그 예외를 잡아서 해결
23 |
24 |
25 |
26 | ### 고급 예외 처리 (try/catch/finally/throw)
27 |
28 | - 주로 사용하고 있는 Javascript에서 고급 예외 처리를 try/catch/finally 구문과 throw로 해결이 가능하다
29 |
30 | - try: 예외가 발생할 지도 모르는 코드 블록들을 정의하는 공간 (일단 시도해본다)
31 |
32 | - catch: try안에서 예외가 발생한 경우, 그 예외를 처리하는 공간 (예외를 잡는다)
33 |
34 | - finally: catch이후 예외와 상관없이 실행해야 하는 공간 (끝끝내 해야 한다)
35 |
36 | - throw: 예외를 강제적으로 발생시킨다. (예외를 가까운 예외처리 부분으로 던진다)
37 |
38 | ```javascript
39 | try {
40 | /**
41 | * 정상이라면 이 코드는 아무런 문제없이 블록의 시작부터 끝까지 실행된다.
42 | * 하지만 경우에 따라 예외가 발생할 수 있다.
43 | * 예외는 throw 문에 의해 직접적으로 발생할 수도 있고,
44 | * 또는 예외를 발생시키는 메서드의 호출에 의해 발생할 수도 있다.
45 | */
46 | } catch (e) {
47 | /**
48 | * 이 블록 내부의 문장들은 오직 try 블록에서 예외가 발생할 경우에만 실행된다.
49 | * 이 문장들에선 지역 변수 e를 사용하여 Error 객체 또는 앞에서 던진 다른 값을 참조할 수 있다.
50 | * 이 블록에서는 어떻게든 그 예외를 처리할 수도 있고,
51 | * 그냥 아무것도 하지 않고 예외를 무시할 수도 있고,
52 | * 아니면 throw 를 사용해서 예외를 다시 발생시킬 수도 있다.
53 | */
54 | } finally {
55 | /**
56 | * 이 블록에는 try 블록에서 일어난 일에 관계없이 무조건 실행될 코드가 위치한다.
57 | * 이 코드는 try 블록이 어떻게든 종료되면 실행된다.
58 | * try 블록이 종료되는 상황은 다음과 같다.
59 | * 1) 정상적으로 블록의 끝에 도달했을 때
60 | * 2) break, continue 또는 return 문에 의해서
61 | * 3) 예외가 발생했지만 catch 절에서 처리했을 때
62 | * 4) 예외가 발생했고 그것이 잡히지 않은 채 퍼져나갈 때
63 | */
64 | }
65 |
66 | ```
67 |
68 |
69 |
70 | ### 이를 현재 진행 중인 프로젝트에 적용시켜본다면...??
71 |
72 | - 현재 우리는 프로젝트를 진행하면서 vuex와 firebase를 적용하고 있다. 이를 적용하는 데 사용하는 언어는 javascript이므로 웹 사이트의 예외 처리를 할 수 있을 것이다. 근데... 어떻게?
73 |
74 | - 현재 프로젝트 구조를 간략화한다면 아래 구조일 것이다.
75 |
76 | ```
77 | APP
78 | |
79 | └-- view
80 | |
81 | └-- components
82 | |
83 | └-- Vuex(store)
84 | |
85 | └-- state
86 | |
87 | └-- getters
88 | |
89 | └-- mutations
90 | |
91 | └-- actions
92 | |
93 | └-- firebase API
94 | |
95 | └-- kakaomap API
96 | ```
97 |
98 | 사용자들이 사용하고 있는 뷰안에 여러 컴퍼넌트들이 존재하고 그 컴퍼넌트들은 vuex(store)를 바라보고 있고 vuex는 데이터 상태(state), 상태들을 가져오는 getters, state를 변경하는 mutations, 그리고 mutation을 작동시키는 actions로 이루어져 있다. 여기서 actions에서 firebase API를 사용함으로써 DB에 접근하고 state를 반영하고 있다.
99 |
100 | - 즉, APP - View - Component - store - **firebase API & kakaomap API** - store - component - view - app 순으로 데이터가 진행 될 것이다.
101 | - firebase API와 kakaomap API에서 나는 예외 상황들을 try-catch-throw를 통해 상단의 vuex의 예외처리 부분으로 던질 수 있고, vuex(특히 actions 부분)에서 이 예외상황을 이어받아 상단의 component로 전달 할 수 있다. 마지막으로 component가 예외상황을 받으면 그 예외 상황을 처리하는 페이지로 넘어가게 끔, 유도해주면 될 수 있다.
102 | - 작성시간 기준(2019-11-03)으로 코드를 확인한 결과 firebase API에서 try-catch를 통해 예외를 throw를 하고 있음을 확인하였고 (몇몇 부분 제외), vuex(store)의 actions에서도 대부분 try-catch를 통해 예외를 throw를 하고 있다. **그렇다면 vuex가 쏘아올린 예외는 어디에 있는 것일까?**
103 |
104 | - 로그인이나 랜덤닉네임관련 컴퍼넌트에도 try-catch를 하고는 있다. 하지만 중요한 건 **catch** 부분이다. 이 catch부분에서 어떠한 예외페이지로 보내는 vue-router.push가 있어야 할 것이다. 물론 예외페이지도 만들어야 할 것이다.
105 | - 그 외 다른 컴퍼넌트, 특히 채팅과 맵리스트, 그리고 kakaomap API를 사용하는 컴퍼넌트들도 위와 같이 처리해주면서 예외페이지로 넘어가게 만드는 catch부분이 필요할 것으로 보인다.
106 |
107 |
108 |
109 | ### 예외페이지가 받을 에러들
110 |
111 | - 이 프로젝트에서 생길 수 있는 에러들은 크게 3가지로 구분지을 수 있다고 생각한다.
112 |
113 | - 서비스 사용에서의 에러
114 | - firebase의 에러
115 | - kakaomap의 에러
116 |
117 | 서비스 사용에서의 에러는 없는 페이지로 가는 에러(404)나 시간 초과 등 사용자 사용 과정에서 일어날 수 있음직 한 에러들이다. firebase와 kakaomap의 에러는 관련 API를 사용하면서 발생하는 에러로 따로 관련 API reference에 명시를 하고 있다.
118 |
119 | - 이러한 오류들을 받아들이는 파라미터들을 case by case로 나눠서 해동 오류를 보여줌으로써 예외 처리를 마무리 할 수 있다고 생각한다.
120 | - 그리고, 개인적으로 이러한 오류 발생 가능성을 생각하는 것은 초기에 생각을 했어야 하는 아쉬움이 있다. 이러한 프로젝트와 같은 프로젝트를 만들시, 오류를 테스트하는 공간을 따로 만들어 처리한다면, 예외처리 노하우를 쉽게 익힐 수 있을 것이다.
121 |
122 |
123 |
124 | 출처
125 |
126 | https://dimitrioslytras.com/blog/vue-error-handling/
--------------------------------------------------------------------------------
/wiki/gitlab-ci.md:
--------------------------------------------------------------------------------
1 | ## GitLab CI/CD 사용하기
2 |
3 | > Vue.js + Express + AWS EC2 + GitLab CI/CD
4 |
5 |
6 |
7 | - ##### 처음에는 Jenkins를 이용해서 CI/CD를 하고자 했지만, GitLab에서 제공하는 UI를 사용하고 싶어서 GitLab CI/CD를 사용하였습니다.
8 |
9 | - ##### Vue.js 프로젝트는 S3를 사용하면 훨씬 더 배포가 쉽기 때문에 단순 Vue.js 프로젝트라면 S3를 추천합니다.
10 |
11 |
12 |
13 | ### 1. Vue.js 프로젝트 Express로 EC2에 배포하기
14 |
15 | - 참고자료
16 |
17 |
18 |
19 | ### 2. GitLab CI/CD 환경 구축
20 |
21 | - GitLab Runner 설치
22 |
23 | 파이프라인의 각 Job을 담당할 GitLab Runner를 Ubuntu(EC2)에 설치한다.
24 |
25 | 1. 바이너리 설치 파일 다운로드
26 |
27 | ```shell
28 | # Linux x86-64
29 | sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64
30 |
31 | # Linux x86
32 | sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-386
33 |
34 | # Linux arm
35 | sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-arm
36 | ```
37 |
38 | 2. 실행 권한 부여
39 |
40 | ```shell
41 | sudo chmod +x /usr/local/bin/gitlab-runner
42 | ```
43 |
44 | 3. GitLab CI를 위한 User를 생성한다.
45 |
46 | ```shell
47 | sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
48 | ```
49 |
50 | 4. 서비스를 설치한다.
51 |
52 | ```shell
53 | sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
54 | sudo gitlab-runner start
55 | ```
56 |
57 |
58 |
59 | - GitLab Runner 등록
60 |
61 | 1. 등록 명령어를 입력한다.
62 |
63 | ```shell
64 | sudo gitlab-runner register
65 | ```
66 |
67 | 2. URL, Token 입력
68 |
69 |
70 |
71 | ```shell
72 | Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com )
73 | https://gitlab.com
74 | ```
75 |
76 | ```shell
77 | Please enter the gitlab-ci token for this runner
78 | xxx
79 | ```
80 |
81 | 3. Runner에 대한 설명 입력
82 |
83 | ```shell
84 | Please enter the gitlab-ci description for this runner
85 | [hostname] my-runner
86 | ```
87 |
88 | 4. Runner Tag 입력
89 |
90 | ```shell
91 | Please enter the gitlab-ci tags for this runner (comma separated):
92 | my-tag,another-tag
93 | ```
94 |
95 | 5. Runner executor 입력
96 |
97 | ```shell
98 | Please enter the executor: ssh, docker+machine, docker-ssh+machine, kubernetes, docker, parallels, virtualbox, docker-ssh, shell:
99 | shell
100 | ```
101 |
102 |
103 |
104 | - 활성화 된 Runner
105 |
106 |
107 |
108 |
109 |
110 | - `.gilab-ci.yml` 파일 만들기
111 |
112 | runner의 작업에 대한 설정 파일, 프로젝트 디렉토리의 루트에 위치시킨다.
113 |
114 | ```yml
115 | # 배포 작업
116 | deploy-to-server:
117 | # stage는 build, test, deploy가 있다.
118 | stage: deploy
119 | # only 옵션을 통해 특정 상황에서만 runner를 호출할 수 있다.
120 | only:
121 | - master
122 | # script는 해당 job에서 실행할 명령어 script다.
123 | script:
124 | - echo 'DEPLOY RUNNING'
125 | - cd client
126 | - npm install --progress=false
127 | - npm run build
128 | - cd ../server
129 | - npm install
130 | # 해당 job을 담당할 Runner의 Tag
131 | tags:
132 | - deploy
133 |
134 | # 빌드 테스트 작업
135 | build-test:
136 | stage: build
137 | script:
138 | - echo 'BUILD TEST RUNNING'
139 | - cd client
140 | - npm install --progress=false
141 | - npm run build
142 | - whoami
143 | tags:
144 | - build
145 |
146 | ```
147 |
148 |
149 |
150 | - 파이프라인 현황
151 |
152 |
153 |
154 | - Push 했을 때 빌드 테스트를 실시한다.
155 |
156 |
157 |
158 | - Merge Request 했을 때 빌드 테스트를 통과해야 Merge가 활성화 된다.
159 |
160 |
161 |
162 | - 브랜치 보호정책
163 |
164 |
165 |
166 | - Merge request 정책
167 |
168 |
169 |
170 |
171 |
172 | ### 3. 자동배포에 관하여
173 |
174 | - Vue.js 프로젝트는 배포시에 서버를 재부팅할 필요없이 서버가 가동중인 상태에서 배포파일을 교체해주면 된다.
175 | - shell이 닫히더라도 계속해서 프로세스가 가동되어야하기 때문에 `nohup npm run start&` 명령어를 사용한다.
--------------------------------------------------------------------------------
/wiki/gitlab-ci/merge-request-policy.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/wiki/gitlab-ci/merge-request-policy.PNG
--------------------------------------------------------------------------------
/wiki/gitlab-ci/merge-to-develop.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/wiki/gitlab-ci/merge-to-develop.PNG
--------------------------------------------------------------------------------
/wiki/gitlab-ci/pipeline.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/wiki/gitlab-ci/pipeline.PNG
--------------------------------------------------------------------------------
/wiki/gitlab-ci/protect-branch.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/wiki/gitlab-ci/protect-branch.PNG
--------------------------------------------------------------------------------
/wiki/gitlab-ci/push-to-feature.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/wiki/gitlab-ci/push-to-feature.PNG
--------------------------------------------------------------------------------
/wiki/gitlab-ci/runner-info.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/wiki/gitlab-ci/runner-info.PNG
--------------------------------------------------------------------------------
/wiki/gitlab-ci/runner-status.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NerdInRoom/virtualtraveler-vue/40b1eb16355313487463677ae2e77a4170e77e96/wiki/gitlab-ci/runner-status.PNG
--------------------------------------------------------------------------------
/wiki/javascript-modularization.md:
--------------------------------------------------------------------------------
1 | # JavaScript import/export
2 |
3 | > 참고자료 를 정리한 내용입니다.
4 |
5 | ### `require`
6 |
7 | NodeJs에서 사용되는 CommonJS 키워드
8 |
9 | ```js
10 | const module = require('moduleA');
11 | ```
12 |
13 |
14 |
15 | ### `import`
16 |
17 | ES6에서 새롭게 도입된 키워드
18 |
19 | ```js
20 | import module from 'moduleA';
21 | ```
22 |
23 | - 비동기 방식으로 작동한다.
24 | - 모듈에서 실제 쓰이는 부분만 불러오기 때문에 성능과 메모리 부분에서 유리한 측면이 있다.
25 |
26 |
27 |
28 | #### 복수 객체 export / import
29 |
30 | ##### export
31 |
32 | - Named Exports
33 |
34 | 내보낼 때 변수, 함수의 이름을 그대로 불러온다.
35 |
36 | ```js
37 | //currency-functions.js
38 |
39 | const exchangeRate = 0.91;
40 |
41 | // 안 내보냄
42 | function roundTwoDecimals(amount) {
43 | return Math.round(amount * 100) / 100;
44 | }
45 |
46 | // 내보내기 1
47 | export function canadianToUs(canadian) {
48 | return roundTwoDecimals(canadian * exchangeRate);
49 | }
50 |
51 | // 내보내기 2
52 | const usToCanadian = function(us) {
53 | return roundTwoDecimals(us / exchangeRate);
54 | };
55 | export { usToCanadian };
56 | ```
57 |
58 |
59 |
60 | - Default Export
61 |
62 | 단일 객체 내보내기, 내보낼 때 이름을 지정하지 않기 때문에 불러올 때 아무 이름이나 사용할 수 있다.
63 |
64 | ```js
65 | //currency-object.js
66 |
67 | const exchangeRate = 0.91;
68 |
69 | // 안 내보냄
70 | function roundTwoDecimals(amount) {
71 | return Math.round(amount * 100) / 100;
72 | }
73 |
74 | // 내보내기
75 | export default {
76 | canadianToUs(canadian) {
77 | return roundTwoDecimals(canadian * exchangeRate);
78 | },
79 |
80 | usToCanadian: function(us) {
81 | return roundTwoDecimals(us / exchangeRate);
82 | }
83 | };
84 | ```
85 |
86 | 변수에 할당하여 내보내고 싶다면 다음과 같이 가능하고 불러올 때 변수 이름을 강제하지도 않는다.
87 |
88 | ```js
89 | const obj = {
90 | canadianToUs(canadian) {
91 | return roundTwoDecimals(canadian * exchangeRate);
92 | }
93 | };
94 |
95 | obj.usToCanadian = function(us) {
96 | return roundTwoDecimals(us / exchangeRate);
97 | };
98 |
99 | export default obj;
100 | ```
101 |
102 |
103 |
104 | ##### import
105 |
106 | - 여러 객체(Named Exports)를 불러올 때는 ES6의 Destructuring 문법을 사용해서 필요한 객체만 선택적으로 전역에서 사용하거나, 모든 객체에 별명을 붙이고 그 별명을 통해서 접근할 수도 있습니다.
107 |
108 | ```js
109 | // test-currency-functions.js
110 |
111 | // Destructuring
112 | import { canadianToUs } from './currency-functions';
113 |
114 | console.log('50 Canadian dollars equals this amount of US dollars:');
115 | console.log(canadianToUs(50));
116 |
117 | // Alias
118 | import * as currency from './currency-functions';
119 |
120 | console.log('30 US dollars equals this amount of Canadian dollars:');
121 | console.log(currency.usToCanadian(30));
122 | ```
123 |
124 |
125 |
126 |
127 |
128 | - 단일 객체를 불러올 때는 원하는 이름을 주고 해당 객체를 통해 속성에 접근하면 된다.
129 |
130 | ```js
131 | //test-currency-object.js
132 |
133 | import currency from './currency-object';
134 |
135 | console.log('50 Canadian dollars equals this amount of US dollars:');
136 | console.log(currency.canadianToUs(50));
137 |
138 | console.log('30 US dollars equals this amount of Canadian dollars:');
139 | console.log(currency.usToCanadian(30));
140 | ```
141 |
142 |
--------------------------------------------------------------------------------
/wiki/lifecycle-vue.md:
--------------------------------------------------------------------------------
1 |
2 | * 인스턴스 생성 new Vue()
3 |
4 | 2. 이벤트 및 라이프 사이클 초기화
5 |
6 | 3. beforeCreate
7 | 인스턴스가 생성되고 나서 가장 처음으로 실행됨
8 | data 속성과 methods 속성이 아직 인스턴스에 정의되지 않았음
9 | DOM과 같은 화면 요소에도 접근 불가
10 |
11 | 4. 화면에 반응성 주입
12 |
13 | 5. created
14 | data 속성과 methods 속성이 정의되었음
15 | 인스턴스가 화면요소에 부착되기 전이기 때문에 template 속성에 정의된 DOM 접근불가
16 | data, methods 속성에 접근 가능한 첫 라이프사이클이자 컴포넌트가 생성되고 나서 실행
17 | 되는 단계이기 때문에 서버에 데이터를 요청하여 받아오는 로직을 수행하기 좋다.
18 |
19 | 6. el, template 속성 확인
20 |
21 | 7. template 속성 내용을 render()로 변환
22 |
23 | 8. beforeMount
24 | created 단계 이후 template 속성에 지정한 마크업 속성을 render() 함수로 변환한 후
25 | el 속성에 지정한 화면 요소에 인스턴스를 부착하기 전에 호출
26 | render() 함수가 호출되기 직전의 로직
27 |
28 | 9. $el 생성 후 el 속성 값을 대입
29 |
30 | 10. mounted
31 | template 속성에 정의한 화면 요소에 접근 가능
32 | 화면 요소를 제어하는 로직을 수행하기 좋은 단계
33 | 단, DOM에 인스턴스가 부탁되자마자 호출되기 때문에 하위 컴포넌트나 외부 라이브러리에 의해
34 | 추가된 화면 요소들이 최종 HTML코드로 변환되는 시점과 다를 수 있다.
35 |
36 | * 인스턴스를 화면에 부착
37 |
38 | (부착 후에는 데이터가 변경되는 경우에만 거침)
39 | 데이터 관찰 - 뷰의 반응성을 제공하기 위해 $watch속성으로 감시함
40 |
41 | 1) 인스턴스의 데이터 변경
42 |
43 | 2) beforeUpdate
44 | 관찰 중인 데이터가 변경되면 가상 돔으로 화면을 다시 그리기 전에 호출되는 단계
45 | 변경 예정인 새 데이터에 접근할 수 있다. 값을 변경해도 화면이 다시 그려지지는 않는다.
46 |
47 | 3) 화면 재 랜더링 및 데이터 갱신
48 |
49 | 4) updated
50 | 데이터 변경으로 화면을 다시 그리고 나면 실행되는 단계 (화면 요소 변경 완료 시점)
51 | 데이터 값을 여기서 변경하면 무한루프에 빠질 수 있음(computed, watch를 이용해야 함)
52 |
53 | * 인스턴스 내용 갱신
54 |
55 | 12. 인스턴스 접근 가능
56 |
57 | 13. beforeDestroy
58 | 뷰 인스턴스 데이터를 제거하기 좋은 단계
59 |
60 | 14. 컴포넌트, 인스턴스, 디렉티브 등 모두 해제
61 |
62 | 15. destroyed
63 |
64 | * 인스턴스 소멸
65 |
66 |
--------------------------------------------------------------------------------
/wiki/this.md:
--------------------------------------------------------------------------------
1 | # this
2 |
3 | - 전역을 가리키는 this
4 | - window
5 | - 생성자 함수 안에서 객체를 가리키는 this
6 | - 생성된 객체
7 | - 비동기 호출에서의 this
8 | - 비동기 호출에서는 기본적으로 기존의 this를 벗어난 스코프의 this
9 | - 비동기 호출 전에 this를 저장해 둔다 `let that = this`
10 | - ES6에서는 화살표 함수를 사용한다. `() => { this }`
11 | - 화살표 함수 내에서의 this는 호출 시점의 전역 객체를 가리킨다.
12 |
13 |
--------------------------------------------------------------------------------
/wiki/var_let_const.md:
--------------------------------------------------------------------------------
1 | # JavsScript의 변수 선언 var, let, const
2 |
3 | > 2019-10-23 (작성자: 강민)
4 |
5 |
6 |
7 | ### var
8 |
9 | - 재할당, 재선언이 가능한 변수 선언
10 |
11 | - 함수 단위의 scope로 외부에서 접근이 가능
12 |
13 | ```javascript
14 | var a = 1
15 | a = 2
16 | console.log(a) // 2
17 | var a = 3
18 | console.log(a) // 3
19 | ```
20 |
21 |
22 |
23 | ### let
24 |
25 | - 재할당은 가능하지만, 재선언은 불가능한 변수 선언
26 |
27 | - 블록({ }) 단위의 scope로 오직 블록 안에서만 생명주기를 가짐
28 |
29 | ```javascript
30 | let b = 1
31 | b = 2
32 | console.log(b) // 2
33 | let b = 3 // SyntaxError: Identifier 'b' has already been declared
34 | ```
35 |
36 |
37 |
38 | ### const
39 |
40 | - 재할당과 재선언이 불가능한 변수 선언. 상수로 많이 쓰임
41 |
42 | - 블록({ }) 단위의 scope로 오직 블록 안에서만 생명주기를 가짐
43 |
44 | ```javascript
45 | const c = 1
46 | c = 2 // TypeError: Assignment to constant variable
47 | ```
48 |
49 |
50 |
51 | ### var, let, const 비교
52 |
53 | | | var | let | const |
54 | | -------- | ---- | ---- | ----- |
55 | | 재할당 | O | O | X |
56 | | 재선언 | O | X | X |
57 | | scope | 함수 | 블록 | 블록 |
58 | | 외부접근 | O | X | X |
59 |
60 |
61 |
62 | ### 결론
63 |
64 | - javascript의 대표적인 변수 선언들 중 가운데 var같은 경우 변수를 또 선언할 수 있기에 별다른 에러를 허용하는 원인이 된다. 또한 외부 접근이 가능하여 코드가 꼬일 수도 있다. 그러므로 var사용 대신, let과 const를 사용하자.
65 | - let같은 경우 다시 할당할 수 있다는 점에서 값이 자주 바뀌는 변수로 선언할 때 사용하자
66 | - const같은 경우 새로이 할당이 필요 없는 변수로 선언할 때, 즉, 상수처럼 사용할 때 사용하자.
67 |
68 |
--------------------------------------------------------------------------------
/wiki/vuex-vue.md:
--------------------------------------------------------------------------------
1 | Vuex - 상태 관리 라이브러리
2 |
3 | > 개요
4 | >
5 | > - 복잡한 애플리케이션의 컴포넌트들을 효율적으로 관리하는 라이브러리
6 | > - Flux 패턴
7 | > - state, getters, mutations, actions
8 | > - Helper
9 | > - 프로젝트 구조화, 모듈 구조화
10 |
11 |
12 |
13 | ### Vuex란?
14 |
15 | - 무수히 많은 컴포넌트의 데이터를 관리하기 위한 상태 관리 패턴이자 라이브러리
16 | - React의 Flux 패턴에서 기인함
17 | - Vue.js 중고급 개발자가 되기위한 필수 관문
18 |
19 |
20 |
21 | ### Flux란?
22 |
23 | - MVC 패턴의 복잡한 데이터 흐름 문제를 해결하는 개발 패턴 - Unidirectional data flow
24 | - 단방향 흐름
25 | - `Action` - `Dispatcher` - `Model` - `View`
26 | - Action : 화면에서 발생하는 이벤트 또는 사용자의 입력
27 | - Dispatcher : 데이터를 변경하는 방법, 메서드
28 | - Model : 화면에 표시할 데이터
29 | - View : 사용자에게 비춰지는 화면
30 |
31 |
32 |
33 | ### MVC 패턴의 문제점
34 |
35 | - 기능 추가 및 변경에 따라 생기는 문제점을 예측할 수가 없음. 예) 페이스북 채팅 화면
36 | - 앱이 복잡해지면서 생기는 업데이트 루프
37 |
38 |
39 |
40 | ### Flux 패턴의 단방향 데이터 흐름
41 |
42 | - 데이터의 흐름이 여러 갈래로 나뉘지 않고 단방향으로만 처리
43 | - `Action` - `Dispatcher` - `Store` - `View` - `Action` - `Dispatcher`
44 |
45 |
46 |
47 | ### Vuex로 해결할 수 있는 문제
48 |
49 | 1. MVC 패턴에서 발생하는 구조적 오류
50 | 2. 컴포넌트 간 데이터 전달 명시
51 | 3. 여러 개의 컴포넌트에서 같은 데이터를 업데이트 할 때 동기화 문제
52 |
53 |
54 |
55 | ### Vuex 컨셉
56 |
57 | - State : 컴포넌트 간에 공유하는 데이터 `data()`
58 |
59 | - View : 데이터를 표시하는 화면 `template`
60 |
61 | - Action : 사용자의 입력에 따라 데이터를 변경하는 `methods`
62 |
63 | 
64 |
65 |
66 |
67 | ### Vuex 구조
68 |
69 | 
70 |
71 | - Actions - 비동기
72 | - Mutations - 동기
73 |
74 |
75 |
76 | ### Vuex 설치하기
77 |
78 | - Vuex는 싱글 파일 컴포넌트 체계에서 npm 방식으로 라이브러리를 설치하는 것이 좋다.
79 |
80 | ```
81 | npm install vuex --save
82 | ```
83 |
84 | - ES6와 함께 사용해야 더 많은 기능과 이점을 제공받을 수 있음
85 |
86 | - npm audit
87 |
88 | - npm 모듈의 취약점을 점검해주는 기능
89 |
90 |
91 |
92 | ### Vuex 등록
93 |
94 | - src/store/store.js
95 |
96 | ```javascript
97 | import Vue from 'vue'
98 | import Vuex from 'vuex'
99 |
100 | Vue.use(Vuex);
101 | // use는 vue의 plug-in 기능으로 글로벌하게 사용하겠다는 의미
102 |
103 | export const store = new Vuex.Store({
104 |
105 | });
106 | ```
107 |
108 |
109 |
110 | - main.js
111 |
112 | ```js
113 | import {store} from './store/store'
114 | // 변수라서 {store}
115 |
116 | new Vue({
117 | el: '#app',
118 | store
119 | // store: stroe 변수와 선언이 같기 때문에 축약 가능
120 | })
121 | ```
122 |
123 |
124 |
125 | ### Vuex 기술 요소
126 |
127 | - state : 여러 컴포넌트에 공유되는 데이터 `data`
128 | - getters : 연산된 state 값을 접근하는 속성 `computed`
129 | - mutations : state 값을 변경하는 이벤트 로직 메서드 `methods`
130 | - actions : 비동기 처리 로직을 선언하는 메서드 `aysnc methods`
131 |
132 |
133 |
134 | ### state란?
135 |
136 | - 여러 컴포넌트 간에 공유할 데이터 - 상태
137 |
138 | ```js
139 | // Vue
140 | data: {
141 | message: 'Hello Vue.js!'
142 | }
143 |
144 | // Vuex
145 | state: {
146 | message: 'Hello Vue.js!'
147 | }
148 | ```
149 |
150 | ```html
151 |
152 | {{ message }}
153 |
154 |
155 | {{ this.$store.state.message }}
156 | ```
157 |
158 |
159 |
160 | ### getters란?
161 |
162 | - state 값을 접근하는 속성이자 `computed()`처럼 미리 연산된 값을 접근하는 속성
163 |
164 | ```js
165 | // store.js
166 | state: {
167 | num: 10
168 | },
169 | getters: {
170 | getNumber(state) {
171 | return state.num;
172 | },
173 | doubleNumber(state) {
174 | return state.num * 2;
175 | }
176 | }
177 | ```
178 |
179 | ```html
180 | {{ this.$store.getters.getNumber}}
181 | {{ this.$store.getters.doubleNumber }}
182 | ```
183 |
184 |
185 |
186 | ### actions란?
187 |
188 | - 비동기 처리 로직을 선언하는 메서드, 비동기 로직을 담당하는 mutations
189 |
190 | - 데이터 요청, Promise, ES6 async과 같은 비동기 처리는 모두 actions에 선언
191 |
192 | ```javascript
193 | // store.js
194 | state: {
195 | num: 10
196 | },
197 | mutations: {
198 | doubleNumber (state) {
199 | state.num * 2;
200 | }
201 | },
202 | actions: {
203 | delayDoubleNumber (context) { // context로 store의 메서드와 속성 접근
204 | context.commit('dobuleNumber');
205 | }
206 | }
207 |
208 | // App.vue
209 | this.$store.dispatch('delayDoubleNumber');
210 | ```
211 |
212 | #### actions 비동기 코드 예제 1
213 |
214 | ```js
215 | // store.js
216 | mutations: {
217 | addCounter(state) {
218 | state.counter++;
219 | },
220 | },
221 | actions: {
222 | delayedAddCounter(context) {
223 | setTimeout(() => context.commit('addCounter'), 2000);
224 | }
225 | }
226 |
227 | //App.vue
228 | methods: {
229 | incrementCounter() {
230 | this.$store.dispatch('delayedAddCounter');
231 | }
232 | }
233 | ```
234 |
235 | #### actions 비동기 코드 예제 2
236 |
237 | ```js
238 | // store.js
239 | mutations: {
240 | setData(state, fetchedData){
241 | state.product = fetchedData;
242 | }
243 | },
244 | actions: {
245 | fetchProductData(context) {
246 | return axios.get('https://domain.com/products/1')
247 | .then(response => context.commit('setData', response));
248 | }
249 | }
250 |
251 | //App.vue
252 | methods: {
253 | getProduct() {
254 | this.$store.dispatch('fetchProductData');
255 | }
256 | }
257 | ```
258 |
259 |
260 |
261 | ### 왜 비동기 처리 로직은 actions에 선언해야 할까?
262 |
263 | - 언제 어느 컴포넌트에서 해당 state를 호출하고, 변경했는지 확인하기가 어려움
264 | - 결론 : state 값의 변화를 추적하기 어렵기 때문에 mutations 속성에는 동기 처리 로직만 넣어야 한다.
265 |
266 |
267 |
268 | ### 각 속성들을 더 쉽게 사용하는 방법 - Helper
269 |
270 | Store에 있는 아래 4가지 속성들을 간편하게 코딩하는 방법
271 |
272 | - state -> mapState
273 | - getters -> mapGetters
274 | - mutations -> mapMutations
275 | - actions -> mapActions
276 |
277 |
278 |
279 | ### Helper의 사용법
280 |
281 | - Helper를 사용하고자 하는 vue 파일에서 아래와 같이 해당 Helper를 로딩
282 |
283 | ```js
284 | // App.vue
285 | import { mapState } from 'vuex'
286 | import { mapGetters } from 'vuex'
287 | import { mapMutations } from 'vuex'
288 | import { mapActions } from 'vuex'
289 |
290 | export default {
291 | computed() { ...mapState(['num']), ...mapGetters(['countedNum'])},
292 | methods: { ...mapMutations(['clickBtn']), ...mapActions(['asyncClickBtn'])}
293 | }
294 | ```
295 |
296 | - ...는 ES6의 Object Spread Operator
297 |
298 |
299 |
300 | ### mapState
301 |
302 | - Vuex에 선언한 state 속성을 뷰 컴포넌트에 더 쉽게 연결해주는 헬퍼
303 |
304 | ```js
305 | // App.vue
306 | import { mapState } from 'vuex'
307 |
308 | computed() {
309 | ...mapState(['num'])
310 | // num() { return this.$store.state.num; }
311 | }
312 |
313 | // store.js
314 | state: {
315 | num: 10
316 | }
317 | ```
318 |
319 | ```html
320 |
321 | {{ this.num }}
322 | ```
323 |
324 |
325 |
326 | ### mapGetters
327 |
328 | - Vuex에 선언한 getters 속성을 뷰 컴포넌트에 더 쉽게 연결해주는 헬퍼
329 |
330 | ```js
331 | // App.vue
332 | import { mapGetters } from 'vuex'
333 |
334 | computed() { ...mapGetters(['reverseMessage']) }
335 |
336 | // store.js
337 | getters: {
338 | reverseMessage(state) {
339 | return state.msg.split('').reverse().join('');
340 | }
341 | }
342 | ```
343 |
344 | ```html
345 |
346 | {{ this.reverseMessage }}
347 | ```
348 |
349 |
350 |
351 | ### mapMutations
352 |
353 | - Vuex에 선언한 mutations 속성을 뷰 컴포넌트에 더 쉽게 연결해주는 Helper
354 |
355 | ```js
356 | // App.vue
357 | import { mapMutations } from 'vuex'
358 |
359 | methods: {
360 | ...mapMutations(['clickBtn']),
361 | authLogin() {},
362 | displayTable() {}
363 | }
364 |
365 | // store.js
366 | mutations: {
367 | clickBtn(state) {
368 | alert(state.msg);
369 | }
370 | }
371 | ```
372 |
373 | ```html
374 | popup message
375 | ```
376 |
377 |
378 |
379 | ### mapActions
380 |
381 | - Vuex에 선언한 actions속성을 뷰 컴포넌트에 더 쉽게 연결해주는 Helper
382 |
383 | ```js
384 | // App.vue
385 | import { mapActions } from 'vuex'
386 |
387 | methods: {
388 | ...mapActions(['delayClickBtn']),
389 | }
390 |
391 | // store.js
392 | actions: {
393 | delayClickBtn(context) {
394 | setTimeout(() => context.commit('clickBtn'), 2000);
395 | }
396 | }
397 | ```
398 |
399 | ```html
400 | delay popup message
401 | ```
402 |
403 |
404 |
405 | ### Helper의 유연한 문법
406 |
407 | - Vuex에 선언한 속성을 그대로 컴포넌트에 연결하는 문법
408 |
409 | ```js
410 | // 배열 리터럴
411 | ...mapMutations([
412 | 'clickBtn', // 'clickBtn' : clickBtn
413 | 'addNumber' // addNumber(인자)
414 | ])
415 | ```
416 |
417 | - Vuex에 선언한 속성을 컴포넌트의 특정 메서드에다가 연결하는 문법
418 |
419 | ```js
420 | // 객체 리터럴
421 | ...mapMutations({
422 | popupMsg: 'clickBtn' // 컴포넌트 메서드 명 : Store의 Mutation 명
423 | })
424 | ```
425 |
426 |
427 |
428 |
--------------------------------------------------------------------------------