Showing
24 changed files
with
1826 additions
and
0 deletions
server/.gitignore
0 → 100644
server/README.md
0 → 100644
1 | +# SMB (스마트 약병) | ||
2 | + | ||
3 | +# 박권수 : Web Server & Database | ||
4 | + | ||
5 | +- `Server` : **Node.JS** | ||
6 | +- `Web Framework` : **Koa** | ||
7 | +- `DBMS` : **Mongo DB** | ||
8 | +- `Networking` : **HTTP, MQTT** | ||
9 | + | ||
10 | +# How To Use | ||
11 | + | ||
12 | +1. **Node, Mongo DB Install** | ||
13 | + | ||
14 | +```html | ||
15 | +brew install node | ||
16 | +brew install mongodb-community@4.4 | ||
17 | +``` | ||
18 | + | ||
19 | + 2. **ServiceKey, Mongo DB URL Setting** | ||
20 | + | ||
21 | +```html | ||
22 | +// .env | ||
23 | +SERVER_PORT= | ||
24 | +MONGO_URL= | ||
25 | +JWT_SECRET= | ||
26 | +SERVICE_KEY= | ||
27 | +``` | ||
28 | + | ||
29 | + 3. **Server On** | ||
30 | + | ||
31 | +```html | ||
32 | +npm start | ||
33 | +``` | ||
34 | + | ||
35 | +# DataBase Table & Field | ||
36 | + | ||
37 | +- **유저 Table / 허브(가칭) Table** | ||
38 | + | ||
39 | +![https://github.com/Park-KwonSoo/Smart_Medicine_Bottle/blob/server/design/DataTable1.png?raw=true](https://github.com/Park-KwonSoo/Smart_Medicine_Bottle/blob/server/design/DataTable1.png?raw=true) | ||
40 | + | ||
41 | +- **약병 Table** | ||
42 | + | ||
43 | +![https://github.com/Park-KwonSoo/Smart_Medicine_Bottle/blob/server/design/DataTable2.png?raw=true](https://github.com/Park-KwonSoo/Smart_Medicine_Bottle/blob/server/design/DataTable2.png?raw=true) | ||
44 | + | ||
45 | +- **약 정보 Table** | ||
46 | + | ||
47 | +![https://github.com/Park-KwonSoo/Smart_Medicine_Bottle/blob/server/design/DataTable3.png?raw=true](https://github.com/Park-KwonSoo/Smart_Medicine_Bottle/blob/server/design/DataTable3.png?raw=true) | ||
48 | + | ||
49 | +# ToDo | ||
50 | + | ||
51 | +- [x] **MQTT Hosting** | ||
52 | + | ||
53 | +→ 5 / 7 : [test.mosquitto.org](http://test.mosquitto.org) 와 raspberry 3.0 model B - mosquitto 설치로 다중 broker 연결, publish & subscribe 확인 | ||
54 | + | ||
55 | +- [x] **Middle Ware** | ||
56 | + | ||
57 | +→ 5 / 9 : jwtMiddleWare ⇒ access tokening | ||
58 | + | ||
59 | +- [x] **인증 구현** | ||
60 | + | ||
61 | +→ 5 / 9 : Register, Login, Logout, and Access Token | ||
62 | + | ||
63 | +- [x] **데이터테이블 수정 및 추가 기능 구현** | ||
64 | + | ||
65 | +→ 5 / 9 : schema is changed | ||
66 | + | ||
67 | +- [x] 데이터 처리 로직 구현 | ||
68 | +- [x] Node.JS의 특정 유저의 MQTT client를 어떻게 모듈화 시킬까 ? | ||
69 | +- [x] API 유저 인증 추가 | ||
70 | + | ||
71 | +→ 5 / 11 : 각 API에 Authorization 추가 | ||
72 | + | ||
73 | +- [x] Bottle API : 데이터 요청 message publishing 추가 | ||
74 | + | ||
75 | +→ 5 / 11: Bottle Info 조회 시, Broker로 약병의 현재 상태 요청 메시지 전송 | ||
76 | + | ||
77 | +- [x] Hub, Bottle, User unregister 추가 및 연관 데이터 처리 | ||
78 | +- [x] logic return value 및 status | ||
79 | + | ||
80 | +→ 5 / 11 : ctx.body, status 추가 | ||
81 | + | ||
82 | +- [ ] Private IP의 브로커를 웹서버와 연결 | ||
83 | +- [x] Native Application에 전달할 데이터 규칙 정하기 | ||
84 | +- [ ] WebServer AWS 배포 | ||
85 | +- [ ] 안드로이드 <> 서버 <> 브로커 <> 약병 연결하기 | ||
86 | + | ||
87 | +⇒ 안드로이드에서 블루투스로 약병 찾은 후, 해당 약병의 정보를 서버로 전송, 서버는 이 정보를 브로커에게 전송 후 블루투스 통신? | ||
88 | + | ||
89 | +- [x] bottleCtrl : lookUpInfo 함수에서 req 보낸 후 응답받은 새로운 bottle을 출력해야 한다. | ||
90 | +- [x] Hub 이름 짓기 | ||
91 | + | ||
92 | +→ Care Bridge | ||
93 | + | ||
94 | +- [ ] 약병 데이터 업데이트 시간 한국시간으로 | ||
95 | + | ||
96 | +[Schedule](https://www.notion.so/cdcc6627a8344c8da56ffb3856bfc1b9) | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/index.js
0 → 100644
1 | +const Koa = require('koa'); | ||
2 | +const Router = require('koa-router'); | ||
3 | +const bodyparser = require('koa-bodyparser'); | ||
4 | + | ||
5 | +const Mongoose = require('mongoose'); | ||
6 | +const api = require('./src/api'); | ||
7 | +const updateMedicineInfo = require('./src/lib/UpdatingMedicineInfo'); | ||
8 | +const MqttServer = require('./src/util/MqttServer'); | ||
9 | + | ||
10 | +require('dotenv').config(); | ||
11 | +const { SERVER_PORT, MONGO_URL } = process.env; | ||
12 | + | ||
13 | +const app = new Koa(); | ||
14 | +const router = new Router(); | ||
15 | + | ||
16 | + | ||
17 | +Mongoose.connect(MONGO_URL, { | ||
18 | + useFindAndModify : false, | ||
19 | + useNewUrlParser : true, | ||
20 | + useUnifiedTopology: true, | ||
21 | + useCreateIndex : true | ||
22 | +}).then(() => { | ||
23 | + console.log('\x1b[1;32mMongo DB is connected : ', MONGO_URL, '\x1b[0m'); | ||
24 | + updateMedicineInfo.updateMedicineInfo(); | ||
25 | +}).catch(e => { | ||
26 | + console.log(e); | ||
27 | +}) | ||
28 | + | ||
29 | +app.use(bodyparser()); | ||
30 | +router.use('/api', api.routes()); | ||
31 | +app.use(router.routes()).use(router.allowedMethods()); | ||
32 | + | ||
33 | +app.listen(SERVER_PORT, () => { | ||
34 | + console.log('\x1b[1;36mPORT : ', SERVER_PORT, 'is connected\x1b[0m'); | ||
35 | + MqttServer.on(); | ||
36 | +}) | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/package-lock.json
0 → 100644
1 | +{ | ||
2 | + "name": "server", | ||
3 | + "version": "1.0.0", | ||
4 | + "lockfileVersion": 1, | ||
5 | + "requires": true, | ||
6 | + "dependencies": { | ||
7 | + "balanced-match": { | ||
8 | + "version": "1.0.2", | ||
9 | + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", | ||
10 | + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" | ||
11 | + }, | ||
12 | + "base64-js": { | ||
13 | + "version": "1.5.1", | ||
14 | + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", | ||
15 | + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" | ||
16 | + }, | ||
17 | + "bl": { | ||
18 | + "version": "4.1.0", | ||
19 | + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", | ||
20 | + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", | ||
21 | + "requires": { | ||
22 | + "buffer": "^5.5.0", | ||
23 | + "inherits": "^2.0.4", | ||
24 | + "readable-stream": "^3.4.0" | ||
25 | + } | ||
26 | + }, | ||
27 | + "brace-expansion": { | ||
28 | + "version": "1.1.11", | ||
29 | + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", | ||
30 | + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", | ||
31 | + "requires": { | ||
32 | + "balanced-match": "^1.0.0", | ||
33 | + "concat-map": "0.0.1" | ||
34 | + } | ||
35 | + }, | ||
36 | + "buffer": { | ||
37 | + "version": "5.7.1", | ||
38 | + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", | ||
39 | + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", | ||
40 | + "requires": { | ||
41 | + "base64-js": "^1.3.1", | ||
42 | + "ieee754": "^1.1.13" | ||
43 | + } | ||
44 | + }, | ||
45 | + "buffer-from": { | ||
46 | + "version": "1.1.1", | ||
47 | + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", | ||
48 | + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" | ||
49 | + }, | ||
50 | + "callback-stream": { | ||
51 | + "version": "1.1.0", | ||
52 | + "resolved": "https://registry.npmjs.org/callback-stream/-/callback-stream-1.1.0.tgz", | ||
53 | + "integrity": "sha1-RwGlEmbwbgbqpx/BcjOCLYdfSQg=", | ||
54 | + "requires": { | ||
55 | + "inherits": "^2.0.1", | ||
56 | + "readable-stream": "> 1.0.0 < 3.0.0" | ||
57 | + }, | ||
58 | + "dependencies": { | ||
59 | + "readable-stream": { | ||
60 | + "version": "2.3.7", | ||
61 | + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", | ||
62 | + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", | ||
63 | + "requires": { | ||
64 | + "core-util-is": "~1.0.0", | ||
65 | + "inherits": "~2.0.3", | ||
66 | + "isarray": "~1.0.0", | ||
67 | + "process-nextick-args": "~2.0.0", | ||
68 | + "safe-buffer": "~5.1.1", | ||
69 | + "string_decoder": "~1.1.1", | ||
70 | + "util-deprecate": "~1.0.1" | ||
71 | + } | ||
72 | + }, | ||
73 | + "safe-buffer": { | ||
74 | + "version": "5.1.2", | ||
75 | + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", | ||
76 | + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" | ||
77 | + }, | ||
78 | + "string_decoder": { | ||
79 | + "version": "1.1.1", | ||
80 | + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", | ||
81 | + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", | ||
82 | + "requires": { | ||
83 | + "safe-buffer": "~5.1.0" | ||
84 | + } | ||
85 | + } | ||
86 | + } | ||
87 | + }, | ||
88 | + "commist": { | ||
89 | + "version": "1.1.0", | ||
90 | + "resolved": "https://registry.npmjs.org/commist/-/commist-1.1.0.tgz", | ||
91 | + "integrity": "sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==", | ||
92 | + "requires": { | ||
93 | + "leven": "^2.1.0", | ||
94 | + "minimist": "^1.1.0" | ||
95 | + } | ||
96 | + }, | ||
97 | + "concat-map": { | ||
98 | + "version": "0.0.1", | ||
99 | + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", | ||
100 | + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" | ||
101 | + }, | ||
102 | + "concat-stream": { | ||
103 | + "version": "2.0.0", | ||
104 | + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", | ||
105 | + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", | ||
106 | + "requires": { | ||
107 | + "buffer-from": "^1.0.0", | ||
108 | + "inherits": "^2.0.3", | ||
109 | + "readable-stream": "^3.0.2", | ||
110 | + "typedarray": "^0.0.6" | ||
111 | + } | ||
112 | + }, | ||
113 | + "core-util-is": { | ||
114 | + "version": "1.0.2", | ||
115 | + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", | ||
116 | + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" | ||
117 | + }, | ||
118 | + "debug": { | ||
119 | + "version": "4.3.1", | ||
120 | + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", | ||
121 | + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", | ||
122 | + "requires": { | ||
123 | + "ms": "2.1.2" | ||
124 | + } | ||
125 | + }, | ||
126 | + "duplexify": { | ||
127 | + "version": "3.7.1", | ||
128 | + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", | ||
129 | + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", | ||
130 | + "requires": { | ||
131 | + "end-of-stream": "^1.0.0", | ||
132 | + "inherits": "^2.0.1", | ||
133 | + "readable-stream": "^2.0.0", | ||
134 | + "stream-shift": "^1.0.0" | ||
135 | + }, | ||
136 | + "dependencies": { | ||
137 | + "readable-stream": { | ||
138 | + "version": "2.3.7", | ||
139 | + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", | ||
140 | + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", | ||
141 | + "requires": { | ||
142 | + "core-util-is": "~1.0.0", | ||
143 | + "inherits": "~2.0.3", | ||
144 | + "isarray": "~1.0.0", | ||
145 | + "process-nextick-args": "~2.0.0", | ||
146 | + "safe-buffer": "~5.1.1", | ||
147 | + "string_decoder": "~1.1.1", | ||
148 | + "util-deprecate": "~1.0.1" | ||
149 | + } | ||
150 | + }, | ||
151 | + "safe-buffer": { | ||
152 | + "version": "5.1.2", | ||
153 | + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", | ||
154 | + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" | ||
155 | + }, | ||
156 | + "string_decoder": { | ||
157 | + "version": "1.1.1", | ||
158 | + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", | ||
159 | + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", | ||
160 | + "requires": { | ||
161 | + "safe-buffer": "~5.1.0" | ||
162 | + } | ||
163 | + } | ||
164 | + } | ||
165 | + }, | ||
166 | + "end-of-stream": { | ||
167 | + "version": "1.4.4", | ||
168 | + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", | ||
169 | + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", | ||
170 | + "requires": { | ||
171 | + "once": "^1.4.0" | ||
172 | + } | ||
173 | + }, | ||
174 | + "extend": { | ||
175 | + "version": "3.0.2", | ||
176 | + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", | ||
177 | + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" | ||
178 | + }, | ||
179 | + "fs.realpath": { | ||
180 | + "version": "1.0.0", | ||
181 | + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", | ||
182 | + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" | ||
183 | + }, | ||
184 | + "glob": { | ||
185 | + "version": "7.1.6", | ||
186 | + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", | ||
187 | + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", | ||
188 | + "requires": { | ||
189 | + "fs.realpath": "^1.0.0", | ||
190 | + "inflight": "^1.0.4", | ||
191 | + "inherits": "2", | ||
192 | + "minimatch": "^3.0.4", | ||
193 | + "once": "^1.3.0", | ||
194 | + "path-is-absolute": "^1.0.0" | ||
195 | + } | ||
196 | + }, | ||
197 | + "glob-parent": { | ||
198 | + "version": "3.1.0", | ||
199 | + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", | ||
200 | + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", | ||
201 | + "requires": { | ||
202 | + "is-glob": "^3.1.0", | ||
203 | + "path-dirname": "^1.0.0" | ||
204 | + } | ||
205 | + }, | ||
206 | + "glob-stream": { | ||
207 | + "version": "6.1.0", | ||
208 | + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-6.1.0.tgz", | ||
209 | + "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", | ||
210 | + "requires": { | ||
211 | + "extend": "^3.0.0", | ||
212 | + "glob": "^7.1.1", | ||
213 | + "glob-parent": "^3.1.0", | ||
214 | + "is-negated-glob": "^1.0.0", | ||
215 | + "ordered-read-streams": "^1.0.0", | ||
216 | + "pumpify": "^1.3.5", | ||
217 | + "readable-stream": "^2.1.5", | ||
218 | + "remove-trailing-separator": "^1.0.1", | ||
219 | + "to-absolute-glob": "^2.0.0", | ||
220 | + "unique-stream": "^2.0.2" | ||
221 | + }, | ||
222 | + "dependencies": { | ||
223 | + "readable-stream": { | ||
224 | + "version": "2.3.7", | ||
225 | + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", | ||
226 | + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", | ||
227 | + "requires": { | ||
228 | + "core-util-is": "~1.0.0", | ||
229 | + "inherits": "~2.0.3", | ||
230 | + "isarray": "~1.0.0", | ||
231 | + "process-nextick-args": "~2.0.0", | ||
232 | + "safe-buffer": "~5.1.1", | ||
233 | + "string_decoder": "~1.1.1", | ||
234 | + "util-deprecate": "~1.0.1" | ||
235 | + } | ||
236 | + }, | ||
237 | + "safe-buffer": { | ||
238 | + "version": "5.1.2", | ||
239 | + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", | ||
240 | + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" | ||
241 | + }, | ||
242 | + "string_decoder": { | ||
243 | + "version": "1.1.1", | ||
244 | + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", | ||
245 | + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", | ||
246 | + "requires": { | ||
247 | + "safe-buffer": "~5.1.0" | ||
248 | + } | ||
249 | + } | ||
250 | + } | ||
251 | + }, | ||
252 | + "help-me": { | ||
253 | + "version": "1.1.0", | ||
254 | + "resolved": "https://registry.npmjs.org/help-me/-/help-me-1.1.0.tgz", | ||
255 | + "integrity": "sha1-jy1QjQYAtKRW2i8IZVbn5cBWo8Y=", | ||
256 | + "requires": { | ||
257 | + "callback-stream": "^1.0.2", | ||
258 | + "glob-stream": "^6.1.0", | ||
259 | + "through2": "^2.0.1", | ||
260 | + "xtend": "^4.0.0" | ||
261 | + } | ||
262 | + }, | ||
263 | + "ieee754": { | ||
264 | + "version": "1.2.1", | ||
265 | + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", | ||
266 | + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" | ||
267 | + }, | ||
268 | + "inflight": { | ||
269 | + "version": "1.0.6", | ||
270 | + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", | ||
271 | + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", | ||
272 | + "requires": { | ||
273 | + "once": "^1.3.0", | ||
274 | + "wrappy": "1" | ||
275 | + } | ||
276 | + }, | ||
277 | + "inherits": { | ||
278 | + "version": "2.0.4", | ||
279 | + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", | ||
280 | + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" | ||
281 | + }, | ||
282 | + "is-absolute": { | ||
283 | + "version": "1.0.0", | ||
284 | + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", | ||
285 | + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", | ||
286 | + "requires": { | ||
287 | + "is-relative": "^1.0.0", | ||
288 | + "is-windows": "^1.0.1" | ||
289 | + } | ||
290 | + }, | ||
291 | + "is-extglob": { | ||
292 | + "version": "2.1.1", | ||
293 | + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", | ||
294 | + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" | ||
295 | + }, | ||
296 | + "is-glob": { | ||
297 | + "version": "3.1.0", | ||
298 | + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", | ||
299 | + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", | ||
300 | + "requires": { | ||
301 | + "is-extglob": "^2.1.0" | ||
302 | + } | ||
303 | + }, | ||
304 | + "is-negated-glob": { | ||
305 | + "version": "1.0.0", | ||
306 | + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", | ||
307 | + "integrity": "sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=" | ||
308 | + }, | ||
309 | + "is-relative": { | ||
310 | + "version": "1.0.0", | ||
311 | + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", | ||
312 | + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", | ||
313 | + "requires": { | ||
314 | + "is-unc-path": "^1.0.0" | ||
315 | + } | ||
316 | + }, | ||
317 | + "is-unc-path": { | ||
318 | + "version": "1.0.0", | ||
319 | + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", | ||
320 | + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", | ||
321 | + "requires": { | ||
322 | + "unc-path-regex": "^0.1.2" | ||
323 | + } | ||
324 | + }, | ||
325 | + "is-windows": { | ||
326 | + "version": "1.0.2", | ||
327 | + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", | ||
328 | + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" | ||
329 | + }, | ||
330 | + "isarray": { | ||
331 | + "version": "1.0.0", | ||
332 | + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", | ||
333 | + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" | ||
334 | + }, | ||
335 | + "json-stable-stringify-without-jsonify": { | ||
336 | + "version": "1.0.1", | ||
337 | + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", | ||
338 | + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" | ||
339 | + }, | ||
340 | + "leven": { | ||
341 | + "version": "2.1.0", | ||
342 | + "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", | ||
343 | + "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=" | ||
344 | + }, | ||
345 | + "minimatch": { | ||
346 | + "version": "3.0.4", | ||
347 | + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", | ||
348 | + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", | ||
349 | + "requires": { | ||
350 | + "brace-expansion": "^1.1.7" | ||
351 | + } | ||
352 | + }, | ||
353 | + "minimist": { | ||
354 | + "version": "1.2.5", | ||
355 | + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", | ||
356 | + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" | ||
357 | + }, | ||
358 | + "mqtt": { | ||
359 | + "version": "4.2.6", | ||
360 | + "resolved": "https://registry.npmjs.org/mqtt/-/mqtt-4.2.6.tgz", | ||
361 | + "integrity": "sha512-GpxVObyOzL0CGPBqo6B04GinN8JLk12NRYAIkYvARd9ZCoJKevvOyCaWK6bdK/kFSDj3LPDnCsJbezzNlsi87Q==", | ||
362 | + "requires": { | ||
363 | + "commist": "^1.0.0", | ||
364 | + "concat-stream": "^2.0.0", | ||
365 | + "debug": "^4.1.1", | ||
366 | + "help-me": "^1.0.1", | ||
367 | + "inherits": "^2.0.3", | ||
368 | + "minimist": "^1.2.5", | ||
369 | + "mqtt-packet": "^6.6.0", | ||
370 | + "pump": "^3.0.0", | ||
371 | + "readable-stream": "^3.6.0", | ||
372 | + "reinterval": "^1.1.0", | ||
373 | + "split2": "^3.1.0", | ||
374 | + "ws": "^7.3.1", | ||
375 | + "xtend": "^4.0.2" | ||
376 | + } | ||
377 | + }, | ||
378 | + "mqtt-packet": { | ||
379 | + "version": "6.9.1", | ||
380 | + "resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-6.9.1.tgz", | ||
381 | + "integrity": "sha512-0+u0ZoRj6H6AuzNY5d8qzXzyXmFI19gkdPRA14kGfKvbqYcpOL+HWUGHjtCxHqjm8CscwsH+dX0+Rxx4se5HSA==", | ||
382 | + "requires": { | ||
383 | + "bl": "^4.0.2", | ||
384 | + "debug": "^4.1.1", | ||
385 | + "process-nextick-args": "^2.0.1" | ||
386 | + } | ||
387 | + }, | ||
388 | + "ms": { | ||
389 | + "version": "2.1.2", | ||
390 | + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", | ||
391 | + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" | ||
392 | + }, | ||
393 | + "once": { | ||
394 | + "version": "1.4.0", | ||
395 | + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", | ||
396 | + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", | ||
397 | + "requires": { | ||
398 | + "wrappy": "1" | ||
399 | + } | ||
400 | + }, | ||
401 | + "ordered-read-streams": { | ||
402 | + "version": "1.0.1", | ||
403 | + "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", | ||
404 | + "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", | ||
405 | + "requires": { | ||
406 | + "readable-stream": "^2.0.1" | ||
407 | + }, | ||
408 | + "dependencies": { | ||
409 | + "readable-stream": { | ||
410 | + "version": "2.3.7", | ||
411 | + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", | ||
412 | + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", | ||
413 | + "requires": { | ||
414 | + "core-util-is": "~1.0.0", | ||
415 | + "inherits": "~2.0.3", | ||
416 | + "isarray": "~1.0.0", | ||
417 | + "process-nextick-args": "~2.0.0", | ||
418 | + "safe-buffer": "~5.1.1", | ||
419 | + "string_decoder": "~1.1.1", | ||
420 | + "util-deprecate": "~1.0.1" | ||
421 | + } | ||
422 | + }, | ||
423 | + "safe-buffer": { | ||
424 | + "version": "5.1.2", | ||
425 | + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", | ||
426 | + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" | ||
427 | + }, | ||
428 | + "string_decoder": { | ||
429 | + "version": "1.1.1", | ||
430 | + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", | ||
431 | + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", | ||
432 | + "requires": { | ||
433 | + "safe-buffer": "~5.1.0" | ||
434 | + } | ||
435 | + } | ||
436 | + } | ||
437 | + }, | ||
438 | + "path-dirname": { | ||
439 | + "version": "1.0.2", | ||
440 | + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", | ||
441 | + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" | ||
442 | + }, | ||
443 | + "path-is-absolute": { | ||
444 | + "version": "1.0.1", | ||
445 | + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", | ||
446 | + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" | ||
447 | + }, | ||
448 | + "process-nextick-args": { | ||
449 | + "version": "2.0.1", | ||
450 | + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", | ||
451 | + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" | ||
452 | + }, | ||
453 | + "pump": { | ||
454 | + "version": "3.0.0", | ||
455 | + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", | ||
456 | + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", | ||
457 | + "requires": { | ||
458 | + "end-of-stream": "^1.1.0", | ||
459 | + "once": "^1.3.1" | ||
460 | + } | ||
461 | + }, | ||
462 | + "pumpify": { | ||
463 | + "version": "1.5.1", | ||
464 | + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", | ||
465 | + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", | ||
466 | + "requires": { | ||
467 | + "duplexify": "^3.6.0", | ||
468 | + "inherits": "^2.0.3", | ||
469 | + "pump": "^2.0.0" | ||
470 | + }, | ||
471 | + "dependencies": { | ||
472 | + "pump": { | ||
473 | + "version": "2.0.1", | ||
474 | + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", | ||
475 | + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", | ||
476 | + "requires": { | ||
477 | + "end-of-stream": "^1.1.0", | ||
478 | + "once": "^1.3.1" | ||
479 | + } | ||
480 | + } | ||
481 | + } | ||
482 | + }, | ||
483 | + "readable-stream": { | ||
484 | + "version": "3.6.0", | ||
485 | + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", | ||
486 | + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", | ||
487 | + "requires": { | ||
488 | + "inherits": "^2.0.3", | ||
489 | + "string_decoder": "^1.1.1", | ||
490 | + "util-deprecate": "^1.0.1" | ||
491 | + } | ||
492 | + }, | ||
493 | + "reinterval": { | ||
494 | + "version": "1.1.0", | ||
495 | + "resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz", | ||
496 | + "integrity": "sha1-M2Hs+jymwYKDOA3Qu5VG85D17Oc=" | ||
497 | + }, | ||
498 | + "remove-trailing-separator": { | ||
499 | + "version": "1.1.0", | ||
500 | + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", | ||
501 | + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" | ||
502 | + }, | ||
503 | + "safe-buffer": { | ||
504 | + "version": "5.2.1", | ||
505 | + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", | ||
506 | + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" | ||
507 | + }, | ||
508 | + "split2": { | ||
509 | + "version": "3.2.2", | ||
510 | + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", | ||
511 | + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", | ||
512 | + "requires": { | ||
513 | + "readable-stream": "^3.0.0" | ||
514 | + } | ||
515 | + }, | ||
516 | + "stream-shift": { | ||
517 | + "version": "1.0.1", | ||
518 | + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", | ||
519 | + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" | ||
520 | + }, | ||
521 | + "string_decoder": { | ||
522 | + "version": "1.3.0", | ||
523 | + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", | ||
524 | + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", | ||
525 | + "requires": { | ||
526 | + "safe-buffer": "~5.2.0" | ||
527 | + } | ||
528 | + }, | ||
529 | + "through2": { | ||
530 | + "version": "2.0.5", | ||
531 | + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", | ||
532 | + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", | ||
533 | + "requires": { | ||
534 | + "readable-stream": "~2.3.6", | ||
535 | + "xtend": "~4.0.1" | ||
536 | + }, | ||
537 | + "dependencies": { | ||
538 | + "readable-stream": { | ||
539 | + "version": "2.3.7", | ||
540 | + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", | ||
541 | + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", | ||
542 | + "requires": { | ||
543 | + "core-util-is": "~1.0.0", | ||
544 | + "inherits": "~2.0.3", | ||
545 | + "isarray": "~1.0.0", | ||
546 | + "process-nextick-args": "~2.0.0", | ||
547 | + "safe-buffer": "~5.1.1", | ||
548 | + "string_decoder": "~1.1.1", | ||
549 | + "util-deprecate": "~1.0.1" | ||
550 | + } | ||
551 | + }, | ||
552 | + "safe-buffer": { | ||
553 | + "version": "5.1.2", | ||
554 | + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", | ||
555 | + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" | ||
556 | + }, | ||
557 | + "string_decoder": { | ||
558 | + "version": "1.1.1", | ||
559 | + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", | ||
560 | + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", | ||
561 | + "requires": { | ||
562 | + "safe-buffer": "~5.1.0" | ||
563 | + } | ||
564 | + } | ||
565 | + } | ||
566 | + }, | ||
567 | + "through2-filter": { | ||
568 | + "version": "3.0.0", | ||
569 | + "resolved": "https://registry.npmjs.org/through2-filter/-/through2-filter-3.0.0.tgz", | ||
570 | + "integrity": "sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==", | ||
571 | + "requires": { | ||
572 | + "through2": "~2.0.0", | ||
573 | + "xtend": "~4.0.0" | ||
574 | + } | ||
575 | + }, | ||
576 | + "to-absolute-glob": { | ||
577 | + "version": "2.0.2", | ||
578 | + "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", | ||
579 | + "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", | ||
580 | + "requires": { | ||
581 | + "is-absolute": "^1.0.0", | ||
582 | + "is-negated-glob": "^1.0.0" | ||
583 | + } | ||
584 | + }, | ||
585 | + "typedarray": { | ||
586 | + "version": "0.0.6", | ||
587 | + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", | ||
588 | + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" | ||
589 | + }, | ||
590 | + "unc-path-regex": { | ||
591 | + "version": "0.1.2", | ||
592 | + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", | ||
593 | + "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=" | ||
594 | + }, | ||
595 | + "unique-stream": { | ||
596 | + "version": "2.3.1", | ||
597 | + "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-2.3.1.tgz", | ||
598 | + "integrity": "sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==", | ||
599 | + "requires": { | ||
600 | + "json-stable-stringify-without-jsonify": "^1.0.1", | ||
601 | + "through2-filter": "^3.0.0" | ||
602 | + } | ||
603 | + }, | ||
604 | + "util-deprecate": { | ||
605 | + "version": "1.0.2", | ||
606 | + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", | ||
607 | + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" | ||
608 | + }, | ||
609 | + "wrappy": { | ||
610 | + "version": "1.0.2", | ||
611 | + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", | ||
612 | + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" | ||
613 | + }, | ||
614 | + "ws": { | ||
615 | + "version": "7.4.5", | ||
616 | + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", | ||
617 | + "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==" | ||
618 | + }, | ||
619 | + "xtend": { | ||
620 | + "version": "4.0.2", | ||
621 | + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", | ||
622 | + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" | ||
623 | + } | ||
624 | + } | ||
625 | +} |
server/package.json
0 → 100644
1 | +{ | ||
2 | + "name": "server", | ||
3 | + "version": "1.0.0", | ||
4 | + "description": "for Smart Medicine Bottle IoT Server", | ||
5 | + "main": "index.js", | ||
6 | + "scripts": { | ||
7 | + "start": "nodemon", | ||
8 | + "test": "node test" | ||
9 | + }, | ||
10 | + "repository": { | ||
11 | + "type": "git", | ||
12 | + "url": "http://khuhub.khu.ac.kr/2021-1-capstone-design1/RIT_Project1.git" | ||
13 | + }, | ||
14 | + "keywords": [ | ||
15 | + "IoT" | ||
16 | + ], | ||
17 | + "author": "박권수", | ||
18 | + "license": "ISC", | ||
19 | + "dependencies": { | ||
20 | + "mqtt": "^4.2.6" | ||
21 | + } | ||
22 | +} |
server/src/api/auth/auth.ctrl.js
0 → 100644
1 | +//회원가입, 로그인 및 로그아웃에 관한 api | ||
2 | +const User = require('../../models/user'); | ||
3 | +const Joi = require('joi'); | ||
4 | + | ||
5 | +exports.register = async(ctx) => { | ||
6 | + const { userId, password, passwordCheck } = ctx.request.body; | ||
7 | + | ||
8 | + const schema = Joi.object().keys({ | ||
9 | + userId : Joi.string().email().max(50).required(), | ||
10 | + password : Joi.string().required(), | ||
11 | + passwordCheck : Joi.string().required(), | ||
12 | + }) | ||
13 | + | ||
14 | + const result = schema.validate(ctx.request.body); | ||
15 | + if(result.error || password !== passwordCheck) { | ||
16 | + ctx.status = 400; | ||
17 | + return; | ||
18 | + } | ||
19 | + | ||
20 | + const existUser = await User.findByUserId(userId); | ||
21 | + if(existUser) { | ||
22 | + ctx.status = 409; | ||
23 | + return; | ||
24 | + } | ||
25 | + | ||
26 | + const user = new User({ | ||
27 | + userId | ||
28 | + }); | ||
29 | + | ||
30 | + await user.setPassword(password); | ||
31 | + await user.save(); | ||
32 | + | ||
33 | + ctx.status = 201; | ||
34 | + | ||
35 | +}; | ||
36 | + | ||
37 | +exports.login = async(ctx) => { | ||
38 | + const { userId, password } = ctx.request.body; | ||
39 | + | ||
40 | + const schema = Joi.object().keys({ | ||
41 | + userId : Joi.string().email().max(50).required(), | ||
42 | + password : Joi.string().required() | ||
43 | + }) | ||
44 | + | ||
45 | + const result = schema.validate(ctx.request.body); | ||
46 | + if(result.error) { | ||
47 | + ctx.status = 400; | ||
48 | + return; | ||
49 | + } | ||
50 | + | ||
51 | + const user = await User.findByUserId(userId); | ||
52 | + if(!user) { | ||
53 | + ctx.stauts = 401; | ||
54 | + return; | ||
55 | + } | ||
56 | + | ||
57 | + const isPasswordTrue = await user.checkPassword(password); | ||
58 | + if(!isPasswordTrue) { | ||
59 | + ctx.status = 401; | ||
60 | + return; | ||
61 | + } | ||
62 | + | ||
63 | + const token = await user.generateToken(); | ||
64 | + ctx.cookies.set('access_token', token, { | ||
65 | + httpOnly : true, | ||
66 | + maxAge : 1000 * 60 * 60 * 24 * 30 | ||
67 | + }); | ||
68 | + | ||
69 | + ctx.status = 200; | ||
70 | + ctx.body = { | ||
71 | + userId, | ||
72 | + token | ||
73 | + }; | ||
74 | + | ||
75 | +}; | ||
76 | + | ||
77 | +exports.logout = async(ctx) => { | ||
78 | + ctx.cookies.set('access_token', null, { | ||
79 | + httpOnly : true, | ||
80 | + maxAge : 0 | ||
81 | + }); | ||
82 | + | ||
83 | + ctx.status = 204; | ||
84 | +}; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/src/api/auth/index.js
0 → 100644
1 | +const Router = require('koa-router'); | ||
2 | +const authCtrl = require('./auth.ctrl'); | ||
3 | + | ||
4 | +const auth = new Router(); | ||
5 | + | ||
6 | +/** | ||
7 | + * 회원가입 (email type) | ||
8 | + * url : http://localhost:4000/api/auth/register | ||
9 | + * request parameter : userId, password, passwordCheck | ||
10 | + * return : null | ||
11 | + */ | ||
12 | +auth.post('/register', authCtrl.register); | ||
13 | + | ||
14 | +/** | ||
15 | + * 로그인 (email type) | ||
16 | + * url : http://localhost:4000/api/auth/login | ||
17 | + * request parameter : userId, password | ||
18 | + * return : userId | ||
19 | + */ | ||
20 | +auth.post('/login', authCtrl.login); | ||
21 | + | ||
22 | +/** | ||
23 | + * 로그아웃 | ||
24 | + * url : http://localhost:4000/api/auth/logout | ||
25 | + * request parameter : null | ||
26 | + * return : null | ||
27 | + */ | ||
28 | +auth.post('/logout', authCtrl.logout); | ||
29 | + | ||
30 | +module.exports = auth; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/src/api/bottle/bottle.ctrl.js
0 → 100644
1 | +//어플에서 약병 등록 및, 약병에 관한 정보 조회 = 여기서 mqtt통신으로 broker에 데이터를 요청한다. | ||
2 | +const Bottle = require('../../models/bottle'); | ||
3 | +const Hub = require('../../models/hub'); | ||
4 | +const Medicine = require('../../models/medicine'); | ||
5 | +const Mqtt = require('../../lib/MqttModule'); | ||
6 | +const jwt = require('jsonwebtoken'); | ||
7 | + | ||
8 | +//약병 등록 | ||
9 | +exports.bottleConnect = async(ctx) => { | ||
10 | + const token = ctx.req.headers.authorization; | ||
11 | + if(!token || !token.length) { | ||
12 | + ctx.status = 401; | ||
13 | + return; | ||
14 | + } | ||
15 | + | ||
16 | + const { userId } = jwt.verify(token, process.env.JWT_SECRET); | ||
17 | + const { bottleId, hubId } = ctx.request.body; | ||
18 | + | ||
19 | + const isExistBottle = await Bottle.findByBottleId(bottleId); | ||
20 | + if(isExistBottle) { | ||
21 | + ctx.status = 409; | ||
22 | + return; | ||
23 | + } | ||
24 | + | ||
25 | + const hub = await Hub.findByHubId(hubId); | ||
26 | + if(!hub) { | ||
27 | + ctx.status = 404; | ||
28 | + return; | ||
29 | + } | ||
30 | + if(hub.getHub_UserId() !== userId) { | ||
31 | + ctx.status = 403; | ||
32 | + return; | ||
33 | + } | ||
34 | + | ||
35 | + const hosting = hub.getHubHost(); | ||
36 | + if(!hosting) { | ||
37 | + ctx.status = 404; | ||
38 | + return; | ||
39 | + } | ||
40 | + | ||
41 | + | ||
42 | + const newBottle = new Bottle({ | ||
43 | + bottleId, | ||
44 | + hubId | ||
45 | + }); | ||
46 | + | ||
47 | + const client = await Mqtt.mqttOn(hosting); | ||
48 | + const topic = 'bottle/' + newBottle.getBottleId() + '/bts'; | ||
49 | + Mqtt.mqttSubscribe(client, topic); | ||
50 | + | ||
51 | + await newBottle.save(); | ||
52 | + | ||
53 | + ctx.status = 201; | ||
54 | +}; | ||
55 | + | ||
56 | +//약병 등록 해제 | ||
57 | +exports.bottleDisconnect = async(ctx) => { | ||
58 | + const token = ctx.req.headers.authorization; | ||
59 | + if(!token || !token.length) { | ||
60 | + ctx.status = 401; | ||
61 | + return; | ||
62 | + } | ||
63 | + | ||
64 | + const { userId } = jwt.verify(token, process.env.JWT_SECRET); | ||
65 | + const { bottleId } = ctx.params; | ||
66 | + | ||
67 | + const bottle = await Bottle.findByBottleId(bottleId); | ||
68 | + if(!bottle) { | ||
69 | + ctx.status = 404; | ||
70 | + return; | ||
71 | + } | ||
72 | + | ||
73 | + const hub = await Hub.findByHubId(bottle.getHubId()); | ||
74 | + if(hub.getHub_UserId() !== userId) { | ||
75 | + ctx.status = 403; | ||
76 | + return; | ||
77 | + } | ||
78 | + | ||
79 | + const hosting = hub.getHubHost(); | ||
80 | + | ||
81 | + const client = await Mqtt.mqttOn(hosting); | ||
82 | + const topic = 'bottle/' + bottleId + '/bts'; | ||
83 | + Mqtt.mqttUnsubscribe(client, topic); | ||
84 | + | ||
85 | + await Bottle.deleteOne({ bottleId }); | ||
86 | + | ||
87 | + ctx.status = 204; | ||
88 | + | ||
89 | +}; | ||
90 | + | ||
91 | +//약병 정보를 조회 -> 약병에 현재 데이터를 요청한다. message : req | ||
92 | +exports.lookupInfo = async(ctx) => { | ||
93 | + const token = ctx.req.headers.authorization; | ||
94 | + if(!token || !token.length) { | ||
95 | + ctx.status = 401; | ||
96 | + return; | ||
97 | + } | ||
98 | + | ||
99 | + const { userId } = jwt.verify(token, process.env.JWT_SECRET); | ||
100 | + const { bottleId } = ctx.params; | ||
101 | + | ||
102 | + const isBottleExist = await Bottle.findByBottleId(bottleId); | ||
103 | + if(!isBottleExist) { | ||
104 | + ctx.status = 404; | ||
105 | + return; | ||
106 | + } | ||
107 | + | ||
108 | + const hub = await Hub.findByHubId(isBottleExist.getHubId()); | ||
109 | + if(hub.getHub_UserId() !== userId) { | ||
110 | + ctx.status = 403; | ||
111 | + return; | ||
112 | + } | ||
113 | + | ||
114 | + const hosting = hub.getHubHost(); | ||
115 | + //서버에서 bottle로 데이터를 요청한다. | ||
116 | + const client = await Mqtt.mqttOn(hosting); | ||
117 | + const topic = 'bottle/' + bottleId + '/stb'; | ||
118 | + const message = 'req'; | ||
119 | + await Mqtt.mqttPublishMessage(client, { topic, message }); | ||
120 | + | ||
121 | + const bottle = await Bottle.findByBottleId(bottleId); | ||
122 | + | ||
123 | + ctx.status = 200; | ||
124 | + ctx.body = bottle; | ||
125 | +} | ||
126 | + | ||
127 | +//약병의 ID를 찾아서 약의 정보를 등록 : Post | ||
128 | +exports.setMedicine = async(ctx) => { | ||
129 | + const token = ctx.req.headers.authorization; | ||
130 | + if(!token || !token.length) { | ||
131 | + ctx.status = 401; | ||
132 | + return; | ||
133 | + } | ||
134 | + | ||
135 | + const { userId } = jwt.verify(token, process.env.JWT_SECRET); | ||
136 | + const { bottleId } = ctx.params; | ||
137 | + const { medicineId, dosage } = ctx.request.body; | ||
138 | + | ||
139 | + const bottle = await Bottle.findByBottleId(bottleId); | ||
140 | + if(!bottle) { | ||
141 | + ctx.status = 404; | ||
142 | + return; | ||
143 | + } | ||
144 | + | ||
145 | + const hub = await Hub.findByHubId(bottle.getHubId()); | ||
146 | + if(hub.getHub_UserId() !== userId) { | ||
147 | + ctx.status = 403; | ||
148 | + return; | ||
149 | + } | ||
150 | + | ||
151 | + const medicine = await Medicine.findByMedicineId(medicineId); | ||
152 | + if(!medicine) { | ||
153 | + ctx.status = 404; | ||
154 | + return; | ||
155 | + } | ||
156 | + | ||
157 | + await Bottle.findOneAndUpdate({ | ||
158 | + bottleId | ||
159 | + }, { | ||
160 | + medicineId, | ||
161 | + dosage : parseInt(dosage) | ||
162 | + }); | ||
163 | + | ||
164 | + ctx.status = 200; | ||
165 | +} | ||
166 | + | ||
167 | +//로그인한 유저의 약병 리스트 가져오기 | ||
168 | +exports.getBottleList = async(ctx) => { | ||
169 | + const token = ctx.req.headers.authorization; | ||
170 | + if(!token || !token.length) { | ||
171 | + ctx.status = 401; | ||
172 | + return; | ||
173 | + } | ||
174 | + | ||
175 | + const { userId } = jwt.verify(token, process.env.JWT_SECRET); | ||
176 | + const { hubId } = ctx.params; | ||
177 | + | ||
178 | + const hub = await Hub.findByHubId(hubId); | ||
179 | + if(!hub) { | ||
180 | + ctx.status = 404; | ||
181 | + return; | ||
182 | + } | ||
183 | + | ||
184 | + if(hub.getHub_UserId() !== userId) { | ||
185 | + ctx.status = 403; | ||
186 | + return; | ||
187 | + } | ||
188 | + | ||
189 | + const bottleList = await Bottle.find({ hubId }); | ||
190 | + if(!bottleList || !bottleList.length) { | ||
191 | + ctx.status = 404; | ||
192 | + return; | ||
193 | + } | ||
194 | + | ||
195 | + ctx.status = 200; | ||
196 | + ctx.body = bottleList; | ||
197 | + | ||
198 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/src/api/bottle/index.js
0 → 100644
1 | +const Router = require('koa-router'); | ||
2 | +const bottleCtrl = require('./bottle.ctrl'); | ||
3 | + | ||
4 | +const bottle = new Router(); | ||
5 | + | ||
6 | +/** | ||
7 | + * 약병 연결 | ||
8 | + * request parameter : bottleId, hubId | ||
9 | + * url : http://localhost:4000/api/bottle | ||
10 | + * return : null | ||
11 | + */ | ||
12 | +bottle.post('/', bottleCtrl.bottleConnect); | ||
13 | + | ||
14 | +/** | ||
15 | + * 약병 연결 해제 | ||
16 | + * request parameter : x | ||
17 | + * url : http://localhost:4000/api/bottle/:bottleId | ||
18 | + * return : null | ||
19 | + */ | ||
20 | +bottle.delete('/:bottleId', bottleCtrl.bottleDisconnect); | ||
21 | + | ||
22 | +/** | ||
23 | + * 약병 정보 확인 | ||
24 | + * request parameter : x | ||
25 | + * url : http://localhost:4000/api/bottle/:bottleId | ||
26 | + * return : bottle(json type) | ||
27 | + */ | ||
28 | +bottle.get('/:bottleId', bottleCtrl.lookupInfo); | ||
29 | + | ||
30 | +/** | ||
31 | + * 약병에 약 등록 = 약 검색 후 약 ID(medicineId)와 복용 정보 보고 사용자가 약 복용량(dosage) 입력 | ||
32 | + * request parameter : medicineId, dosage | ||
33 | + * url : http://localhost:4000/api/bottle/:bottleId | ||
34 | + * return : bottle(json type) | ||
35 | + */ | ||
36 | +bottle.patch('/:bottleId', bottleCtrl.setMedicine); | ||
37 | + | ||
38 | +/** | ||
39 | + * 현재 로그인한 유저의 허브 중, 해당 허브에 등록된 약병 리스트를 가져옴 | ||
40 | + * request parameter : x | ||
41 | + * url : http://localhost:4000/api/bottle/hub/:hubId | ||
42 | + * return : bottle List(json type List) | ||
43 | + */ | ||
44 | +bottle.get('/hub/:hubId', bottleCtrl.getBottleList) | ||
45 | + | ||
46 | +module.exports = bottle; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/src/api/hub/hub.ctrl.js
0 → 100644
1 | +//허브(Mqtt Broker)등록 및 삭제 | ||
2 | +const Hub = require('../../models/hub'); | ||
3 | +const Mqtt = require('../../lib/MqttModule'); | ||
4 | +const DataProcess = require('../../lib/DataProcess'); | ||
5 | +const jwt = require('jsonwebtoken'); | ||
6 | + | ||
7 | +exports.hubConnect = async (ctx) => { | ||
8 | + const token = ctx.req.headers.authorization; | ||
9 | + if(!token || !token.length) { | ||
10 | + ctx.status = 401; | ||
11 | + return; | ||
12 | + } | ||
13 | + | ||
14 | + const { userId } = jwt.verify(token, process.env.JWT_SECRET); | ||
15 | + const { hubId, host, port } = ctx.request.body; | ||
16 | + | ||
17 | + const isExistHub = await Hub.findByHubId(hubId); | ||
18 | + if(isExistHub) { | ||
19 | + ctx.status = 409; | ||
20 | + return; | ||
21 | + } | ||
22 | + | ||
23 | + const hosting = { | ||
24 | + host, | ||
25 | + port | ||
26 | + }; | ||
27 | + | ||
28 | + Mqtt.mqttOn(hosting, DataProcess.dataPublish); | ||
29 | + | ||
30 | + const hub = new Hub({ | ||
31 | + hubId, | ||
32 | + hosting, | ||
33 | + userId | ||
34 | + }); | ||
35 | + | ||
36 | + await hub.save(); | ||
37 | + | ||
38 | + ctx.status = 201; | ||
39 | + ctx.body = hub; | ||
40 | +}; | ||
41 | + | ||
42 | +exports.getHubList = async(ctx) => { | ||
43 | + const token = ctx.req.headers.authorization; | ||
44 | + if(!token || !token.length) { | ||
45 | + ctx.status = 401; | ||
46 | + return; | ||
47 | + } | ||
48 | + | ||
49 | + const { userId } = jwt.verify(token, process.env.JWT_SECRET); | ||
50 | + const hubList = await Hub.find({ userId }); | ||
51 | + if(!hubList || !hubList.length) { | ||
52 | + ctx.status = 404; | ||
53 | + return; | ||
54 | + } | ||
55 | + | ||
56 | + ctx.status = 200; | ||
57 | + ctx.body = hubList; | ||
58 | +}; | ||
59 | + | ||
60 | +exports.hubDisconnect = async(ctx) => { | ||
61 | + const token = ctx.req.headers.authorization; | ||
62 | + if(!token || !token.length) { | ||
63 | + ctx.status = 401; | ||
64 | + return; | ||
65 | + } | ||
66 | + | ||
67 | + const { userId } = jwt.verify(token, process.env.JWT_SECRET); | ||
68 | + const { hubId } = ctx.params; | ||
69 | + | ||
70 | + const hub = await Hub.findByHubId(hubId); | ||
71 | + if(!hub) { | ||
72 | + ctx.status = 404; | ||
73 | + return; | ||
74 | + } | ||
75 | + if(hub.getHub_UserId() !== userId) { | ||
76 | + ctx.status = 403; | ||
77 | + return; | ||
78 | + } | ||
79 | + | ||
80 | + const hosting = await hub.getHubHost(); | ||
81 | + Mqtt.mqttOff(hosting); | ||
82 | + | ||
83 | + await Hub.deleteOne({ hubId }); | ||
84 | + | ||
85 | + ctx.status = 204; | ||
86 | +}; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/src/api/hub/index.js
0 → 100644
1 | +const Router = require('koa-router'); | ||
2 | +const hubCtrl = require('./hub.ctrl'); | ||
3 | + | ||
4 | +const hub = new Router(); | ||
5 | + | ||
6 | +/** | ||
7 | + * 허브 등록 | ||
8 | + * request parameter : hubId, host, port | ||
9 | + * url : http://localhost:4000/api/hub | ||
10 | + * return : hub(json type) | ||
11 | + */ | ||
12 | +hub.post('/', hubCtrl.hubConnect); | ||
13 | + | ||
14 | +/** | ||
15 | + * 로그인한 유저의 허브 목록 가져오기 | ||
16 | + * request parameter : X | ||
17 | + * url : http://localhost:4000/api/hub | ||
18 | + * return : hub List(json type) | ||
19 | + */ | ||
20 | +hub.get('/', hubCtrl.getHubList); | ||
21 | + | ||
22 | +/** | ||
23 | + * 허브 등록 해제 | ||
24 | + * request parameter : x | ||
25 | + * url : http://localhost:4000/api/hub/:hubId | ||
26 | + * return : null | ||
27 | + */ | ||
28 | +hub.delete('/:hubId', hubCtrl.hubDisconnect); | ||
29 | + | ||
30 | +module.exports = hub; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/src/api/index.js
0 → 100644
1 | +const Router = require('koa-router'); | ||
2 | +const auth = require('./auth'); | ||
3 | +const bottle = require('./bottle'); | ||
4 | +const hub = require('./hub'); | ||
5 | +const medicine = require('./medicine'); | ||
6 | + | ||
7 | +const api = new Router(); | ||
8 | + | ||
9 | +api.use('/auth', auth.routes()); | ||
10 | +api.use('/bottle', bottle.routes()); | ||
11 | +api.use('/hub', hub.routes()); | ||
12 | +api.use('/medicine', medicine.routes()); | ||
13 | + | ||
14 | +module.exports = api; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/src/api/medicine/index.js
0 → 100644
1 | +const Router = require('koa-router'); | ||
2 | +const medicineCtrl = require('./medicine.ctrl'); | ||
3 | + | ||
4 | +const medicine = new Router(); | ||
5 | + | ||
6 | +/** | ||
7 | + * 약 검색 후 검색 대상 가져오기 | ||
8 | + * request parameter : name, company, target 중 하나 | ||
9 | + * url : http://localhost:4000/api/medicine | ||
10 | + * return : medicine List(json 타입의 List) | ||
11 | + */ | ||
12 | +medicine.post('/', medicineCtrl.medicineSearch); | ||
13 | + | ||
14 | +/** | ||
15 | + * 약 검색 후 검색 대상 가져오기 | ||
16 | + * request parameter : x | ||
17 | + * url : http://localhost:4000/api/medicine/:mdedicineId | ||
18 | + * return : medicine(json type) | ||
19 | + */ | ||
20 | +medicine.get('/:medicineId', medicineCtrl.medicineGet); | ||
21 | + | ||
22 | +module.exports = medicine; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/src/api/medicine/medicine.ctrl.js
0 → 100644
1 | +//약의 정보를 검색하는 API : 약명, 제조사, 효능 | ||
2 | +const Medicine = require('../../models/medicine'); | ||
3 | + | ||
4 | +exports.medicineSearch = async(ctx) => { | ||
5 | + const token = ctx.req.headers.authorization; | ||
6 | + if(!token || !token.length) { | ||
7 | + ctx.status = 401; | ||
8 | + return; | ||
9 | + } | ||
10 | + | ||
11 | + const { name, company, target } = ctx.request.body; | ||
12 | + | ||
13 | + let result = []; | ||
14 | + | ||
15 | + if (name && name !== '' && name !== undefined) | ||
16 | + result = await medicineSearch_ByName(name); | ||
17 | + | ||
18 | + else if (company && company !== '' && company !== undefined) | ||
19 | + result = await medicineSearch_ByCompany(company); | ||
20 | + | ||
21 | + else if (target && target !== '' && target !== undefined) | ||
22 | + result = await medicineSearch_ByTarget(target); | ||
23 | + | ||
24 | + if(!result.length) { | ||
25 | + ctx.status = 404; | ||
26 | + return; | ||
27 | + } | ||
28 | + | ||
29 | + ctx.status = 200; | ||
30 | + ctx.body = result; | ||
31 | +} | ||
32 | + | ||
33 | +exports.medicineGet = async(ctx) => { | ||
34 | + const token = ctx.req.headers.authorization; | ||
35 | + if(!token || !token.length) { | ||
36 | + ctx.status = 401; | ||
37 | + return; | ||
38 | + } | ||
39 | + | ||
40 | + const { medicineId } = ctx.params; | ||
41 | + const medicine = await Medicine.findByMedicineId(medicineId); | ||
42 | + if(!medicine) { | ||
43 | + ctx.status = 404; | ||
44 | + return; | ||
45 | + } | ||
46 | + | ||
47 | + ctx.status = 200; | ||
48 | + ctx.body = medicine; | ||
49 | + | ||
50 | +} | ||
51 | + | ||
52 | +//이름으로 약 검색 | ||
53 | +const medicineSearch_ByName = async(name) => { | ||
54 | + const result = await Medicine.findByName(name); | ||
55 | + return result; | ||
56 | +} | ||
57 | + | ||
58 | +//제조사명으로 약 검색 | ||
59 | +const medicineSearch_ByCompany = async(company) => { | ||
60 | + const result = await Medicine.findByCompany(company); | ||
61 | + return result; | ||
62 | +} | ||
63 | + | ||
64 | +//타겟 병명으로 약 검색 | ||
65 | +const medicineSearch_ByTarget = async(target) => { | ||
66 | + const result = await Medicine.findByTarget(target); | ||
67 | + return result; | ||
68 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/src/lib/DataProcess.js
0 → 100644
1 | +const Bottle = require('../models/bottle'); | ||
2 | + | ||
3 | +//message subscribe 후 message를 가공한 이후 해당 데이터를 보낼 topic과 message를 리턴하는 함수 | ||
4 | +exports.dataPublish = async (topic, message) => { | ||
5 | + //client가 subscribe를 하면 메시지를 보낸 약병의 topic과 message를 가공 및 보낸 약병의 bottleId를 가져옴 | ||
6 | + const data = await factoring(topic, message); | ||
7 | + const { bottleId } = data; | ||
8 | + | ||
9 | + //가공된 데이터를 bottleId의 약병에 업데이트 | ||
10 | + await bottleInfoUpdate(data); | ||
11 | + //가공된 데이터를 메시지로 만들어 topic과 message 리턴 | ||
12 | + const result = await transPublishingTopicAndMessage(bottleId); | ||
13 | + | ||
14 | + return result; | ||
15 | + | ||
16 | +}; | ||
17 | + | ||
18 | +//Hub topic : bottle/bottleId | ||
19 | +//Hub로부터 받은 message : 개폐여부/온도/습도/초음파센서 | ||
20 | +const factoring = async (topic, message) => { | ||
21 | + const bottleId = parseInt(topic.split('/')[1]); | ||
22 | + const data = message.split('/'); | ||
23 | + let [isOpen, temperature, humidity, balance] = data; | ||
24 | + | ||
25 | + if(isOpen === '0') | ||
26 | + balance = await balanceFactoring(balance); | ||
27 | + else balance = '-1'; | ||
28 | + | ||
29 | + const openDate = new Date(); | ||
30 | + | ||
31 | + return { | ||
32 | + bottleId, | ||
33 | + isOpen, | ||
34 | + openDate, | ||
35 | + temperature, | ||
36 | + humidity, | ||
37 | + balance | ||
38 | + }; | ||
39 | + | ||
40 | +} | ||
41 | + | ||
42 | +const balanceFactoring = (balance) => { | ||
43 | + const max = 10; //Digital Lead Sensor Maximum Value | ||
44 | + const slicingBalance = max / 5; | ||
45 | + | ||
46 | + if(parseInt(balance) < slicingBalance || parseInt(balance) > max * 2) | ||
47 | + return '80'; | ||
48 | + else if(parseInt(balance) < slicingBalance * 2) | ||
49 | + return '60'; | ||
50 | + else if(parseInt(balance) < slicingBalance * 3) | ||
51 | + return '40'; | ||
52 | + else if(parseInt(balance) < slicingBalance * 4) | ||
53 | + return '20'; | ||
54 | + else return '0'; | ||
55 | + | ||
56 | +} | ||
57 | + | ||
58 | +//bottleId가 포함된 data를 받아서 해당 약병의 data를 업데이트한다. | ||
59 | +const bottleInfoUpdate = async(data) => { | ||
60 | + let { bottleId, isOpen, openDate, temperature, humidity, balance } = data; | ||
61 | + | ||
62 | + bottleId = parseInt(bottleId); | ||
63 | + isOpen = parseInt(isOpen); | ||
64 | + temperature = parseFloat(temperature); | ||
65 | + humidity = parseFloat(humidity); | ||
66 | + balance = parseInt(balance); | ||
67 | + | ||
68 | + if(isOpen) { | ||
69 | + await Bottle.findOneAndUpdate({ | ||
70 | + bottleId | ||
71 | + }, { recentOpen : openDate }); | ||
72 | + } | ||
73 | + | ||
74 | + if(balance !== -1) { | ||
75 | + await Bottle.findOneAndUpdate({ | ||
76 | + bottleId | ||
77 | + }, { balance }) | ||
78 | + } | ||
79 | + | ||
80 | + await Bottle.findOneAndUpdate({ | ||
81 | + bottleId | ||
82 | + }, { | ||
83 | + temperature, | ||
84 | + humidity | ||
85 | + }); | ||
86 | +} | ||
87 | + | ||
88 | +//해당 MQTT Broker(client)에 bottleId의 정보에 관한 topic과 message를 리턴한다. | ||
89 | +const transPublishingTopicAndMessage = async(bottleId) => { | ||
90 | + const topic = 'bottle/' + bottleId + '/stb'; | ||
91 | + | ||
92 | + const bottle = await Bottle.findByBottleId(bottleId); | ||
93 | + const recentOpen = bottle.getRecentOpenDate(); | ||
94 | + const dosage = bottle.getDosage(); | ||
95 | + | ||
96 | + const message = 'res/' + await transDate(recentOpen) + '/' + dosage; | ||
97 | + | ||
98 | + return { | ||
99 | + topic, | ||
100 | + message | ||
101 | + }; | ||
102 | +} | ||
103 | + | ||
104 | +//날짜를 mmdd로 변환해주는 함수 | ||
105 | +const transDate = (date) => { | ||
106 | + return (date.getMonth() + 1 < 10 ? '0' + String(date.getMonth() + 1) : String(date.getMonth() + 1)) | ||
107 | + + (date.getDate() < 10 ? '0' + String(date.getDate()) : String(date.getDate())); | ||
108 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/src/lib/MqttModule.js
0 → 100644
1 | +const mqtt = require('mqtt'); | ||
2 | +const clientList = []; | ||
3 | + | ||
4 | +exports.mqttOn = async (hosting, func) => { | ||
5 | + const filterIndex = clientList.findIndex(client => { | ||
6 | + return (client.options.clientId === hosting.clientId | ||
7 | + && client.options.host === hosting.host | ||
8 | + && client.options.port === hosting.port) | ||
9 | + }); | ||
10 | + | ||
11 | + if(filterIndex === -1) { | ||
12 | + const client = mqtt.connect(hosting); | ||
13 | + clientList.push(client); | ||
14 | + | ||
15 | + client.on('connect', () => { | ||
16 | + console.log(`Hub connected: `, client.connected); | ||
17 | + }); | ||
18 | + | ||
19 | + client.on('message', async (topic, message, packet) => { | ||
20 | + const result = await func(topic, message.toString()); | ||
21 | + console.log('\x1b[1;32msubscribe : topic', topic, 'message : ', message.toString(), '\x1b[0m'); | ||
22 | + this.mqttPublishMessage(client, result); | ||
23 | + }); | ||
24 | + | ||
25 | + return client; | ||
26 | + } | ||
27 | + | ||
28 | + return clientList[filterIndex]; | ||
29 | +}; | ||
30 | + | ||
31 | +exports.mqttSubscribe = (client, topic) => { | ||
32 | + client.subscribe(topic); | ||
33 | +}; | ||
34 | + | ||
35 | +exports.mqttPublishMessage = (client, { topic, message }) => { | ||
36 | + client.publish(topic, message, () => { | ||
37 | + console.log('\x1b[1;33mpublish : topic', topic, 'message : ', message, '\x1b[0m'); | ||
38 | + }); | ||
39 | +}; | ||
40 | + | ||
41 | +exports.mqttUnsubscribe = (client, topic) => { | ||
42 | + client.unsubscribe(topic, () => { | ||
43 | + console.log('unsubscribe', topic); | ||
44 | + }); | ||
45 | +}; | ||
46 | + | ||
47 | +exports.mqttOff = (hosting) => { | ||
48 | + const filterIndex = clientList.findIndex(client => { | ||
49 | + return (client.options.clientId === hosting.clientId | ||
50 | + && client.options.host === hosting.host | ||
51 | + && client.options.port === hosting.port) | ||
52 | + }); | ||
53 | + | ||
54 | + if(filterIndex !== -1) { | ||
55 | + clientList[filterIndex].end(); | ||
56 | + clientList.splice(filterIndex, 1); | ||
57 | + } | ||
58 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/src/lib/UpdatingMedicineInfo.js
0 → 100644
1 | +const axios = require('axios'); | ||
2 | +const Medicine = require('../models/medicine'); | ||
3 | + | ||
4 | +exports.updateMedicineInfo = async() => { | ||
5 | + const itemArray = await getItemsList(getQueryURL); | ||
6 | + await exportJsonData(itemArray); | ||
7 | + | ||
8 | + console.log('\x1b[1;35mAll of data is updated!\x1b[0m'); | ||
9 | +} | ||
10 | + | ||
11 | +//queryUrl을 return하는 함수 : 한 페이지에 100개의 item씩 요청할 수 있다. | ||
12 | +const getQueryURL = (i) => { | ||
13 | + const url = "http://apis.data.go.kr/1471000/DrbEasyDrugInfoService/getDrbEasyDrugList"; | ||
14 | + const queryParams = '?' + encodeURIComponent('ServiceKey') + '=' + process.env.SERVICE_KEY; | ||
15 | + const pageNum = '&' + encodeURIComponent('pageNo') + '=' + encodeURIComponent(i); | ||
16 | + const numOfItem = '&' + encodeURIComponent('numOfRows') + '=' + encodeURIComponent(100); | ||
17 | + const output = '&' + encodeURIComponent('type') + '=' + encodeURIComponent('json'); | ||
18 | + | ||
19 | + return url + queryParams + pageNum + numOfItem + output; | ||
20 | +} | ||
21 | + | ||
22 | +//모든 page의 item을 list에 push해서 return하는 함수 | ||
23 | +const getItemsList = async(queryUrl) => { | ||
24 | + let i = 1, getItem = null, items = null; | ||
25 | + const result = []; | ||
26 | + | ||
27 | + while(true) { | ||
28 | + getItem = await axios.get(queryUrl(i)); | ||
29 | + items = getItem.data.body.items; | ||
30 | + | ||
31 | + if(items === undefined) | ||
32 | + return result; | ||
33 | + | ||
34 | + result.push(...items); | ||
35 | + console.log('\x1b[100mmedicine data getting processing... : page', i, 'done\x1b[0m'); | ||
36 | + i++; | ||
37 | + } | ||
38 | +} | ||
39 | + | ||
40 | +//itemArray에 있는 모든 data를 MongoDB의 SMB collections에 저장함 | ||
41 | +const exportJsonData = (itemList) => { | ||
42 | + itemList.forEach(async item => { | ||
43 | + const medicineId = item.itemSeq; | ||
44 | + const medicineInfo = { | ||
45 | + name : item.itemName, | ||
46 | + company : item.entpName, | ||
47 | + target : await slicingInfo(item.efcyQesitm), | ||
48 | + dosage : await slicingInfo(item.useMethodQesitm), | ||
49 | + warn : await slicingInfo(item.atpnWarnQesitm ? | ||
50 | + item.atpnWarnQesitm + '\n' + item.atpnQesitm | ||
51 | + : item.atpnQesitm), | ||
52 | + antiEffect : await slicingInfo(item.seQesitm) | ||
53 | + }; | ||
54 | + | ||
55 | + Medicine.findOneAndUpdate({ | ||
56 | + medicineId | ||
57 | + }, medicineInfo, { | ||
58 | + upsert : true | ||
59 | + }).exec(); | ||
60 | + }) | ||
61 | +} | ||
62 | + | ||
63 | +//복용 정보에서 불필요한 태그를 제거하고 제거된 값을 반환한다. | ||
64 | +const slicingInfo = async (info) => { | ||
65 | + let result = info; | ||
66 | + | ||
67 | + if(info) { | ||
68 | + result = await info.split('<p>').join('') | ||
69 | + .split('</p>').join('') | ||
70 | + .split('<sup>').join('') | ||
71 | + .split('</sup>').join('') | ||
72 | + .split('null').join(''); | ||
73 | + } | ||
74 | + | ||
75 | + return result; | ||
76 | +} | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/src/lib/jwtMiddleWare.js
0 → 100644
1 | +const jwt = require("jsonwebtoken"); | ||
2 | +const User = require('../models/user'); | ||
3 | + | ||
4 | +const jwtMiddleware = async (ctx, next) => { | ||
5 | + const token = ctx.cookies.get("access_token"); | ||
6 | + if(!token) { | ||
7 | + return next(); | ||
8 | + } | ||
9 | + | ||
10 | + try { | ||
11 | + const decoded = jwt.verify(token, process.env.JWT_SECRET); | ||
12 | + ctx.state.user = { | ||
13 | + _id : decoded._id, | ||
14 | + userId : decoded.userId | ||
15 | + }; | ||
16 | + const now = Math.floor(Date.now() / 1000); | ||
17 | + if (decoded.exp - now < 60 * 60 * 24 * 7) { | ||
18 | + const user = await User.findById(decoded._id); | ||
19 | + const token = user.generateToken(); | ||
20 | + | ||
21 | + ctx.cookies.set('access_token', token, { | ||
22 | + httpOnly : true, | ||
23 | + maxAge : 1000 * 60 * 60 * 24 * 30 | ||
24 | + }) | ||
25 | + } | ||
26 | + | ||
27 | + } catch(e) { | ||
28 | + ctx.state.user = null; | ||
29 | + } | ||
30 | + | ||
31 | + return next(); | ||
32 | + | ||
33 | +}; | ||
34 | + | ||
35 | +module.exports = jwtMiddleware; |
server/src/models/bottle.js
0 → 100644
1 | +const mongoose = require('mongoose'); | ||
2 | + | ||
3 | +const Schema = mongoose.Schema; | ||
4 | + | ||
5 | +const BottleSchema = new Schema ({ | ||
6 | + bottleId : { type : Number, required : true, unique : true }, | ||
7 | + temperature : { type : Number, default : 0 }, | ||
8 | + humidity : { type : Number, default : 0 }, | ||
9 | + balance : { type : Number, default : 0 }, | ||
10 | + recentOpen : { type : Date, default : Date.now }, | ||
11 | + medicineId : { type : Number, default : null, }, | ||
12 | + hubId : Number, | ||
13 | + dosage : { type : Number, default : 0 } | ||
14 | +}) | ||
15 | + | ||
16 | +BottleSchema.statics.findByBottleId = function(bottleId) { | ||
17 | + return this.findOne({ bottleId }); | ||
18 | +}; | ||
19 | + | ||
20 | +BottleSchema.methods.getBottleId = function() { | ||
21 | + return this.bottleId; | ||
22 | +}; | ||
23 | + | ||
24 | +BottleSchema.methods.getRecentOpenDate = function() { | ||
25 | + return this.recentOpen; | ||
26 | +}; | ||
27 | + | ||
28 | +BottleSchema.methods.getTemperature = function() { | ||
29 | + return this.temperature; | ||
30 | +}; | ||
31 | + | ||
32 | +BottleSchema.methods.getHumidity = function() { | ||
33 | + return this.humidity; | ||
34 | +}; | ||
35 | + | ||
36 | +BottleSchema.methods.getBalance = function() { | ||
37 | + return this.balance; | ||
38 | +}; | ||
39 | + | ||
40 | +BottleSchema.methods.getDosage = function() { | ||
41 | + return this.dosage; | ||
42 | +}; | ||
43 | + | ||
44 | +BottleSchema.methods.getMedicineId = function() { | ||
45 | + return this.medicineId; | ||
46 | +}; | ||
47 | + | ||
48 | +BottleSchema.methods.getHubId = function() { | ||
49 | + return this.hubId; | ||
50 | +}; | ||
51 | + | ||
52 | +module.exports = mongoose.model('Bottle', BottleSchema); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/src/models/hub.js
0 → 100644
1 | +const mongoose = require('mongoose'); | ||
2 | + | ||
3 | +const Schema = mongoose.Schema; | ||
4 | + | ||
5 | +const HubSchema = new Schema ({ | ||
6 | + hubId : { type : Number, required : true, unique : true }, | ||
7 | + hosting : { type : Object, default : null }, | ||
8 | + userId : { type : String, default : null }, | ||
9 | +}); | ||
10 | + | ||
11 | +HubSchema.statics.findByHubId = function(hubId) { | ||
12 | + return this.findOne({ hubId }) | ||
13 | +}; | ||
14 | + | ||
15 | +HubSchema.methods.setHubHost = function(hosting) { | ||
16 | + this.hosting = hosting; | ||
17 | +}; | ||
18 | + | ||
19 | +HubSchema.methods.getHubHost = function() { | ||
20 | + return this.hosting; | ||
21 | +}; | ||
22 | + | ||
23 | +HubSchema.methods.setHub_UserId = function(userId) { | ||
24 | + this.userId = userId; | ||
25 | +}; | ||
26 | + | ||
27 | +HubSchema.methods.getHub_UserId = function() { | ||
28 | + return this.userId; | ||
29 | +}; | ||
30 | + | ||
31 | +module.exports = mongoose.model('Hub', HubSchema); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/src/models/medicine.js
0 → 100644
1 | +const mongoose = require('mongoose'); | ||
2 | + | ||
3 | +const Schema = mongoose.Schema; | ||
4 | + | ||
5 | +const MedicineSchema = new Schema ({ | ||
6 | + medicineId : { type : Number, required : true, unique : true }, | ||
7 | + name : { type : String, required : true }, | ||
8 | + company : String, | ||
9 | + target : { type : String, required : true }, | ||
10 | + dosage : { type : String, required : true }, | ||
11 | + warn : { type : String, required : true }, | ||
12 | + antiEffect : { type : String, required : true } | ||
13 | +}) | ||
14 | + | ||
15 | +MedicineSchema.statics.findByName = async function(name) { | ||
16 | + const all = await this.find().exec(); | ||
17 | + const result = all.filter(item => { | ||
18 | + return item.name.includes(name) | ||
19 | + }); | ||
20 | + | ||
21 | + return result; | ||
22 | +}; | ||
23 | + | ||
24 | +MedicineSchema.statics.findByCompany = async function(company) { | ||
25 | + const all = await this.find().exec(); | ||
26 | + const result = all.filter(item => { | ||
27 | + return item.company.includes(company) | ||
28 | + }); | ||
29 | + | ||
30 | + return result; | ||
31 | +}; | ||
32 | + | ||
33 | +MedicineSchema.statics.findByTarget = async function(target) { | ||
34 | + const all = await this.find().exec(); | ||
35 | + const result = all.filter(item => { | ||
36 | + return item.target.includes(target) | ||
37 | + }); | ||
38 | + | ||
39 | + return result; | ||
40 | +}; | ||
41 | + | ||
42 | +MedicineSchema.statics.findByMedicineId = function(medicineId) { | ||
43 | + return this.findOne({ medicineId }) | ||
44 | +}; | ||
45 | + | ||
46 | + | ||
47 | +module.exports = mongoose.model('Medicine', MedicineSchema); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/src/models/user.js
0 → 100644
1 | +const mongoose = require('mongoose'); | ||
2 | +const bcrypt = require('bcrypt'); | ||
3 | +const jwt = require('jsonwebtoken'); | ||
4 | + | ||
5 | +const Schema = mongoose.Schema; | ||
6 | + | ||
7 | +const UserSchema = new Schema({ | ||
8 | + userId : { type: String, require : true, unique : true, lowercase : true }, | ||
9 | + hashedPassword : { type : String, default : null } | ||
10 | +}); | ||
11 | + | ||
12 | +UserSchema.methods.setPassword = async function(password) { | ||
13 | + const hash = await bcrypt.hash(password, 10); | ||
14 | + this.hashedPassword = hash; | ||
15 | +}; | ||
16 | + | ||
17 | +UserSchema.methods.checkPassword = async function(password) { | ||
18 | + const result = await bcrypt.compare(password, this.hashedPassword) | ||
19 | + return result; | ||
20 | +}; | ||
21 | + | ||
22 | +UserSchema.statics.findByUserId = async function(userId) { | ||
23 | + return this.findOne({ userId }); | ||
24 | +}; | ||
25 | + | ||
26 | +UserSchema.methods.generateToken = function() { | ||
27 | + const token = jwt.sign ( | ||
28 | + { | ||
29 | + _id : this._id, | ||
30 | + userId : this.userId | ||
31 | + }, | ||
32 | + process.env.JWT_SECRET, | ||
33 | + { expiresIn : '30d' } | ||
34 | + ); | ||
35 | + return token; | ||
36 | +}; | ||
37 | + | ||
38 | +module.exports = mongoose.model("User", UserSchema); | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
server/src/util/MqttServer.js
0 → 100644
1 | +const Mqtt = require('../lib/MqttModule'); | ||
2 | +const DataProcess = require('../lib/DataProcess'); | ||
3 | +const Hub = require('../models/hub'); | ||
4 | +const Bottle = require('../models/bottle'); | ||
5 | + | ||
6 | +exports.on = async() => { | ||
7 | + await subscribeOn(); | ||
8 | + console.log('\x1b[1;34mMQTT Server On\x1b[0m'); | ||
9 | +}; | ||
10 | + | ||
11 | +const subscribeOn = async () => { | ||
12 | + const bottleList = await Bottle.find(); | ||
13 | + | ||
14 | + bottleList.forEach(async(bottle) => { | ||
15 | + const topic = 'bottle/' + bottle.getBottleId() + '/bts'; | ||
16 | + const hub = await Hub.findByHubId(bottle.getHubId()); | ||
17 | + const client = await Mqtt.mqttOn(hub.getHubHost(), DataProcess.dataPublish); | ||
18 | + Mqtt.mqttSubscribe(client, topic); | ||
19 | + }) | ||
20 | +}; | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
-
Please register or login to post a comment