├── .env.example
├── .gitignore
├── .gitmodules
├── .jshintrc
├── LICENSE.md
├── README.md
├── data
└── .gitkeep
├── inc
├── blacklist.js
├── db.js
└── utils.js
├── index.js
├── models
├── Gym.js
├── GymMember.js
├── GymPokemon.js
├── Pokemon.js
├── Pokestop.js
└── Raid.js
├── package-lock.json
├── package.json
└── routes
├── captcha.js
├── index.js
└── raw_data.js
/.env.example:
--------------------------------------------------------------------------------
1 | ################
2 | # Application. #
3 | ################
4 |
5 | # Specify enabled logging, separated via comma. Use * for wildcard.
6 | # Supported options:
7 | # devkat - All devkat logs.
8 | # devkat:db - Database & query logs.
9 | # devkat:db:pokemon - Pokémon DB logs.
10 | # devkat:db:pokemon:sql - Raw SQL query logs.
11 | # devkat:master - Process manager & general status.
12 | # devkat:routes - Webserver routing logs.
13 | # devkat:routes:raw_data - Webserver logs for /raw_data.
14 | #
15 | # Examples:
16 | # DEBUG=* - Enable all logging.
17 | # DEBUG=devkat:master - Only devkat status/process manager logging.
18 | # DEBUG=devkat:*,-devkat:routes* - Exclude webserver routes from logging.
19 | DEBUG=devkat*,-devkat:db:*:sql
20 |
21 | # Limits per query to /raw_data.
22 | POKEMON_LIMIT_PER_QUERY=50000
23 | POKESTOP_LIMIT_PER_QUERY=50000
24 | GYM_LIMIT_PER_QUERY=50000
25 |
26 |
27 | ##############
28 | # Webserver. #
29 | ##############
30 |
31 | # Webserver host IP to bind to. 127.0.0.1 for local (recommended with nginx
32 | # reverse proxy), and 0.0.0.0 binds to all interfaces.
33 | WEB_HOST=127.0.0.1
34 |
35 | # Webserver port.
36 | WEB_PORT=1337
37 |
38 | # Enable gzip compression.
39 | ENABLE_GZIP=true
40 |
41 | # Set up IP(s)/domain(s) to allow CORS for, via comma-separated list.
42 | CORS_WHITELIST=http://127.0.0.1,https://localhost
43 |
44 | # HTTPS key file paths.
45 | ENABLE_HTTPS=false
46 | HTTPS_KEY_PATH=privkey.pem
47 | HTTPS_CERT_PATH=cert.pem
48 |
49 | # Enable/disable webserver request throttling.
50 | ENABLE_THROTTLE=true
51 | # Rate limit: requests per second.
52 | THROTTLE_RATE=10
53 | # Allow user to temporarily go over the rate limit, up to the burst limit.
54 | THROTTLE_BURST=20
55 |
56 |
57 | #############
58 | # Database. #
59 | #############
60 |
61 | # MySQL only.
62 | DB_HOST=localhost
63 | DB_PORT=3306
64 | DB_USER=root
65 | DB_PASS=
66 | DB_DATABASE=database
67 |
68 | # Amount of max. DB connections. If your system allows for high concurrency,
69 | # make sure you have enough connections in the pool.
70 | DB_POOL_MAX_SIZE=10
71 |
72 |
73 | ########################################################################
74 | # Warning: only customize below options if you know what you're doing. #
75 | ########################################################################
76 |
77 | # Overwrite environment for end users, in case they forget.
78 | # Don't touch this!
79 | NODE_ENV=production
80 |
81 |
82 | ##############
83 | # Webserver. #
84 | ##############
85 |
86 | # Use built-in process management with cluster.
87 | ENABLE_CLUSTER=true
88 |
89 | # Number of web serving workers. Leave this commented to use your CPU count,
90 | # which is the optimal amount in nearly all cases.
91 | #WEB_WORKERS=1
92 |
93 | # Automatically restart workers if they've crashed.
94 | AUTORESTART_WORKERS=true
95 |
96 |
97 | ###################
98 | # Webserver load. #
99 | ###################
100 |
101 | # Enable/disable the load limiter.
102 | ENABLE_LOAD_LIMITER=true
103 |
104 | # Enable/disable logging when load limiter kicks in on a worker.
105 | ENABLE_LOAD_LIMITER_LOGGING=false
106 |
107 | # Represents the maximum amount of time in milliseconds that the event queue
108 | # is behind, before we consider the process "too busy".
109 | MAX_LAG_MS=150
110 |
111 | # The check interval for measuring event loop lag, in milliseconds.
112 | LAG_INTERVAL_MS=500
113 |
114 |
115 | ############
116 | # Routing. #
117 | ############
118 |
119 | # Remap the webserver's routing.
120 | ROUTE_RAW_DATA=/raw_data
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "data/pokedex"]
2 | path = data/pokedex
3 | url = https://github.com/sebastienvercammen/Pokemon-Go-Pokedex.git
4 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "node": true,
3 | "esnext": true
4 | }
5 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | GNU AFFERO GENERAL PUBLIC LICENSE
2 | Version 3, 19 November 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU Affero General Public License is a free, copyleft license for
11 | software and other kinds of works, specifically designed to ensure
12 | cooperation with the community in the case of network server software.
13 |
14 | The licenses for most software and other practical works are designed
15 | to take away your freedom to share and change the works. By contrast,
16 | our General Public Licenses are intended to guarantee your freedom to
17 | share and change all versions of a program--to make sure it remains free
18 | software for all its users.
19 |
20 | When we speak of free software, we are referring to freedom, not
21 | price. Our General Public Licenses are designed to make sure that you
22 | have the freedom to distribute copies of free software (and charge for
23 | them if you wish), that you receive source code or can get it if you
24 | want it, that you can change the software or use pieces of it in new
25 | free programs, and that you know you can do these things.
26 |
27 | Developers that use our General Public Licenses protect your rights
28 | with two steps: (1) assert copyright on the software, and (2) offer
29 | you this License which gives you legal permission to copy, distribute
30 | and/or modify the software.
31 |
32 | A secondary benefit of defending all users' freedom is that
33 | improvements made in alternate versions of the program, if they
34 | receive widespread use, become available for other developers to
35 | incorporate. Many developers of free software are heartened and
36 | encouraged by the resulting cooperation. However, in the case of
37 | software used on network servers, this result may fail to come about.
38 | The GNU General Public License permits making a modified version and
39 | letting the public access it on a server without ever releasing its
40 | source code to the public.
41 |
42 | The GNU Affero General Public License is designed specifically to
43 | ensure that, in such cases, the modified source code becomes available
44 | to the community. It requires the operator of a network server to
45 | provide the source code of the modified version running there to the
46 | users of that server. Therefore, public use of a modified version, on
47 | a publicly accessible server, gives the public access to the source
48 | code of the modified version.
49 |
50 | An older license, called the Affero General Public License and
51 | published by Affero, was designed to accomplish similar goals. This is
52 | a different license, not a version of the Affero GPL, but Affero has
53 | released a new version of the Affero GPL which permits relicensing under
54 | this license.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | TERMS AND CONDITIONS
60 |
61 | 0. Definitions.
62 |
63 | "This License" refers to version 3 of the GNU Affero General Public License.
64 |
65 | "Copyright" also means copyright-like laws that apply to other kinds of
66 | works, such as semiconductor masks.
67 |
68 | "The Program" refers to any copyrightable work licensed under this
69 | License. Each licensee is addressed as "you". "Licensees" and
70 | "recipients" may be individuals or organizations.
71 |
72 | To "modify" a work means to copy from or adapt all or part of the work
73 | in a fashion requiring copyright permission, other than the making of an
74 | exact copy. The resulting work is called a "modified version" of the
75 | earlier work or a work "based on" the earlier work.
76 |
77 | A "covered work" means either the unmodified Program or a work based
78 | on the Program.
79 |
80 | To "propagate" a work means to do anything with it that, without
81 | permission, would make you directly or secondarily liable for
82 | infringement under applicable copyright law, except executing it on a
83 | computer or modifying a private copy. Propagation includes copying,
84 | distribution (with or without modification), making available to the
85 | public, and in some countries other activities as well.
86 |
87 | To "convey" a work means any kind of propagation that enables other
88 | parties to make or receive copies. Mere interaction with a user through
89 | a computer network, with no transfer of a copy, is not conveying.
90 |
91 | An interactive user interface displays "Appropriate Legal Notices"
92 | to the extent that it includes a convenient and prominently visible
93 | feature that (1) displays an appropriate copyright notice, and (2)
94 | tells the user that there is no warranty for the work (except to the
95 | extent that warranties are provided), that licensees may convey the
96 | work under this License, and how to view a copy of this License. If
97 | the interface presents a list of user commands or options, such as a
98 | menu, a prominent item in the list meets this criterion.
99 |
100 | 1. Source Code.
101 |
102 | The "source code" for a work means the preferred form of the work
103 | for making modifications to it. "Object code" means any non-source
104 | form of a work.
105 |
106 | A "Standard Interface" means an interface that either is an official
107 | standard defined by a recognized standards body, or, in the case of
108 | interfaces specified for a particular programming language, one that
109 | is widely used among developers working in that language.
110 |
111 | The "System Libraries" of an executable work include anything, other
112 | than the work as a whole, that (a) is included in the normal form of
113 | packaging a Major Component, but which is not part of that Major
114 | Component, and (b) serves only to enable use of the work with that
115 | Major Component, or to implement a Standard Interface for which an
116 | implementation is available to the public in source code form. A
117 | "Major Component", in this context, means a major essential component
118 | (kernel, window system, and so on) of the specific operating system
119 | (if any) on which the executable work runs, or a compiler used to
120 | produce the work, or an object code interpreter used to run it.
121 |
122 | The "Corresponding Source" for a work in object code form means all
123 | the source code needed to generate, install, and (for an executable
124 | work) run the object code and to modify the work, including scripts to
125 | control those activities. However, it does not include the work's
126 | System Libraries, or general-purpose tools or generally available free
127 | programs which are used unmodified in performing those activities but
128 | which are not part of the work. For example, Corresponding Source
129 | includes interface definition files associated with source files for
130 | the work, and the source code for shared libraries and dynamically
131 | linked subprograms that the work is specifically designed to require,
132 | such as by intimate data communication or control flow between those
133 | subprograms and other parts of the work.
134 |
135 | The Corresponding Source need not include anything that users
136 | can regenerate automatically from other parts of the Corresponding
137 | Source.
138 |
139 | The Corresponding Source for a work in source code form is that
140 | same work.
141 |
142 | 2. Basic Permissions.
143 |
144 | All rights granted under this License are granted for the term of
145 | copyright on the Program, and are irrevocable provided the stated
146 | conditions are met. This License explicitly affirms your unlimited
147 | permission to run the unmodified Program. The output from running a
148 | covered work is covered by this License only if the output, given its
149 | content, constitutes a covered work. This License acknowledges your
150 | rights of fair use or other equivalent, as provided by copyright law.
151 |
152 | You may make, run and propagate covered works that you do not
153 | convey, without conditions so long as your license otherwise remains
154 | in force. You may convey covered works to others for the sole purpose
155 | of having them make modifications exclusively for you, or provide you
156 | with facilities for running those works, provided that you comply with
157 | the terms of this License in conveying all material for which you do
158 | not control copyright. Those thus making or running the covered works
159 | for you must do so exclusively on your behalf, under your direction
160 | and control, on terms that prohibit them from making any copies of
161 | your copyrighted material outside their relationship with you.
162 |
163 | Conveying under any other circumstances is permitted solely under
164 | the conditions stated below. Sublicensing is not allowed; section 10
165 | makes it unnecessary.
166 |
167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168 |
169 | No covered work shall be deemed part of an effective technological
170 | measure under any applicable law fulfilling obligations under article
171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172 | similar laws prohibiting or restricting circumvention of such
173 | measures.
174 |
175 | When you convey a covered work, you waive any legal power to forbid
176 | circumvention of technological measures to the extent such circumvention
177 | is effected by exercising rights under this License with respect to
178 | the covered work, and you disclaim any intention to limit operation or
179 | modification of the work as a means of enforcing, against the work's
180 | users, your or third parties' legal rights to forbid circumvention of
181 | technological measures.
182 |
183 | 4. Conveying Verbatim Copies.
184 |
185 | You may convey verbatim copies of the Program's source code as you
186 | receive it, in any medium, provided that you conspicuously and
187 | appropriately publish on each copy an appropriate copyright notice;
188 | keep intact all notices stating that this License and any
189 | non-permissive terms added in accord with section 7 apply to the code;
190 | keep intact all notices of the absence of any warranty; and give all
191 | recipients a copy of this License along with the Program.
192 |
193 | You may charge any price or no price for each copy that you convey,
194 | and you may offer support or warranty protection for a fee.
195 |
196 | 5. Conveying Modified Source Versions.
197 |
198 | You may convey a work based on the Program, or the modifications to
199 | produce it from the Program, in the form of source code under the
200 | terms of section 4, provided that you also meet all of these conditions:
201 |
202 | a) The work must carry prominent notices stating that you modified
203 | it, and giving a relevant date.
204 |
205 | b) The work must carry prominent notices stating that it is
206 | released under this License and any conditions added under section
207 | 7. This requirement modifies the requirement in section 4 to
208 | "keep intact all notices".
209 |
210 | c) You must license the entire work, as a whole, under this
211 | License to anyone who comes into possession of a copy. This
212 | License will therefore apply, along with any applicable section 7
213 | additional terms, to the whole of the work, and all its parts,
214 | regardless of how they are packaged. This License gives no
215 | permission to license the work in any other way, but it does not
216 | invalidate such permission if you have separately received it.
217 |
218 | d) If the work has interactive user interfaces, each must display
219 | Appropriate Legal Notices; however, if the Program has interactive
220 | interfaces that do not display Appropriate Legal Notices, your
221 | work need not make them do so.
222 |
223 | A compilation of a covered work with other separate and independent
224 | works, which are not by their nature extensions of the covered work,
225 | and which are not combined with it such as to form a larger program,
226 | in or on a volume of a storage or distribution medium, is called an
227 | "aggregate" if the compilation and its resulting copyright are not
228 | used to limit the access or legal rights of the compilation's users
229 | beyond what the individual works permit. Inclusion of a covered work
230 | in an aggregate does not cause this License to apply to the other
231 | parts of the aggregate.
232 |
233 | 6. Conveying Non-Source Forms.
234 |
235 | You may convey a covered work in object code form under the terms
236 | of sections 4 and 5, provided that you also convey the
237 | machine-readable Corresponding Source under the terms of this License,
238 | in one of these ways:
239 |
240 | a) Convey the object code in, or embodied in, a physical product
241 | (including a physical distribution medium), accompanied by the
242 | Corresponding Source fixed on a durable physical medium
243 | customarily used for software interchange.
244 |
245 | b) Convey the object code in, or embodied in, a physical product
246 | (including a physical distribution medium), accompanied by a
247 | written offer, valid for at least three years and valid for as
248 | long as you offer spare parts or customer support for that product
249 | model, to give anyone who possesses the object code either (1) a
250 | copy of the Corresponding Source for all the software in the
251 | product that is covered by this License, on a durable physical
252 | medium customarily used for software interchange, for a price no
253 | more than your reasonable cost of physically performing this
254 | conveying of source, or (2) access to copy the
255 | Corresponding Source from a network server at no charge.
256 |
257 | c) Convey individual copies of the object code with a copy of the
258 | written offer to provide the Corresponding Source. This
259 | alternative is allowed only occasionally and noncommercially, and
260 | only if you received the object code with such an offer, in accord
261 | with subsection 6b.
262 |
263 | d) Convey the object code by offering access from a designated
264 | place (gratis or for a charge), and offer equivalent access to the
265 | Corresponding Source in the same way through the same place at no
266 | further charge. You need not require recipients to copy the
267 | Corresponding Source along with the object code. If the place to
268 | copy the object code is a network server, the Corresponding Source
269 | may be on a different server (operated by you or a third party)
270 | that supports equivalent copying facilities, provided you maintain
271 | clear directions next to the object code saying where to find the
272 | Corresponding Source. Regardless of what server hosts the
273 | Corresponding Source, you remain obligated to ensure that it is
274 | available for as long as needed to satisfy these requirements.
275 |
276 | e) Convey the object code using peer-to-peer transmission, provided
277 | you inform other peers where the object code and Corresponding
278 | Source of the work are being offered to the general public at no
279 | charge under subsection 6d.
280 |
281 | A separable portion of the object code, whose source code is excluded
282 | from the Corresponding Source as a System Library, need not be
283 | included in conveying the object code work.
284 |
285 | A "User Product" is either (1) a "consumer product", which means any
286 | tangible personal property which is normally used for personal, family,
287 | or household purposes, or (2) anything designed or sold for incorporation
288 | into a dwelling. In determining whether a product is a consumer product,
289 | doubtful cases shall be resolved in favor of coverage. For a particular
290 | product received by a particular user, "normally used" refers to a
291 | typical or common use of that class of product, regardless of the status
292 | of the particular user or of the way in which the particular user
293 | actually uses, or expects or is expected to use, the product. A product
294 | is a consumer product regardless of whether the product has substantial
295 | commercial, industrial or non-consumer uses, unless such uses represent
296 | the only significant mode of use of the product.
297 |
298 | "Installation Information" for a User Product means any methods,
299 | procedures, authorization keys, or other information required to install
300 | and execute modified versions of a covered work in that User Product from
301 | a modified version of its Corresponding Source. The information must
302 | suffice to ensure that the continued functioning of the modified object
303 | code is in no case prevented or interfered with solely because
304 | modification has been made.
305 |
306 | If you convey an object code work under this section in, or with, or
307 | specifically for use in, a User Product, and the conveying occurs as
308 | part of a transaction in which the right of possession and use of the
309 | User Product is transferred to the recipient in perpetuity or for a
310 | fixed term (regardless of how the transaction is characterized), the
311 | Corresponding Source conveyed under this section must be accompanied
312 | by the Installation Information. But this requirement does not apply
313 | if neither you nor any third party retains the ability to install
314 | modified object code on the User Product (for example, the work has
315 | been installed in ROM).
316 |
317 | The requirement to provide Installation Information does not include a
318 | requirement to continue to provide support service, warranty, or updates
319 | for a work that has been modified or installed by the recipient, or for
320 | the User Product in which it has been modified or installed. Access to a
321 | network may be denied when the modification itself materially and
322 | adversely affects the operation of the network or violates the rules and
323 | protocols for communication across the network.
324 |
325 | Corresponding Source conveyed, and Installation Information provided,
326 | in accord with this section must be in a format that is publicly
327 | documented (and with an implementation available to the public in
328 | source code form), and must require no special password or key for
329 | unpacking, reading or copying.
330 |
331 | 7. Additional Terms.
332 |
333 | "Additional permissions" are terms that supplement the terms of this
334 | License by making exceptions from one or more of its conditions.
335 | Additional permissions that are applicable to the entire Program shall
336 | be treated as though they were included in this License, to the extent
337 | that they are valid under applicable law. If additional permissions
338 | apply only to part of the Program, that part may be used separately
339 | under those permissions, but the entire Program remains governed by
340 | this License without regard to the additional permissions.
341 |
342 | When you convey a copy of a covered work, you may at your option
343 | remove any additional permissions from that copy, or from any part of
344 | it. (Additional permissions may be written to require their own
345 | removal in certain cases when you modify the work.) You may place
346 | additional permissions on material, added by you to a covered work,
347 | for which you have or can give appropriate copyright permission.
348 |
349 | Notwithstanding any other provision of this License, for material you
350 | add to a covered work, you may (if authorized by the copyright holders of
351 | that material) supplement the terms of this License with terms:
352 |
353 | a) Disclaiming warranty or limiting liability differently from the
354 | terms of sections 15 and 16 of this License; or
355 |
356 | b) Requiring preservation of specified reasonable legal notices or
357 | author attributions in that material or in the Appropriate Legal
358 | Notices displayed by works containing it; or
359 |
360 | c) Prohibiting misrepresentation of the origin of that material, or
361 | requiring that modified versions of such material be marked in
362 | reasonable ways as different from the original version; or
363 |
364 | d) Limiting the use for publicity purposes of names of licensors or
365 | authors of the material; or
366 |
367 | e) Declining to grant rights under trademark law for use of some
368 | trade names, trademarks, or service marks; or
369 |
370 | f) Requiring indemnification of licensors and authors of that
371 | material by anyone who conveys the material (or modified versions of
372 | it) with contractual assumptions of liability to the recipient, for
373 | any liability that these contractual assumptions directly impose on
374 | those licensors and authors.
375 |
376 | All other non-permissive additional terms are considered "further
377 | restrictions" within the meaning of section 10. If the Program as you
378 | received it, or any part of it, contains a notice stating that it is
379 | governed by this License along with a term that is a further
380 | restriction, you may remove that term. If a license document contains
381 | a further restriction but permits relicensing or conveying under this
382 | License, you may add to a covered work material governed by the terms
383 | of that license document, provided that the further restriction does
384 | not survive such relicensing or conveying.
385 |
386 | If you add terms to a covered work in accord with this section, you
387 | must place, in the relevant source files, a statement of the
388 | additional terms that apply to those files, or a notice indicating
389 | where to find the applicable terms.
390 |
391 | Additional terms, permissive or non-permissive, may be stated in the
392 | form of a separately written license, or stated as exceptions;
393 | the above requirements apply either way.
394 |
395 | 8. Termination.
396 |
397 | You may not propagate or modify a covered work except as expressly
398 | provided under this License. Any attempt otherwise to propagate or
399 | modify it is void, and will automatically terminate your rights under
400 | this License (including any patent licenses granted under the third
401 | paragraph of section 11).
402 |
403 | However, if you cease all violation of this License, then your
404 | license from a particular copyright holder is reinstated (a)
405 | provisionally, unless and until the copyright holder explicitly and
406 | finally terminates your license, and (b) permanently, if the copyright
407 | holder fails to notify you of the violation by some reasonable means
408 | prior to 60 days after the cessation.
409 |
410 | Moreover, your license from a particular copyright holder is
411 | reinstated permanently if the copyright holder notifies you of the
412 | violation by some reasonable means, this is the first time you have
413 | received notice of violation of this License (for any work) from that
414 | copyright holder, and you cure the violation prior to 30 days after
415 | your receipt of the notice.
416 |
417 | Termination of your rights under this section does not terminate the
418 | licenses of parties who have received copies or rights from you under
419 | this License. If your rights have been terminated and not permanently
420 | reinstated, you do not qualify to receive new licenses for the same
421 | material under section 10.
422 |
423 | 9. Acceptance Not Required for Having Copies.
424 |
425 | You are not required to accept this License in order to receive or
426 | run a copy of the Program. Ancillary propagation of a covered work
427 | occurring solely as a consequence of using peer-to-peer transmission
428 | to receive a copy likewise does not require acceptance. However,
429 | nothing other than this License grants you permission to propagate or
430 | modify any covered work. These actions infringe copyright if you do
431 | not accept this License. Therefore, by modifying or propagating a
432 | covered work, you indicate your acceptance of this License to do so.
433 |
434 | 10. Automatic Licensing of Downstream Recipients.
435 |
436 | Each time you convey a covered work, the recipient automatically
437 | receives a license from the original licensors, to run, modify and
438 | propagate that work, subject to this License. You are not responsible
439 | for enforcing compliance by third parties with this License.
440 |
441 | An "entity transaction" is a transaction transferring control of an
442 | organization, or substantially all assets of one, or subdividing an
443 | organization, or merging organizations. If propagation of a covered
444 | work results from an entity transaction, each party to that
445 | transaction who receives a copy of the work also receives whatever
446 | licenses to the work the party's predecessor in interest had or could
447 | give under the previous paragraph, plus a right to possession of the
448 | Corresponding Source of the work from the predecessor in interest, if
449 | the predecessor has it or can get it with reasonable efforts.
450 |
451 | You may not impose any further restrictions on the exercise of the
452 | rights granted or affirmed under this License. For example, you may
453 | not impose a license fee, royalty, or other charge for exercise of
454 | rights granted under this License, and you may not initiate litigation
455 | (including a cross-claim or counterclaim in a lawsuit) alleging that
456 | any patent claim is infringed by making, using, selling, offering for
457 | sale, or importing the Program or any portion of it.
458 |
459 | 11. Patents.
460 |
461 | A "contributor" is a copyright holder who authorizes use under this
462 | License of the Program or a work on which the Program is based. The
463 | work thus licensed is called the contributor's "contributor version".
464 |
465 | A contributor's "essential patent claims" are all patent claims
466 | owned or controlled by the contributor, whether already acquired or
467 | hereafter acquired, that would be infringed by some manner, permitted
468 | by this License, of making, using, or selling its contributor version,
469 | but do not include claims that would be infringed only as a
470 | consequence of further modification of the contributor version. For
471 | purposes of this definition, "control" includes the right to grant
472 | patent sublicenses in a manner consistent with the requirements of
473 | this License.
474 |
475 | Each contributor grants you a non-exclusive, worldwide, royalty-free
476 | patent license under the contributor's essential patent claims, to
477 | make, use, sell, offer for sale, import and otherwise run, modify and
478 | propagate the contents of its contributor version.
479 |
480 | In the following three paragraphs, a "patent license" is any express
481 | agreement or commitment, however denominated, not to enforce a patent
482 | (such as an express permission to practice a patent or covenant not to
483 | sue for patent infringement). To "grant" such a patent license to a
484 | party means to make such an agreement or commitment not to enforce a
485 | patent against the party.
486 |
487 | If you convey a covered work, knowingly relying on a patent license,
488 | and the Corresponding Source of the work is not available for anyone
489 | to copy, free of charge and under the terms of this License, through a
490 | publicly available network server or other readily accessible means,
491 | then you must either (1) cause the Corresponding Source to be so
492 | available, or (2) arrange to deprive yourself of the benefit of the
493 | patent license for this particular work, or (3) arrange, in a manner
494 | consistent with the requirements of this License, to extend the patent
495 | license to downstream recipients. "Knowingly relying" means you have
496 | actual knowledge that, but for the patent license, your conveying the
497 | covered work in a country, or your recipient's use of the covered work
498 | in a country, would infringe one or more identifiable patents in that
499 | country that you have reason to believe are valid.
500 |
501 | If, pursuant to or in connection with a single transaction or
502 | arrangement, you convey, or propagate by procuring conveyance of, a
503 | covered work, and grant a patent license to some of the parties
504 | receiving the covered work authorizing them to use, propagate, modify
505 | or convey a specific copy of the covered work, then the patent license
506 | you grant is automatically extended to all recipients of the covered
507 | work and works based on it.
508 |
509 | A patent license is "discriminatory" if it does not include within
510 | the scope of its coverage, prohibits the exercise of, or is
511 | conditioned on the non-exercise of one or more of the rights that are
512 | specifically granted under this License. You may not convey a covered
513 | work if you are a party to an arrangement with a third party that is
514 | in the business of distributing software, under which you make payment
515 | to the third party based on the extent of your activity of conveying
516 | the work, and under which the third party grants, to any of the
517 | parties who would receive the covered work from you, a discriminatory
518 | patent license (a) in connection with copies of the covered work
519 | conveyed by you (or copies made from those copies), or (b) primarily
520 | for and in connection with specific products or compilations that
521 | contain the covered work, unless you entered into that arrangement,
522 | or that patent license was granted, prior to 28 March 2007.
523 |
524 | Nothing in this License shall be construed as excluding or limiting
525 | any implied license or other defenses to infringement that may
526 | otherwise be available to you under applicable patent law.
527 |
528 | 12. No Surrender of Others' Freedom.
529 |
530 | If conditions are imposed on you (whether by court order, agreement or
531 | otherwise) that contradict the conditions of this License, they do not
532 | excuse you from the conditions of this License. If you cannot convey a
533 | covered work so as to satisfy simultaneously your obligations under this
534 | License and any other pertinent obligations, then as a consequence you may
535 | not convey it at all. For example, if you agree to terms that obligate you
536 | to collect a royalty for further conveying from those to whom you convey
537 | the Program, the only way you could satisfy both those terms and this
538 | License would be to refrain entirely from conveying the Program.
539 |
540 | 13. Remote Network Interaction; Use with the GNU General Public License.
541 |
542 | Notwithstanding any other provision of this License, if you modify the
543 | Program, your modified version must prominently offer all users
544 | interacting with it remotely through a computer network (if your version
545 | supports such interaction) an opportunity to receive the Corresponding
546 | Source of your version by providing access to the Corresponding Source
547 | from a network server at no charge, through some standard or customary
548 | means of facilitating copying of software. This Corresponding Source
549 | shall include the Corresponding Source for any work covered by version 3
550 | of the GNU General Public License that is incorporated pursuant to the
551 | following paragraph.
552 |
553 | Notwithstanding any other provision of this License, you have
554 | permission to link or combine any covered work with a work licensed
555 | under version 3 of the GNU General Public License into a single
556 | combined work, and to convey the resulting work. The terms of this
557 | License will continue to apply to the part which is the covered work,
558 | but the work with which it is combined will remain governed by version
559 | 3 of the GNU General Public License.
560 |
561 | 14. Revised Versions of this License.
562 |
563 | The Free Software Foundation may publish revised and/or new versions of
564 | the GNU Affero General Public License from time to time. Such new versions
565 | will be similar in spirit to the present version, but may differ in detail to
566 | address new problems or concerns.
567 |
568 | Each version is given a distinguishing version number. If the
569 | Program specifies that a certain numbered version of the GNU Affero General
570 | Public License "or any later version" applies to it, you have the
571 | option of following the terms and conditions either of that numbered
572 | version or of any later version published by the Free Software
573 | Foundation. If the Program does not specify a version number of the
574 | GNU Affero General Public License, you may choose any version ever published
575 | by the Free Software Foundation.
576 |
577 | If the Program specifies that a proxy can decide which future
578 | versions of the GNU Affero General Public License can be used, that proxy's
579 | public statement of acceptance of a version permanently authorizes you
580 | to choose that version for the Program.
581 |
582 | Later license versions may give you additional or different
583 | permissions. However, no additional obligations are imposed on any
584 | author or copyright holder as a result of your choosing to follow a
585 | later version.
586 |
587 | 15. Disclaimer of Warranty.
588 |
589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597 |
598 | 16. Limitation of Liability.
599 |
600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608 | SUCH DAMAGES.
609 |
610 | 17. Interpretation of Sections 15 and 16.
611 |
612 | If the disclaimer of warranty and limitation of liability provided
613 | above cannot be given local legal effect according to their terms,
614 | reviewing courts shall apply local law that most closely approximates
615 | an absolute waiver of all civil liability in connection with the
616 | Program, unless a warranty or assumption of liability accompanies a
617 | copy of the Program in return for a fee.
618 |
619 | END OF TERMS AND CONDITIONS
620 |
621 | How to Apply These Terms to Your New Programs
622 |
623 | If you develop a new program, and you want it to be of the greatest
624 | possible use to the public, the best way to achieve this is to make it
625 | free software which everyone can redistribute and change under these terms.
626 |
627 | To do so, attach the following notices to the program. It is safest
628 | to attach them to the start of each source file to most effectively
629 | state the exclusion of warranty; and each file should have at least
630 | the "copyright" line and a pointer to where the full notice is found.
631 |
632 | Live visualization of all the pokemon in your area... and more!
633 | Copyright (C) 2017 RocketMap Team
634 |
635 | This program is free software: you can redistribute it and/or modify
636 | it under the terms of the GNU Affero General Public License as published
637 | by the Free Software Foundation, either version 3 of the License, or
638 | (at your option) any later version.
639 |
640 | This program is distributed in the hope that it will be useful,
641 | but WITHOUT ANY WARRANTY; without even the implied warranty of
642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643 | GNU Affero General Public License for more details.
644 |
645 | You should have received a copy of the GNU Affero General Public License
646 | along with this program. If not, see .
647 |
648 | Also add information on how to contact you by electronic and paper mail.
649 |
650 | If your software can interact with users remotely through a computer
651 | network, you should also make sure that it provides a way for users to
652 | get its source. For example, if your program is a web application, its
653 | interface could display a "Source" link that leads users to an archive
654 | of the code. There are many ways you could offer source, and different
655 | solutions will be better for different programs; see section 13 for the
656 | specific requirements.
657 |
658 | You should also get your employer (if you work as a programmer) or school,
659 | if any, to sign a "copyright disclaimer" for the program, if necessary.
660 | For more information on this, and how to apply and follow the GNU AGPL, see
661 | .
662 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.patreon.com/devkat)
2 | 
3 | 
4 |
5 | # The Sublimely Magnificent Node.js RM Webserver Mark III
6 |
7 | An asynchronous Node.js webserver using restify for the API, node-mysql for MySQL integration and that supports gzip compression, load limiting with toobusy-js, rate limiting with restify's throttling and multiprocessing (+ process management) with cluster.
8 |
9 | Since this webserver is meant to be set up as a managed microcomponent - not as a replacement webserver for static components - its functionality is strictly limited to serving dynamic data requests only. **Using a more mature webserver to serve static components and as a reverse proxy is highly recommended (nginx on Linux, apache2 on Windows)**, an example nginx configuration is provided below.
10 |
11 | If you want to use a more advanced process manager, we recommend [disabling cluster in your configuration](#disabling-process-management-with-cluster) to disable process management.
12 |
13 | ## Getting Started
14 |
15 | These instructions will help you deploy the project on a live system.
16 |
17 | **Important:** The default configuration example file `.env.example` overwrites the `NODE_ENV` environment variable to `production` for security purposes.
18 |
19 | ### Prerequisites
20 |
21 | - [Node.js v6.11.4 or higher](https://nodejs.org/en/)
22 | - npm v5.5.0 or higher
23 |
24 | ```
25 | Windows: To update npm and Node.js, manually download the LTS version from their website.
26 |
27 | To update Node.js and npm:
28 | apt-get remove node nodejs
29 | curl -L https://git.io/n-install | bash
30 | n lts
31 |
32 | To update only npm:
33 | npm install -g npm
34 | ```
35 |
36 | ### Installing
37 |
38 | Start by reading the license in LICENSE.md.
39 |
40 | Make sure Node.js and npm are properly installed:
41 |
42 | ```
43 | node -v
44 | npm -v
45 | ```
46 |
47 | Clone the project and its submodules:
48 |
49 | ```
50 | git clone --recursive https://github.com/sebastienvercammen/devkat-rm-node-webserver.git
51 | ```
52 |
53 | Make sure you are in the project directory with your terminal, and install the dependencies:
54 |
55 | ```
56 | npm install
57 | ```
58 |
59 | Copy the example configuration file `.env.example` to `.env`:
60 |
61 | ```
62 | Linux:
63 | cp .env.example .env
64 |
65 | Windows:
66 | copy .env.example .env
67 | ```
68 |
69 | And presto, you're ready to configure.
70 |
71 | After configuring, start the server with:
72 |
73 | ```
74 | node index.js
75 | ```
76 |
77 | ### Configuration
78 |
79 | #### Settings you must review
80 |
81 | ```
82 | # Webserver host IP to bind to. 0.0.0.0 binds to all interfaces.
83 | WEB_HOST=0.0.0.0
84 |
85 | # Webserver port.
86 | WEB_PORT=8080
87 |
88 | # Set up domain(s) to allow CORS for, via comma-separated list.
89 | CORS_WHITELIST=*
90 |
91 | # And all database settings.
92 | DB_HOST=localhost
93 | DB_PORT=3306
94 | DB_USER=root
95 | DB_PASS=
96 | DB_DATABASE=database
97 | ```
98 |
99 | #### Enabling gzip compression
100 |
101 | ```
102 | ENABLE_GZIP=true
103 | ```
104 |
105 | #### Enabling HTTPS
106 |
107 | ```
108 | # HTTPS key file paths.
109 | ENABLE_HTTPS=true
110 | HTTPS_KEY_PATH=privkey.pem
111 | HTTPS_CERT_PATH=cert.pem
112 | ```
113 |
114 | #### Enabling load limiter
115 |
116 | ```
117 | # Enable/disable the load limiter.
118 | ENABLE_LOAD_LIMITER=true
119 |
120 | # Enable/disable logging when load limiter kicks in on a worker.
121 | ENABLE_LOAD_LIMITER_LOGGING=true
122 |
123 | # Represents the maximum amount of time in milliseconds that the event queue
124 | # is behind, before we consider the process "too busy".
125 | MAX_LAG_MS=75
126 |
127 | # The check interval for measuring event loop lag, in milliseconds.
128 | LAG_INTERVAL_MS=500
129 | ```
130 |
131 | #### Enabling request throttling
132 |
133 | **Note:** When enabling request throttling, make sure you're not using a reverse proxy, since all requests will come from the same IP.
134 |
135 | ```
136 | # Enable/disable webserver request throttling.
137 | ENABLE_THROTTLE=true
138 | # Rate limit: requests per second.
139 | THROTTLE_RATE=5
140 | # Allow user to temporarily go over the rate limit, up to the burst limit.
141 | THROTTLE_BURST=10
142 | ```
143 |
144 | #### Allowing CORS for all domains
145 |
146 | **Warning:** Enabling CORS for all domains is not recommended. You will only make it easier for scrapers to get your data.
147 |
148 | ```
149 | # Set up domain(s) to allow CORS for, via comma-separated list.
150 | CORS_WHITELIST=*
151 | ```
152 |
153 | #### Disabling process management with cluster
154 |
155 | **Note:** Disabling process management with cluster will automatically make the webserver ignore all configuration items related to multiprocessing/cluster.
156 |
157 | ```
158 | ENABLE_CLUSTER=false
159 | ```
160 |
161 | ## Using nginx as a reverse proxy to /raw_data, with fallback to Flask for unsupported features
162 |
163 | If you're using nginx to serve your RocketMap website, make sure your nginx configuration looks like the example below to serve /raw_data with the new webserver, and all other paths with RocketMap's Flask/werkzeug.
164 |
165 | This example assumes your RM webserver is running on port 5000 and the devkat webserver on port 1337. Adjust accordingly.
166 |
167 | If a feature is not yet implemented in this webserver, the example configuration falls back to the Flask webserver. The focus of this webserver is to have the best performance for /raw_data requests, although other features are planned to be implemented (at a lower priority).
168 |
169 | Based on [RocketMap's nginx example](http://rocketmap.readthedocs.io/en/develop/advanced-install/nginx.html).
170 |
171 | ```
172 | upstream flask {
173 | server 127.0.0.1:5000;
174 | }
175 | upstream devkat {
176 | server 127.0.0.1:1337;
177 | }
178 |
179 | server {
180 | listen 80;
181 |
182 | location /raw_data {
183 | # /stats
184 | if ($arg_seen = "true") {
185 | proxy_pass http://flask;
186 | }
187 |
188 | # /status
189 | if ($arg_status = "true") {
190 | proxy_pass http://flask;
191 | }
192 |
193 | # Appearances & appearance details.
194 | if ($arg_appearances = "true") {
195 | proxy_pass http://flask;
196 | }
197 |
198 | if ($arg_appearancesDetails = "true") {
199 | proxy_pass http://flask;
200 | }
201 |
202 | # Spawnpoints.
203 | if ($arg_spawnpoints = "true") {
204 | proxy_pass http://flask;
205 | }
206 |
207 | # Scanned locations.
208 | if ($arg_scanned = "true") {
209 | proxy_pass http://flask;
210 | }
211 |
212 | proxy_pass http://devkat;
213 | }
214 |
215 | location / {
216 | proxy_pass http://flask;
217 | }
218 | }
219 | ```
220 |
221 | ## License
222 |
223 | This project is licensed under AGPLv3 - see the [LICENSE.md](LICENSE.md) file for details.
224 |
--------------------------------------------------------------------------------
/data/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sebastienvercammen/devkat-rm-node-webserver/f78e23e033735d8cd3b077ca100f9c5329811e06/data/.gitkeep
--------------------------------------------------------------------------------
/inc/blacklist.js:
--------------------------------------------------------------------------------
1 | /*
2 | Blacklist fingerprinting methods. They receive the restify request object as
3 | argument and return true when a blacklisted fingerprint matches.
4 | */
5 |
6 | const blacklist = {};
7 |
8 | // No referrer = request w/o being on a website.
9 | blacklist.no_referrer = (req) => {
10 | return req.header('Referer', false) === false;
11 | };
12 |
13 | // iPokeGo.
14 | blacklist.iPokeGo = (req) => {
15 | const agent = req.header('User-Agent', '');
16 | return agent.toLowerCase().indexOf('ipokego') > -1;
17 | };
18 |
19 | module.exports = blacklist;
20 |
--------------------------------------------------------------------------------
/inc/db.js:
--------------------------------------------------------------------------------
1 | // Parse config.
2 | require('dotenv').config();
3 |
4 | const debug = require('debug')('devkat:db');
5 | const mysql = require('mysql');
6 | const utils = require('./utils.js');
7 |
8 |
9 | /* Settings. */
10 |
11 | const DB_HOST = process.env.DB_HOST || 'localhost';
12 | const DB_PORT = process.env.DB_PORT || 3306;
13 | const DB_USER = process.env.DB_USER || 'root';
14 | const DB_PASS = process.env.DB_PASS || '';
15 | const DB_DATABASE = process.env.DB_DATABASE || 'database';
16 |
17 | const DB_POOL_MAX_SIZE = process.env.DB_POOL_MAX_SIZE || 10;
18 |
19 |
20 | /* DB. */
21 |
22 | const pool = mysql.createPool({
23 | 'host': DB_HOST,
24 | 'port': DB_PORT,
25 | 'user': DB_USER,
26 | 'password': DB_PASS,
27 | 'database': DB_DATABASE,
28 | 'connectionLimit': DB_POOL_MAX_SIZE,
29 | 'insecureAuth': true,
30 | 'dateStrings': true
31 | });
32 |
33 |
34 | /**
35 | * Connection to the local MySQL database.
36 | *
37 | * @param {function} callback Gets called with the error and DB pool as parameters.
38 | * Error parameter is null if the connection succeeded.
39 | */
40 | function connect(callback) {
41 | debug('[%s] Connecting to db on %s:%s...', process.pid, DB_HOST, DB_PORT);
42 |
43 | pool.getConnection(function (err, connection) {
44 | // Something happened.
45 | if (err) {
46 | debug('[%s] Error connecting to db on %s:%s.', process.pid, DB_HOST, DB_PORT, err);
47 | return callback(err, null);
48 | }
49 |
50 | // Connected!
51 | debug('[%s] Connected to db on %s:%s...', process.pid, DB_HOST, DB_PORT);
52 | connection.release();
53 | return callback(null, pool);
54 | });
55 | }
56 |
57 | module.exports = {
58 | 'pool': pool,
59 | 'connect': connect
60 | };
--------------------------------------------------------------------------------
/inc/utils.js:
--------------------------------------------------------------------------------
1 | var utils = {
2 | // Generic error log & exit.
3 | handle_error: (err) => {
4 | console.error(err);
5 | process.exit(1);
6 | },
7 |
8 | // Fix SIGINT on Windows systems.
9 | fixWinSIGINT: () => {
10 | if (process.platform === 'win32') {
11 | require('readline')
12 | .createInterface({
13 | input: process.stdin,
14 | output: process.stdout
15 | })
16 | .on('SIGINT', function () {
17 | process.emit('SIGINT');
18 | });
19 | }
20 | },
21 |
22 | // Readability methods.
23 | isUndefined: (val) => {
24 | return (typeof val === 'undefined');
25 | },
26 |
27 | // TODO: Figure out better name than "isEmpty".
28 | isEmpty: (val) => {
29 | return (utils.isUndefined(val) || val === null || val === '');
30 | },
31 |
32 | // Check if a string is numeric (e.g. for GET params).
33 | isNumeric: (n) => {
34 | return !isNaN(parseFloat(n)) && isFinite(n);
35 | }
36 | };
37 |
38 |
39 | /*
40 | * Pokémon data related methods.
41 | */
42 | utils.pokemon = {
43 | // Check if Pokémon ID is in data.
44 | hasPokemonData: (pokedex, id) => {
45 | return pokedex.hasOwnProperty(id);
46 | },
47 |
48 | // Get a Pokémon's data.
49 | getPokemonData: (pokedex, id) => {
50 | // Are we sure we have this Pokémon?
51 | if (!utils.pokemon.hasPokemonData(pokedex, id)) {
52 | return false;
53 | }
54 |
55 | return pokedex[id];
56 | },
57 |
58 | // Get a Pokémon's name.
59 | getPokemonName: (pokedex, id) => {
60 | // Are we sure we have this Pokémon?
61 | if (!utils.pokemon.hasPokemonData(pokedex, id)) {
62 | return null;
63 | }
64 |
65 | return pokedex[id].name;
66 | },
67 |
68 | // Get a Pokémon's types.
69 | getPokemonTypes: (pokedex, id) => {
70 | // Are we sure we have this Pokémon?
71 | if (!utils.pokemon.hasPokemonData(pokedex, id)) {
72 | return null;
73 | }
74 |
75 | return pokedex[id].types;
76 | }
77 | };
78 |
79 | module.exports = utils;
80 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | Async Node.js webserver w/ restify and node-mysql for RocketMap.
3 | Supports gzip compression, load limiting w/ toobusy-js and multiprocessing
4 | with cluster.
5 |
6 | Developed by Sébastien Vercammen, for devkat.
7 | It's all thanks to our Patrons: https://www.patreon.com/devkat
8 |
9 | Thank you for supporting us to do what we do best.
10 | */
11 |
12 | // Parse config.
13 | require('dotenv').config();
14 |
15 | const fs = require('fs');
16 | const debug = require('debug')('devkat:master');
17 | const cluster = require('cluster');
18 | const utils = require('./inc/utils.js');
19 | const db = require('./inc/db.js');
20 | var shuttingDown = false; // Are we shutting down?
21 | var online_workers = {}; // Status per worker PID.
22 |
23 |
24 | /* Readability references. */
25 |
26 | const fixWinSIGINT = utils.fixWinSIGINT;
27 |
28 |
29 | /* Settings. */
30 |
31 | const SERVER_NAME = 'devkat RM Webserver';
32 | const SERVER_VERSION = '2.2.1';
33 | const HTTPS = process.env.ENABLE_HTTPS === 'true' || false;
34 | const HTTPS_KEY_PATH = process.env.HTTPS_KEY_PATH || 'privkey.pem';
35 | const HTTPS_CERT_PATH = process.env.HTTPS_CERT_PATH || 'cert.pem';
36 | const GZIP = process.env.ENABLE_GZIP === 'true' || false;
37 | const WEB_HOST = process.env.WEB_HOST || '0.0.0.0';
38 | const WEB_PORT = parseInt(process.env.WEB_PORT) || 3000;
39 | const WEB_WORKERS = parseInt(process.env.WEB_WORKERS) || require('os').cpus().length;
40 | const ENABLE_LOAD_LIMITER = process.env.ENABLE_LOAD_LIMITER !== 'false' || false;
41 | const ENABLE_LOAD_LIMITER_LOGGING = process.env.ENABLE_LOAD_LIMITER_LOGGING !== 'false' || false;
42 | const MAX_LAG_MS = parseInt(process.env.MAX_LAG_MS) || 75;
43 | const LAG_INTERVAL_MS = parseInt(process.env.LAG_INTERVAL_MS) || 500;
44 | const ENABLE_CLUSTER = process.env.ENABLE_CLUSTER !== 'false' || false;
45 | const AUTORESTART_WORKERS = process.env.AUTORESTART_WORKERS !== 'false' || false;
46 | const ENABLE_THROTTLE = process.env.ENABLE_THROTTLE !== 'false' || false;
47 | const THROTTLE_RATE = parseInt(process.env.THROTTLE_RATE) || 5;
48 | const THROTTLE_BURST = parseInt(process.env.THROTTLE_BURST) || 10;
49 |
50 |
51 | // If we're on Windows, fix the SIGINT event.
52 | //fixWinSIGINT();
53 |
54 | // If we're the cluster master, manage our processes.
55 | if (ENABLE_CLUSTER && cluster.isMaster) {
56 | debug('Master cluster setting up %s workers...', WEB_WORKERS);
57 |
58 | for (let i = 0; i < WEB_WORKERS; i++) {
59 | cluster.fork();
60 | }
61 |
62 | // Worker is online, but not yet ready to handle requests.
63 | cluster.on('online', function (worker) {
64 | debug('Worker %s (PID %s) is starting...', worker.id, worker.process.pid);
65 | });
66 |
67 | // Worker is ded :(
68 | cluster.on('exit', function (worker, code, signal) {
69 | let id = worker.id;
70 | let pid = worker.process.pid;
71 |
72 | // Don't continue if we're shutting down.
73 | if (shuttingDown) {
74 | debug('Worker %s (PID %s) has exited.', worker.id, worker.process.pid);
75 | return;
76 | }
77 |
78 | // If the worker wasn't online yet, something happened during startup.
79 | if (!online_workers.hasOwnProperty(worker.process.pid)) {
80 | debug('Worker %s (PID %s) encountered an error on startup. Exiting.', id, pid);
81 | shuttingDown = true;
82 |
83 | // Graceful shutdown instead of process.exit().
84 | process.emit('SIGINT');
85 | return;
86 | }
87 |
88 | debug('Worker %s died with code %s, and signal %s.', pid, code, signal);
89 | if (AUTORESTART_WORKERS) debug('Starting a new worker.');
90 |
91 | // Start new worker if autorestart is enabled.
92 | if (AUTORESTART_WORKERS)
93 | cluster.fork();
94 | });
95 |
96 | // Worker disconnected, either on graceful shutdown or kill.
97 | cluster.on('disconnect', (worker) => {
98 | debug('Worker %s (PID %s) has disconnected.', worker.id, worker.process.pid);
99 | });
100 |
101 | // Receive messages from workers.
102 | cluster.on('message', (worker, msg, handle) => {
103 | var status = msg.status;
104 | var id = msg.id;
105 | var pid = msg.pid;
106 |
107 | // Worker had a successful startup.
108 | if (status === 'ONLINE') {
109 | online_workers[pid] = status;
110 | }
111 | });
112 |
113 |
114 | /* Graceful shutdown. */
115 |
116 | process.on('SIGINT', function graceful() {
117 | shuttingDown = true;
118 | debug('Gracefully closing server...');
119 |
120 | // Kill all workers, but let them kill themselves because otherwise they
121 | // might not be ready listening, and you end up with EPIPE errors.
122 | for (var id in cluster.workers) {
123 | let worker = cluster.workers[id];
124 |
125 | // Disconnected workers will exit themselves.
126 | if (worker.isConnected()) {
127 | worker.send('shutdown');
128 | worker.disconnect();
129 | }
130 | }
131 |
132 | // Make sure all workers have exited.
133 | function allWorkersDied(workers) {
134 | for (var id in workers) {
135 | let worker = workers[id];
136 | if (!worker.isDead()) return false;
137 | }
138 |
139 | return true;
140 | }
141 |
142 | // Run code when all workers are dead w/o starving CPU.
143 | function waitUntilAllWorkersDied(workers, interval_ms, callback) {
144 | if (!allWorkersDied(workers)) {
145 | setTimeout(() => {
146 | waitUntilAllWorkersDied(workers, interval_ms, callback);
147 | }, interval_ms);
148 | } else {
149 | // All dead! Yay!
150 | callback();
151 | }
152 | }
153 |
154 | waitUntilAllWorkersDied(cluster.workers, 500, () => {
155 | process.exit(0);
156 | });
157 | });
158 | } else {
159 | // We're a worker, prepare to handle requests.
160 | const toobusy = require('toobusy-js');
161 | const restify = require('restify');
162 | const errors = require('restify-errors');
163 | const restifyPlugins = restify.plugins;
164 |
165 | // Webserver settings & optional HTTPS.
166 | const HTTP_OPTIONS = {
167 | name: SERVER_NAME,
168 | version: SERVER_VERSION
169 | };
170 |
171 | if (HTTPS) {
172 | HTTP_OPTIONS.key = fs.readFileSync(HTTPS_KEY_PATH);
173 | HTTP_OPTIONS.certificate = fs.readFileSync(HTTPS_CERT_PATH);
174 | }
175 |
176 | // Create our server.
177 | const server = restify.createServer(HTTP_OPTIONS);
178 |
179 | // Middleware.
180 | server.use(restifyPlugins.jsonBodyParser({ mapParams: true }));
181 | server.use(restifyPlugins.acceptParser(server.acceptable));
182 | server.use(restifyPlugins.queryParser({ mapParams: true }));
183 | server.use(restifyPlugins.fullResponse());
184 |
185 | if (GZIP) {
186 | server.use(restifyPlugins.gzipResponse());
187 | }
188 |
189 | if (ENABLE_THROTTLE) {
190 | debug('Throttle enabled: %d requests per second, %d burst.', THROTTLE_RATE, THROTTLE_BURST);
191 | server.use(restifyPlugins.throttle({
192 | rate: THROTTLE_RATE,
193 | burst: THROTTLE_BURST,
194 | ip: true
195 | }));
196 | }
197 |
198 | // Middleware which blocks requests when we're too busy.
199 | if (ENABLE_LOAD_LIMITER) {
200 | toobusy.maxLag(MAX_LAG_MS);
201 | toobusy.interval(LAG_INTERVAL_MS);
202 |
203 | debug('Enabled load limiter: ' + MAX_LAG_MS + 'ms limit, ' + LAG_INTERVAL_MS + ' ms check.');
204 |
205 | server.use(function (req, res, next) {
206 | if (toobusy()) {
207 | return next(new errors.ServiceUnavailableError());
208 | } else {
209 | return next();
210 | }
211 | });
212 |
213 | toobusy.onLag((currentLag) => {
214 | currentLag = Math.round(currentLag);
215 |
216 | if (ENABLE_LOAD_LIMITER_LOGGING) {
217 | debug('[%s] Event loop lag detected! Latency: %sms.', process.pid, currentLag);
218 | }
219 | });
220 | }
221 |
222 |
223 | /* App. */
224 |
225 | // Workers can share any TCP connection.
226 | server.listen(WEB_PORT, WEB_HOST, () => {
227 | // Connect to DB.
228 | db.connect(() => {
229 | // Attach routes.
230 | require('./routes')(server);
231 |
232 | // BEEP BOOP, R O B O T I S S E N T I E N T.
233 | if (ENABLE_CLUSTER) {
234 | debug('Worker %s (PID %s) is listening on %s.', cluster.worker.id, process.pid, server.url);
235 |
236 | // We're online. Let's tell our sensei (it's a "master" joke 👀).
237 | process.send({
238 | 'status': 'ONLINE',
239 | 'id': cluster.worker.id,
240 | 'pid': process.pid
241 | });
242 | } else {
243 | debug('Server (PID %s) is listening on %s.', process.pid, server.url);
244 | }
245 | });
246 | });
247 |
248 |
249 | /* Graceful worker shutdown. */
250 | function shutdownWorker() {
251 | if (ENABLE_LOAD_LIMITER) {
252 | // Calling .shutdown allows your process to exit normally.
253 | toobusy.shutdown();
254 | }
255 |
256 | server.close();
257 |
258 | if (ENABLE_CLUSTER) {
259 | debug('Gracefully closed worker %s (PID %s).', cluster.worker.id, process.pid);
260 | } else {
261 | debug('Gracefully closed server (PID %s).', process.pid);
262 | }
263 |
264 | process.exit(0);
265 | }
266 |
267 | process.on('message', function workerMsg(msg) {
268 | if (msg === 'shutdown') {
269 | shutdownWorker();
270 | }
271 | });
272 |
273 | // Handle graceful shutdown if we're not using cluster/process management.
274 | process.on('SIGINT', function gracefulWorker() {
275 | shutdownWorker();
276 | });
277 | }
278 |
--------------------------------------------------------------------------------
/models/Gym.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Parse config.
4 | require('dotenv').config();
5 |
6 |
7 | /* Includes. */
8 |
9 | const db = require('../inc/db.js').pool;
10 | const utils = require('../inc/utils.js');
11 | const pokedex = require('../data/pokedex/pokemon.json');
12 |
13 | const Raid = require('./Raid');
14 | const GymMember = require('./GymMember');
15 | const GymPokemon = require('./GymPokemon');
16 |
17 |
18 | /* Readability references. */
19 |
20 | const isEmpty = utils.isEmpty;
21 | const getPokemonName = utils.pokemon.getPokemonName;
22 | const getPokemonTypes = utils.pokemon.getPokemonTypes;
23 |
24 |
25 | /* Settings. */
26 |
27 | const GYM_LIMIT_PER_QUERY = parseInt(process.env.GYM_LIMIT_PER_QUERY) || 50000;
28 |
29 |
30 | /* Helpers. */
31 |
32 | // Make sure SQL uses proper timezone.
33 | const FROM_UNIXTIME = "CONVERT_TZ(FROM_UNIXTIME(?), @@session.time_zone, '+00:00')";
34 |
35 | function prepareQuery(options) {
36 | // Parse options.
37 | var swLat = options.swLat;
38 | var swLng = options.swLng;
39 | var neLat = options.neLat;
40 | var neLng = options.neLng;
41 | var oSwLat = options.oSwLat;
42 | var oSwLng = options.oSwLng;
43 | var oNeLat = options.oNeLat;
44 | var oNeLng = options.oNeLng;
45 | var timestamp = options.timestamp || false;
46 |
47 | // Query options.
48 | var query_where = [];
49 |
50 | // Optional viewport.
51 | if (!isEmpty(swLat) && !isEmpty(swLng) && !isEmpty(neLat) && !isEmpty(neLng)) {
52 | query_where.push(
53 | [
54 | 'g.latitude >= ? AND g.latitude <= ?',
55 | [swLat, neLat]
56 | ]
57 | );
58 | query_where.push(
59 | [
60 | 'g.longitude >= ? AND g.longitude <= ?',
61 | [swLng, neLng]
62 | ]
63 | );
64 | }
65 |
66 | if (timestamp) {
67 | // Change POSIX timestamp to UTC time.
68 | timestamp = new Date(timestamp).getTime();
69 |
70 | query_where.push(
71 | [
72 | 'g.last_scanned > ' + FROM_UNIXTIME,
73 | [Math.round(timestamp / 1000)]
74 | ]
75 | );
76 |
77 | // Send Gyms in view but exclude those within old boundaries.
78 | // Don't use when timestamp is empty, it means we're intentionally trying to get
79 | // old gyms as well (new viewport or first request).
80 | if (!isEmpty(oSwLat) && !isEmpty(oSwLng) && !isEmpty(oNeLat) && !isEmpty(oNeLng)) {
81 | query_where.push(
82 | [
83 | 'g.latitude < ? AND g.latitude > ? AND g.longitude < ? AND g.longitude > ?',
84 | [oSwLat, oNeLat, oSwLng, oNeLng]
85 | ]
86 | );
87 | }
88 | }
89 |
90 | // Prepare query.
91 | let query = ' WHERE ';
92 | let partials = [];
93 | let values = []; // Unnamed query params.
94 |
95 | // Add individual options.
96 | for (var i = 0; i < query_where.length; i++) {
97 | let w = query_where[i];
98 | // w = [ 'query ?', [opt1] ]
99 | partials.push(w[0]);
100 | values = values.concat(w[1]);
101 | }
102 | query += partials.join(' AND ');
103 |
104 | // Set limit.
105 | query += ' LIMIT ' + GYM_LIMIT_PER_QUERY;
106 |
107 | return [ query, values ];
108 | }
109 |
110 | function prepareGymPromise(query, params) {
111 | return new Promise((resolve, reject) => {
112 | db.query(query, params, (err, results, fields) => {
113 | if (err) {
114 | return reject(err);
115 | } else {
116 | // If there are no gyms, let's just go. 👀
117 | if (results.length == 0) {
118 | return resolve([]);
119 | }
120 |
121 | // Gym references.
122 | const gym_refs = {};
123 |
124 |
125 | /* Add raids. */
126 |
127 | // One query to rule them all.
128 | for (var i = 0; i < results.length; i++) {
129 | let gym = results[i];
130 |
131 | // Avoid timezone issues. This is a UTC timestamp.
132 | gym.last_modified = gym.last_modified.replace(' ', 'T') + 'Z';
133 | gym.last_scanned = gym.last_scanned.replace(' ', 'T') + 'Z';
134 |
135 | // Convert datetime to UNIX timestamp.
136 | gym.last_modified = Date.parse(gym.last_modified) || 0;
137 | gym.last_scanned = Date.parse(gym.last_scanned) || 0;
138 |
139 | // Always send data, even for empty lists. RM expects it.
140 | gym.pokemon = [];
141 |
142 | gym_refs['' + gym.gym_id] = gym;
143 | }
144 |
145 | // Make it easier to use.
146 | const gym_ids = Object.keys(gym_refs);
147 |
148 | // Lesgo.
149 | Raid.from_gym_ids(gym_ids)
150 | .then((raids) => {
151 | // Attach raids to gyms.
152 | for (var i = 0; i < raids.length; i++) {
153 | const raid = raids[i];
154 |
155 | // Avoid timezone issues. This is a UTC timestamp.
156 | raid.spawn = raid.spawn.replace(' ', 'T') + 'Z';
157 | raid.start = raid.start.replace(' ', 'T') + 'Z';
158 | raid.end = raid.end.replace(' ', 'T') + 'Z';
159 |
160 | // Convert datetime to UNIX timestamp.
161 | raid.spawn = Date.parse(raid.spawn) || 0;
162 | raid.start = Date.parse(raid.start) || 0;
163 | raid.end = Date.parse(raid.end) || 0;
164 |
165 | // Assign Pokémon data.
166 | raid.pokemon_name = getPokemonName(pokedex, raid.pokemon_id) || '';
167 | raid.pokemon_types = getPokemonTypes(pokedex, raid.pokemon_id) || [];
168 |
169 | gym_refs['' + raid.gym_id].raid = raid;
170 | }
171 | })
172 | .then(() => GymMember.from_gym_ids(gym_ids))
173 | .then((gymMembers) => {
174 | // Get gym Pokémon from gym members by
175 | // mapping pokemon_uid to gym member.
176 | const pokemon_uids = {};
177 |
178 | if (gymMembers.length > 0) {
179 | for (var i = 0; i < gymMembers.length; i++) {
180 | const member = gymMembers[i];
181 | pokemon_uids[member.pokemon_uid] = member;
182 | }
183 |
184 | return GymPokemon.from_pokemon_uids_map(pokemon_uids);
185 | } else {
186 | return {
187 | 'map': pokemon_uids,
188 | 'pokemon': []
189 | }
190 | }
191 | })
192 | .then((result) => {
193 | const map_obj = result.map;
194 | const gymPokes = result.pokemon;
195 |
196 | // Attach gym members to gyms.
197 | for (var i = 0; i < gymPokes.length; i++) {
198 | const poke = gymPokes[i];
199 | const member = map_obj['' + poke.pokemon_uid]
200 | const gym_id = member.gym_id;
201 | const gym = gym_refs[gym_id];
202 |
203 | // Avoid timezone issues. This is a UTC timestamp.
204 | poke.last_seen = poke.last_seen.replace(' ', 'T') + 'Z';
205 |
206 | // Convert datetime to UNIX timestamp.
207 | poke.last_seen = Date.parse(poke.last_seen) || 0;
208 |
209 | // Assign member data to the Pokémon being sent.
210 | poke.cp_decayed = member.cp_decayed;
211 | poke.last_scanned = member.last_scanned;
212 | poke.deployment_time = member.deployment_time;
213 |
214 | // Assign Pokémon data.
215 | poke.pokemon_name = getPokemonName(pokedex, poke.pokemon_id) || '';
216 | poke.pokemon_types = getPokemonTypes(pokedex, poke.pokemon_id) || [];
217 |
218 | gym.pokemon.push(poke);
219 | }
220 |
221 | const values = Object.keys(gym_refs).map((k) => gym_refs[k]);
222 |
223 | return resolve(values);
224 | })
225 | .catch(utils.handle_error);
226 | }
227 | });
228 | });
229 | }
230 |
231 |
232 | /* Model. */
233 |
234 | const tablename = 'gym';
235 | const Gym = {};
236 |
237 | // Get active Gyms by coords or timestamp.
238 | Gym.get_gyms = (swLat, swLng, neLat, neLng, timestamp, oSwLat, oSwLng, oNeLat, oNeLng) => {
239 | // Prepare query.
240 | const query_where = prepareQuery({
241 | 'swLat': swLat,
242 | 'swLng': swLng,
243 | 'neLat': neLat,
244 | 'neLng': neLng,
245 | 'oSwLat': oSwLat,
246 | 'oSwLng': oSwLng,
247 | 'oNeLat': oNeLat,
248 | 'oNeLng': oNeLng,
249 | 'timestamp': timestamp
250 | });
251 |
252 | const query = 'SELECT g.*, gd.name, gd.description, gd.url FROM ' + tablename + ' g INNER JOIN gymdetails gd ON g.gym_id = gd.gym_id' + query_where[0];
253 | const params = query_where[1];
254 |
255 | // Return promise.
256 | return prepareGymPromise(query, params);
257 | };
258 |
259 | // Get single Gym + Pokémon in Gym by ID.
260 | Gym.get_gym = (id) => {
261 | // This is a simple one.
262 | const query = 'SELECT g.*, gd.name, gd.description, gd.url FROM ' + tablename + ' g INNER JOIN gymdetails gd ON g.gym_id = gd.gym_id WHERE gym_id = ? LIMIT 1';
263 | const params = [ id ];
264 |
265 | // Return promise.
266 | return prepareGymPromise(query, params);
267 | };
268 |
269 |
270 | module.exports = Gym;
271 |
--------------------------------------------------------------------------------
/models/GymMember.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Parse config.
4 | require('dotenv').config();
5 |
6 |
7 | /* Includes. */
8 |
9 | const db = require('../inc/db.js').pool;
10 |
11 |
12 | /* Helpers. */
13 |
14 | // Make sure SQL uses proper timezone.
15 | const FROM_UNIXTIME = "CONVERT_TZ(FROM_UNIXTIME(?), @@session.time_zone, '+00:00')";
16 |
17 | function prepareGymMemberPromise(query, params) {
18 | return new Promise((resolve, reject) => {
19 | db.query(query, params, (err, results, fields) => {
20 | if (err) {
21 | return reject(err);
22 | } else {
23 | return resolve(results);
24 | }
25 | });
26 | });
27 | }
28 |
29 |
30 | /* Model. */
31 |
32 | const tablename = 'gymmember';
33 | const GymMember = {};
34 |
35 | // Get gym member by gym ID.
36 | GymMember.from_gym_id = (id) => {
37 | // This is a simple one.
38 | const query = 'SELECT * FROM ' + tablename + ' WHERE gym_id = ?';
39 | const params = [ id ];
40 |
41 | // Return promise.
42 | return prepareGymMemberPromise(query, params);
43 | };
44 |
45 | // Get gym members by gym IDs.
46 | GymMember.from_gym_ids = (ids) => {
47 | // This is another simple one.
48 | const query = 'SELECT * FROM ' + tablename + ' WHERE gym_id IN (?)';
49 | const params = [ ids ];
50 |
51 | // Return promise.
52 | return prepareGymMemberPromise(query, params);
53 | };
54 |
55 |
56 | module.exports = GymMember;
--------------------------------------------------------------------------------
/models/GymPokemon.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Parse config.
4 | require('dotenv').config();
5 |
6 |
7 | /* Includes. */
8 |
9 | const db = require('../inc/db.js').pool;
10 |
11 |
12 | /* Helpers. */
13 |
14 | // Make sure SQL uses proper timezone.
15 | const FROM_UNIXTIME = "CONVERT_TZ(FROM_UNIXTIME(?), @@session.time_zone, '+00:00')";
16 |
17 | function prepareGymPokemonPromise(query, params, map_object) {
18 | return new Promise((resolve, reject) => {
19 | db.query(query, params, (err, results, fields) => {
20 | if (err) {
21 | return reject(err);
22 | } else {
23 | return resolve({
24 | 'map': map_object,
25 | 'pokemon': results
26 | });
27 | }
28 | });
29 | });
30 | }
31 |
32 |
33 | /* Model. */
34 |
35 | const tablename = 'gympokemon';
36 | const GymPokemon = {};
37 |
38 | // Get gym Pokémon by Pokémon uIDs.
39 | GymPokemon.from_pokemon_uids_map = (pokemon_uids_obj) => {
40 | // This is another simple one.
41 | const query = 'SELECT * FROM ' + tablename + ' WHERE pokemon_uid IN (?)';
42 | const params = [ Object.keys(pokemon_uids_obj) ];
43 |
44 | // Return promise.
45 | return prepareGymPokemonPromise(query, params, pokemon_uids_obj);
46 | };
47 |
48 |
49 | module.exports = GymPokemon;
--------------------------------------------------------------------------------
/models/Pokemon.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Parse config.
4 | require('dotenv').config();
5 |
6 |
7 | /* Includes. */
8 |
9 | const db = require('../inc/db.js').pool;
10 | const utils = require('../inc/utils.js');
11 | const debug = require('debug')('devkat:db:pokemon');
12 | const debug_query = require('debug')('devkat:db:pokemon:sql');
13 | const pokedex = require('../data/pokedex/pokemon.json');
14 |
15 |
16 | /* Readability references. */
17 |
18 | const isEmpty = utils.isEmpty;
19 | const getPokemonName = utils.pokemon.getPokemonName;
20 | const getPokemonTypes = utils.pokemon.getPokemonTypes;
21 |
22 |
23 | /* Settings. */
24 |
25 | const POKEMON_LIMIT_PER_QUERY = parseInt(process.env.POKEMON_LIMIT_PER_QUERY) || 50000;
26 |
27 |
28 | /* Helpers. */
29 |
30 | // Make sure SQL uses proper timezone.
31 | const FROM_UNIXTIME = "CONVERT_TZ(FROM_UNIXTIME(?), @@session.time_zone, '+00:00')";
32 |
33 | function prepareQuery(options) {
34 | // Parse options.
35 | var whitelist = options.whitelist || [];
36 | var blacklist = options.blacklist || [];
37 | var swLat = options.swLat;
38 | var swLng = options.swLng;
39 | var neLat = options.neLat;
40 | var neLng = options.neLng;
41 | var oSwLat = options.oSwLat;
42 | var oSwLng = options.oSwLng;
43 | var oNeLat = options.oNeLat;
44 | var oNeLng = options.oNeLng;
45 | var timestamp = options.timestamp || false;
46 |
47 | // Query options.
48 | debug('Selecting Pokémon where disappear_time > %s.', Math.round(Date.now() / 1000));
49 | var query_where = [
50 | [
51 | 'disappear_time > ' + FROM_UNIXTIME,
52 | [ Math.round(Date.now() / 1000) ]
53 | ]
54 | ];
55 |
56 | // Optional viewport.
57 | if (!isEmpty(swLat) && !isEmpty(swLng) && !isEmpty(neLat) && !isEmpty(neLng)) {
58 | query_where.push(
59 | [
60 | 'latitude >= ? AND latitude <= ?',
61 | [swLat, neLat]
62 | ]
63 | );
64 | query_where.push(
65 | [
66 | 'longitude >= ? AND longitude <= ?',
67 | [swLng, neLng]
68 | ]
69 | );
70 |
71 |
72 | /*
73 | * TODO: If we have a viewport, use distance ordering?
74 | */
75 |
76 | // Center of viewport.
77 | /*var viewport_width = neLng - swLng;
78 | var viewport_height = neLat - swLat;
79 | var middle_point_lat = neLat - (viewport_height / 2);
80 | var middle_point_lng = neLng - (viewport_width / 2);
81 |
82 | poke_options.attributes.include = [
83 | [
84 | // Calculate distance from middle point in viewport w/ MySQL.
85 | Sequelize.literal(`
86 | 3959 *
87 | acos(cos(radians(` + middle_point_lat + `)) *
88 | cos(radians(\`latitude\`)) *
89 | cos(radians(\`longitude\`) -
90 | radians(` + middle_point_lng + `)) +
91 | sin(radians(` + middle_point_lat + `)) *
92 | sin(radians(\`latitude\`)))
93 | `),
94 | 'distance'
95 | ]
96 | ];
97 |
98 | poke_options.order.push(Sequelize.literal('`distance` ASC'));*/
99 | }
100 |
101 | if (whitelist.length > 0) {
102 | query_where.push(
103 | [
104 | 'pokemon_id IN (?)',
105 | [whitelist]
106 | ]
107 | );
108 | }
109 |
110 | if (blacklist.length > 0) {
111 | query_where.push(
112 | [
113 | 'pokemon_id NOT IN (?)',
114 | [blacklist]
115 | ]
116 | );
117 | }
118 |
119 | // If timestamp is known, only load modified Pokemon.
120 | if (timestamp) {
121 | // Change POSIX timestamp to UTC time.
122 | timestamp = new Date(timestamp).getTime();
123 |
124 | query_where.push(
125 | [
126 | 'last_modified > ' + FROM_UNIXTIME,
127 | [Math.round(timestamp / 1000)]
128 | ]
129 | );
130 |
131 | // Send Pokémon in view but exclude those within old boundaries.
132 | // Don't use when timestamp is empty, it means we're intentionally trying to get
133 | // old Pokémon as well (new viewport or first request).
134 | if (!isEmpty(oSwLat) && !isEmpty(oSwLng) && !isEmpty(oNeLat) && !isEmpty(oNeLng)) {
135 | query_where.push(
136 | [
137 | 'latitude < ? AND latitude > ? AND longitude < ? AND longitude > ?',
138 | [oSwLat, oNeLat, oSwLng, oNeLng]
139 | ]
140 | );
141 | }
142 | } else {
143 | debug('Request without timestamp.');
144 | }
145 |
146 | // Prepare query.
147 | let query = ' WHERE ';
148 | let partials = [];
149 | let values = []; // Unnamed query params.
150 |
151 | // Add individual options.
152 | for (var i = 0; i < query_where.length; i++) {
153 | let w = query_where[i];
154 | // w = [ 'query ?', [opt1] ]
155 | partials.push(w[0]);
156 | values = values.concat(w[1]);
157 | }
158 | query += partials.join(' AND ');
159 |
160 | // Set limit.
161 | query += ' LIMIT ' + POKEMON_LIMIT_PER_QUERY;
162 |
163 | return [ query, values ];
164 | }
165 |
166 | function preparePokemonPromise(query, params) {
167 | return new Promise((resolve, reject) => {
168 | db.query(query, params, (err, results, fields) => {
169 | if (err) {
170 | return reject(err);
171 | } else {
172 | // Manipulate response.
173 | for (var i = 0; i < results.length; i++) {
174 | let poke = results[i];
175 | let pokemon_id = poke.pokemon_id;
176 |
177 | // Avoid timezone issues. This is a UTC timestamp.
178 | poke.disappear_time = poke.disappear_time.replace(' ', 'T') + 'Z';
179 | poke.last_modified = poke.last_modified.replace(' ', 'T') + 'Z';
180 |
181 | // Add name/types and transform times. Destructive.
182 | poke.disappear_time = Date.parse(poke.disappear_time) || 0;
183 | poke.last_modified = Date.parse(poke.last_modified) || 0;
184 | poke.pokemon_name = getPokemonName(pokedex, pokemon_id) || '';
185 | poke.pokemon_types = getPokemonTypes(pokedex, pokemon_id) || [];
186 | }
187 |
188 | return resolve(results);
189 | }
190 | });
191 | });
192 | }
193 |
194 |
195 | /* Model. */
196 |
197 | const tablename = 'pokemon';
198 | const Pokemon = {};
199 |
200 | Pokemon.get_active = (excluded, swLat, swLng, neLat, neLng, timestamp, oSwLat, oSwLng, oNeLat, oNeLng) => {
201 | // Prepare query.
202 | const query_where = prepareQuery({
203 | 'blacklist': excluded,
204 | 'swLat': swLat,
205 | 'swLng': swLng,
206 | 'neLat': neLat,
207 | 'neLng': neLng,
208 | 'oSwLat': oSwLat,
209 | 'oSwLng': oSwLng,
210 | 'oNeLat': oNeLat,
211 | 'oNeLng': oNeLng,
212 | 'timestamp': timestamp
213 | });
214 |
215 | const query = 'SELECT * FROM ' + tablename + query_where[0];
216 | const params = query_where[1];
217 |
218 | debug_query(query_where);
219 |
220 | // Return promise.
221 | return preparePokemonPromise(query, params);
222 | };
223 |
224 | Pokemon.get_active_by_ids = (ids, excluded, swLat, swLng, neLat, neLng) => {
225 | // Query options.
226 | const query_where = prepareQuery({
227 | 'whitelist': ids,
228 | 'blacklist': excluded,
229 | 'swLat': swLat,
230 | 'swLng': swLng,
231 | 'neLat': neLat,
232 | 'neLng': neLng
233 | });
234 |
235 | const query = 'SELECT * FROM ' + tablename + query_where[0];
236 | const params = query_where[1];
237 |
238 | debug_query(query_where);
239 |
240 | // Return promise.
241 | return preparePokemonPromise(query, params);
242 | };
243 |
244 | module.exports = Pokemon;
245 |
--------------------------------------------------------------------------------
/models/Pokestop.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Parse config.
4 | require('dotenv').config();
5 |
6 |
7 | /* Includes. */
8 |
9 | const db = require('../inc/db.js').pool;
10 | const utils = require('../inc/utils.js');
11 |
12 |
13 | /* Readability references. */
14 |
15 | var isEmpty = utils.isEmpty;
16 |
17 |
18 | /* Settings. */
19 |
20 | const POKESTOP_LIMIT_PER_QUERY = parseInt(process.env.POKESTOP_LIMIT_PER_QUERY) || 50000;
21 |
22 |
23 | /* Helpers. */
24 |
25 | // Make sure SQL uses proper timezone.
26 | const FROM_UNIXTIME = "CONVERT_TZ(FROM_UNIXTIME(?), @@session.time_zone, '+00:00')";
27 |
28 | function prepareQueryOptions(options) {
29 | // Parse options.
30 | var swLat = options.swLat;
31 | var swLng = options.swLng;
32 | var neLat = options.neLat;
33 | var neLng = options.neLng;
34 | var oSwLat = options.oSwLat;
35 | var oSwLng = options.oSwLng;
36 | var oNeLat = options.oNeLat;
37 | var oNeLng = options.oNeLng;
38 | var timestamp = options.timestamp || false;
39 | var lured = options.lured || false;
40 |
41 | // Query options.
42 | var query_where = [];
43 |
44 | // Optional viewport.
45 | if (!isEmpty(swLat) && !isEmpty(swLng) && !isEmpty(neLat) && !isEmpty(neLng)) {
46 | query_where.push(
47 | [
48 | 'latitude >= ? AND latitude <= ?',
49 | [swLat, neLat]
50 | ]
51 | );
52 | query_where.push(
53 | [
54 | 'longitude >= ? AND longitude <= ?',
55 | [swLng, neLng]
56 | ]
57 | );
58 | }
59 |
60 | if (timestamp) {
61 | // Change POSIX timestamp to UTC time.
62 | timestamp = new Date(timestamp).getTime();
63 |
64 | query_where.push(
65 | [
66 | 'last_updated > ' + FROM_UNIXTIME,
67 | [Math.round(timestamp / 1000)]
68 | ]
69 | );
70 |
71 | // Send Pokéstops in view but exclude those within old boundaries.
72 | // Don't use when timestamp is empty, it means we're intentionally trying to get
73 | // old Pokéstops as well (new viewport or first request).
74 | if (!isEmpty(oSwLat) && !isEmpty(oSwLng) && !isEmpty(oNeLat) && !isEmpty(oNeLng)) {
75 | query_where.push(
76 | [
77 | 'latitude < ? AND latitude > ? AND longitude < ? AND longitude > ?',
78 | [oSwLat, oNeLat, oSwLng, oNeLng]
79 | ]
80 | );
81 | }
82 | }
83 |
84 | // Lured stops.
85 | if (lured) {
86 | query_where.push(
87 | [
88 | 'active_fort_modifier IS NOT NULL',
89 | []
90 | ]
91 | );
92 | }
93 |
94 | // Prepare query.
95 | let query = ' WHERE ';
96 | let partials = [];
97 | let values = []; // Unnamed query params.
98 |
99 | // Add individual options.
100 | for (var i = 0; i < query_where.length; i++) {
101 | let w = query_where[i];
102 | // w = [ 'query ?', [opt1] ]
103 | partials.push(w[0]);
104 | values = values.concat(w[1]);
105 | }
106 | query += partials.join(' AND ');
107 |
108 | // Set limit.
109 | query += ' LIMIT ' + POKESTOP_LIMIT_PER_QUERY;
110 |
111 | return [ query, values ];
112 | }
113 |
114 | function preparePokestopPromise(query, params) {
115 | return new Promise((resolve, reject) => {
116 | db.query(query, params, (err, results, fields) => {
117 | if (err) {
118 | reject(err);
119 | } else {
120 | // If there are no pokéstops, let's just go. 👀
121 | if (results.length == 0) {
122 | return resolve(results);
123 | }
124 |
125 | // Manipulate pokéstops, destructive operations.
126 | for (var i = 0; i < results.length; i++) {
127 | let pokestop = results[i];
128 |
129 | // Avoid timezone issues. This is a UTC timestamp.
130 | pokestop.last_modified = pokestop.last_modified.replace(' ', 'T') + 'Z';
131 | pokestop.last_updated = pokestop.last_updated.replace(' ', 'T') + 'Z';
132 |
133 | if (pokestop.lure_expiration) {
134 | pokestop.lure_expiration = pokestop.lure_expiration.replace(' ', 'T') + 'Z';
135 | }
136 |
137 | // Convert datetime to UNIX timestamp.
138 | pokestop.last_modified = Date.parse(pokestop.last_modified) || 0;
139 | pokestop.last_updated = Date.parse(pokestop.last_updated) || 0;
140 | pokestop.lure_expiration = Date.parse(pokestop.lure_expiration) || 0;
141 | }
142 |
143 | return resolve(results);
144 | }
145 | });
146 | });
147 | }
148 |
149 |
150 | /* Model. */
151 |
152 | const tablename = 'pokestop';
153 | const Pokestop = {};
154 |
155 | // Get active Pokéstops by coords or timestamp.
156 | Pokestop.get_stops = (swLat, swLng, neLat, neLng, lured, timestamp, oSwLat, oSwLng, oNeLat, oNeLng) => {
157 | // Prepare query.
158 | var query_where = prepareQueryOptions({
159 | 'swLat': swLat,
160 | 'swLng': swLng,
161 | 'neLat': neLat,
162 | 'neLng': neLng,
163 | 'oSwLat': oSwLat,
164 | 'oSwLng': oSwLng,
165 | 'oNeLat': oNeLat,
166 | 'oNeLng': oNeLng,
167 | 'lured': lured,
168 | 'timestamp': timestamp
169 | });
170 |
171 | const query = 'SELECT * FROM ' + tablename + query_where[0];
172 | const params = query_where[1];
173 |
174 | // Return promise.
175 | return preparePokestopPromise(query, params);
176 | };
177 |
178 | module.exports = Pokestop;
--------------------------------------------------------------------------------
/models/Raid.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // Parse config.
4 | require('dotenv').config();
5 |
6 |
7 | /* Includes. */
8 |
9 | const db = require('../inc/db.js').pool;
10 |
11 |
12 | /* Helpers. */
13 |
14 | // Make sure SQL uses proper timezone.
15 | const FROM_UNIXTIME = "CONVERT_TZ(FROM_UNIXTIME(?), @@session.time_zone, '+00:00')";
16 |
17 | function prepareRaidPromise(query, params) {
18 | return new Promise((resolve, reject) => {
19 | db.query(query, params, (err, results, fields) => {
20 | if (err) {
21 | return reject(err);
22 | } else {
23 | return resolve(results);
24 | }
25 | });
26 | });
27 | }
28 |
29 |
30 | /* Model. */
31 |
32 | const tablename = 'raid';
33 | const Raid = {};
34 |
35 | // Get raid by gym ID.
36 | Raid.from_gym_id = (id) => {
37 | // This is a simple one.
38 | const query = 'SELECT * FROM ' + tablename + ' WHERE gym_id = ? LIMIT 1';
39 | const params = [ id ];
40 |
41 | // Return promise.
42 | return prepareRaidPromise(query, params);
43 | };
44 |
45 | // Get raids by gym IDs.
46 | Raid.from_gym_ids = (ids) => {
47 | // This is another simple one.
48 | const query = 'SELECT * FROM ' + tablename + ' WHERE gym_id IN (?)';
49 | const params = [ ids ];
50 |
51 | // Return promise.
52 | return prepareRaidPromise(query, params);
53 | };
54 |
55 |
56 | module.exports = Raid;
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rm-node-webserver",
3 | "version": "2.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "asn1": {
8 | "version": "0.2.3",
9 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
10 | "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
11 | },
12 | "bcrypt-pbkdf": {
13 | "version": "1.0.1",
14 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
15 | "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
16 | "optional": true,
17 | "requires": {
18 | "tweetnacl": "0.14.5"
19 | }
20 | },
21 | "bunyan": {
22 | "version": "1.8.12",
23 | "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz",
24 | "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=",
25 | "requires": {
26 | "dtrace-provider": "0.8.5",
27 | "moment": "2.19.1",
28 | "mv": "2.1.1",
29 | "safe-json-stringify": "1.0.4"
30 | },
31 | "dependencies": {
32 | "moment": {
33 | "version": "2.19.1",
34 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.19.1.tgz",
35 | "integrity": "sha1-VtoaLRy/AdOLfhr8McELz6GSkWc=",
36 | "optional": true
37 | }
38 | }
39 | },
40 | "clone-regexp": {
41 | "version": "1.0.0",
42 | "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-1.0.0.tgz",
43 | "integrity": "sha1-6uCiQT9VwJQvgYwin+/OhF1/Oxw=",
44 | "requires": {
45 | "is-regexp": "1.0.0",
46 | "is-supported-regexp-flag": "1.0.0"
47 | }
48 | },
49 | "cluster": {
50 | "version": "0.7.7",
51 | "resolved": "https://registry.npmjs.org/cluster/-/cluster-0.7.7.tgz",
52 | "integrity": "sha1-5JfiZ8yVa9CwUTrbSqOTNX0Ahe8=",
53 | "requires": {
54 | "log": "1.4.0",
55 | "mkdirp": "0.5.1"
56 | },
57 | "dependencies": {
58 | "log": {
59 | "version": "1.4.0",
60 | "resolved": "https://registry.npmjs.org/log/-/log-1.4.0.tgz",
61 | "integrity": "sha1-S6HYkP3iSbAx3KA7w36q8yVlbxw="
62 | },
63 | "mkdirp": {
64 | "version": "0.5.1",
65 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
66 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
67 | "requires": {
68 | "minimist": "0.0.8"
69 | }
70 | }
71 | }
72 | },
73 | "csv": {
74 | "version": "1.2.1",
75 | "resolved": "https://registry.npmjs.org/csv/-/csv-1.2.1.tgz",
76 | "integrity": "sha1-UjHt/BxxUlEuxFeBB2p6l/9SXAw=",
77 | "requires": {
78 | "csv-generate": "1.1.2",
79 | "csv-parse": "1.3.3",
80 | "csv-stringify": "1.1.2",
81 | "stream-transform": "0.2.2"
82 | }
83 | },
84 | "csv-generate": {
85 | "version": "1.1.2",
86 | "resolved": "https://registry.npmjs.org/csv-generate/-/csv-generate-1.1.2.tgz",
87 | "integrity": "sha1-7GsA7a7W5ZrZwgWC9MNk4osUYkA="
88 | },
89 | "csv-parse": {
90 | "version": "1.3.3",
91 | "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-1.3.3.tgz",
92 | "integrity": "sha1-0c/YdDwvhJoKuy/VRNtWaV0ZpJA="
93 | },
94 | "csv-stringify": {
95 | "version": "1.1.2",
96 | "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-1.1.2.tgz",
97 | "integrity": "sha1-d6QVJlgbzjOA8SsA18W7rHDIK1g=",
98 | "requires": {
99 | "lodash.get": "4.4.2"
100 | }
101 | },
102 | "dashdash": {
103 | "version": "1.14.1",
104 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
105 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
106 | "requires": {
107 | "assert-plus": "1.0.0"
108 | },
109 | "dependencies": {
110 | "assert-plus": {
111 | "version": "1.0.0",
112 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
113 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
114 | }
115 | }
116 | },
117 | "debug": {
118 | "version": "3.1.0",
119 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
120 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
121 | "requires": {
122 | "ms": "2.0.0"
123 | },
124 | "dependencies": {
125 | "ms": {
126 | "version": "2.0.0",
127 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
128 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
129 | }
130 | }
131 | },
132 | "detect-node": {
133 | "version": "2.0.3",
134 | "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.3.tgz",
135 | "integrity": "sha1-ogM8CcyOFY03dI+951B4Mr1s4Sc="
136 | },
137 | "dotenv": {
138 | "version": "4.0.0",
139 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-4.0.0.tgz",
140 | "integrity": "sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0="
141 | },
142 | "dtrace-provider": {
143 | "version": "0.8.5",
144 | "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.5.tgz",
145 | "integrity": "sha1-mOu6Ihr6xG4cOf02hY2Pk2dSS5I=",
146 | "optional": true,
147 | "requires": {
148 | "nan": "2.7.0"
149 | },
150 | "dependencies": {
151 | "nan": {
152 | "version": "2.7.0",
153 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.7.0.tgz",
154 | "integrity": "sha1-2Vv3IeyHfgjbJ27T/G63j5CDrUY=",
155 | "optional": true
156 | }
157 | }
158 | },
159 | "ecc-jsbn": {
160 | "version": "0.1.1",
161 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
162 | "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
163 | "optional": true,
164 | "requires": {
165 | "jsbn": "0.1.1"
166 | }
167 | },
168 | "escape-regexp-component": {
169 | "version": "1.0.2",
170 | "resolved": "https://registry.npmjs.org/escape-regexp-component/-/escape-regexp-component-1.0.2.tgz",
171 | "integrity": "sha1-nGO20LJf8qiMOtvRjFthrMO5+qI="
172 | },
173 | "ewma": {
174 | "version": "2.0.1",
175 | "resolved": "https://registry.npmjs.org/ewma/-/ewma-2.0.1.tgz",
176 | "integrity": "sha512-MYYK17A76cuuyvkR7MnqLW4iFYPEi5Isl2qb8rXiWpLiwFS9dxW/rncuNnjjgSENuVqZQkIuR4+DChVL4g1lnw==",
177 | "requires": {
178 | "assert-plus": "1.0.0"
179 | },
180 | "dependencies": {
181 | "assert-plus": {
182 | "version": "1.0.0",
183 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
184 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
185 | }
186 | }
187 | },
188 | "extsprintf": {
189 | "version": "1.0.2",
190 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz",
191 | "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA="
192 | },
193 | "formidable": {
194 | "version": "1.1.1",
195 | "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.1.1.tgz",
196 | "integrity": "sha1-lriIb3w8NQi5Mta9cMTTqI818ak="
197 | },
198 | "getpass": {
199 | "version": "0.1.7",
200 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
201 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
202 | "requires": {
203 | "assert-plus": "1.0.0"
204 | },
205 | "dependencies": {
206 | "assert-plus": {
207 | "version": "1.0.0",
208 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
209 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
210 | }
211 | }
212 | },
213 | "glob": {
214 | "version": "6.0.4",
215 | "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
216 | "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
217 | "optional": true,
218 | "requires": {
219 | "inflight": "1.0.6",
220 | "inherits": "2.0.3",
221 | "minimatch": "3.0.4",
222 | "once": "1.4.0",
223 | "path-is-absolute": "1.0.1"
224 | },
225 | "dependencies": {
226 | "balanced-match": {
227 | "version": "1.0.0",
228 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
229 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
230 | "optional": true
231 | },
232 | "brace-expansion": {
233 | "version": "1.1.8",
234 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
235 | "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
236 | "optional": true,
237 | "requires": {
238 | "balanced-match": "1.0.0",
239 | "concat-map": "0.0.1"
240 | }
241 | },
242 | "concat-map": {
243 | "version": "0.0.1",
244 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
245 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
246 | "optional": true
247 | },
248 | "inflight": {
249 | "version": "1.0.6",
250 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
251 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
252 | "optional": true,
253 | "requires": {
254 | "once": "1.4.0",
255 | "wrappy": "1.0.2"
256 | }
257 | },
258 | "inherits": {
259 | "version": "2.0.3",
260 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
261 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
262 | "optional": true
263 | },
264 | "minimatch": {
265 | "version": "3.0.4",
266 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
267 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
268 | "optional": true,
269 | "requires": {
270 | "brace-expansion": "1.1.8"
271 | }
272 | },
273 | "once": {
274 | "version": "1.4.0",
275 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
276 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
277 | "requires": {
278 | "wrappy": "1.0.2"
279 | }
280 | },
281 | "path-is-absolute": {
282 | "version": "1.0.1",
283 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
284 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
285 | "optional": true
286 | },
287 | "wrappy": {
288 | "version": "1.0.2",
289 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
290 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
291 | }
292 | }
293 | },
294 | "handle-thing": {
295 | "version": "1.2.5",
296 | "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz",
297 | "integrity": "sha1-/Xqtcmvxpf0W38KbL3pmAdJxOcQ="
298 | },
299 | "has-flag": {
300 | "version": "2.0.0",
301 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz",
302 | "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE="
303 | },
304 | "hpack.js": {
305 | "version": "2.1.6",
306 | "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
307 | "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=",
308 | "requires": {
309 | "inherits": "2.0.3",
310 | "obuf": "1.1.1",
311 | "readable-stream": "2.3.3",
312 | "wbuf": "1.7.2"
313 | },
314 | "dependencies": {
315 | "core-util-is": {
316 | "version": "1.0.2",
317 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
318 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
319 | },
320 | "inherits": {
321 | "version": "2.0.3",
322 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
323 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
324 | },
325 | "isarray": {
326 | "version": "1.0.0",
327 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
328 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
329 | },
330 | "readable-stream": {
331 | "version": "2.3.3",
332 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
333 | "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
334 | "requires": {
335 | "core-util-is": "1.0.2",
336 | "inherits": "2.0.3",
337 | "isarray": "1.0.0",
338 | "process-nextick-args": "1.0.7",
339 | "safe-buffer": "5.1.1",
340 | "string_decoder": "1.0.3",
341 | "util-deprecate": "1.0.2"
342 | }
343 | },
344 | "string_decoder": {
345 | "version": "1.0.3",
346 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
347 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
348 | "requires": {
349 | "safe-buffer": "5.1.1"
350 | }
351 | }
352 | }
353 | },
354 | "http-deceiver": {
355 | "version": "1.2.7",
356 | "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
357 | "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc="
358 | },
359 | "is-regexp": {
360 | "version": "1.0.0",
361 | "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz",
362 | "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk="
363 | },
364 | "is-supported-regexp-flag": {
365 | "version": "1.0.0",
366 | "resolved": "https://registry.npmjs.org/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.0.tgz",
367 | "integrity": "sha1-i1IMhfrnolM4LUsCZS4EVXbhO7g="
368 | },
369 | "jsbn": {
370 | "version": "0.1.1",
371 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
372 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
373 | "optional": true
374 | },
375 | "json-schema": {
376 | "version": "0.2.3",
377 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
378 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
379 | },
380 | "jsprim": {
381 | "version": "1.4.0",
382 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz",
383 | "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=",
384 | "requires": {
385 | "assert-plus": "1.0.0",
386 | "extsprintf": "1.0.2",
387 | "json-schema": "0.2.3",
388 | "verror": "1.3.6"
389 | },
390 | "dependencies": {
391 | "assert-plus": {
392 | "version": "1.0.0",
393 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
394 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
395 | }
396 | }
397 | },
398 | "lodash.get": {
399 | "version": "4.4.2",
400 | "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
401 | "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
402 | },
403 | "minimalistic-assert": {
404 | "version": "1.0.0",
405 | "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz",
406 | "integrity": "sha1-cCvi3aazf0g2vLP121ZkG2Sh09M="
407 | },
408 | "minimist": {
409 | "version": "0.0.8",
410 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
411 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
412 | },
413 | "mv": {
414 | "version": "2.1.1",
415 | "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
416 | "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=",
417 | "optional": true,
418 | "requires": {
419 | "mkdirp": "0.5.1",
420 | "ncp": "2.0.0",
421 | "rimraf": "2.4.5"
422 | },
423 | "dependencies": {
424 | "mkdirp": {
425 | "version": "0.5.1",
426 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
427 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
428 | "optional": true,
429 | "requires": {
430 | "minimist": "0.0.8"
431 | }
432 | },
433 | "rimraf": {
434 | "version": "2.4.5",
435 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
436 | "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=",
437 | "optional": true,
438 | "requires": {
439 | "glob": "6.0.4"
440 | }
441 | }
442 | }
443 | },
444 | "mysql": {
445 | "version": "2.15.0",
446 | "resolved": "https://registry.npmjs.org/mysql/-/mysql-2.15.0.tgz",
447 | "integrity": "sha512-C7tjzWtbN5nzkLIV+E8Crnl9bFyc7d3XJcBAvHKEVkjrYjogz3llo22q6s/hw+UcsE4/844pDob9ac+3dVjQSA==",
448 | "requires": {
449 | "bignumber.js": "4.0.4",
450 | "readable-stream": "2.3.3",
451 | "safe-buffer": "5.1.1",
452 | "sqlstring": "2.3.0"
453 | },
454 | "dependencies": {
455 | "bignumber.js": {
456 | "version": "4.0.4",
457 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-4.0.4.tgz",
458 | "integrity": "sha512-LDXpJKVzEx2/OqNbG9mXBNvHuiRL4PzHCGfnANHMJ+fv68Ads3exDVJeGDJws+AoNEuca93bU3q+S0woeUaCdg=="
459 | },
460 | "core-util-is": {
461 | "version": "1.0.2",
462 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
463 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
464 | },
465 | "inherits": {
466 | "version": "2.0.3",
467 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
468 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
469 | },
470 | "isarray": {
471 | "version": "1.0.0",
472 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
473 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
474 | },
475 | "readable-stream": {
476 | "version": "2.3.3",
477 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
478 | "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
479 | "requires": {
480 | "core-util-is": "1.0.2",
481 | "inherits": "2.0.3",
482 | "isarray": "1.0.0",
483 | "process-nextick-args": "1.0.7",
484 | "safe-buffer": "5.1.1",
485 | "string_decoder": "1.0.3",
486 | "util-deprecate": "1.0.2"
487 | }
488 | },
489 | "sqlstring": {
490 | "version": "2.3.0",
491 | "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.0.tgz",
492 | "integrity": "sha1-UluKT9Jtb3GqYegipsr5dtMa0qg="
493 | },
494 | "string_decoder": {
495 | "version": "1.0.3",
496 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
497 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
498 | "requires": {
499 | "safe-buffer": "5.1.1"
500 | }
501 | }
502 | }
503 | },
504 | "ncp": {
505 | "version": "2.0.0",
506 | "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
507 | "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=",
508 | "optional": true
509 | },
510 | "obuf": {
511 | "version": "1.1.1",
512 | "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.1.tgz",
513 | "integrity": "sha1-EEEktsYCxnlogaBCVB0220OlJk4="
514 | },
515 | "pidusage": {
516 | "version": "1.1.6",
517 | "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-1.1.6.tgz",
518 | "integrity": "sha512-ua0CWpD05cMMumfoeUcF1zcvpDkkWxedbkLfGQWwkTsHVkaBPUs7OY/0OmnVU/CrcEhRCwCx5VJ4nMKjnsgGIg=="
519 | },
520 | "process-nextick-args": {
521 | "version": "1.0.7",
522 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
523 | "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
524 | },
525 | "restify": {
526 | "version": "6.2.3",
527 | "resolved": "https://registry.npmjs.org/restify/-/restify-6.2.3.tgz",
528 | "integrity": "sha1-bPtHNZeday0iv4f3vfyn6Tkytng=",
529 | "requires": {
530 | "assert-plus": "1.0.0",
531 | "bunyan": "1.8.12",
532 | "clone-regexp": "1.0.0",
533 | "csv": "1.2.1",
534 | "dtrace-provider": "0.8.5",
535 | "escape-regexp-component": "1.0.2",
536 | "ewma": "2.0.1",
537 | "formidable": "1.1.1",
538 | "http-signature": "1.2.0",
539 | "lodash": "4.17.4",
540 | "lru-cache": "4.1.1",
541 | "mime": "1.4.1",
542 | "negotiator": "0.6.1",
543 | "once": "1.4.0",
544 | "pidusage": "1.1.6",
545 | "qs": "6.5.1",
546 | "restify-errors": "5.0.0",
547 | "semver": "5.4.1",
548 | "spdy": "3.4.7",
549 | "uuid": "3.1.0",
550 | "vasync": "1.6.4",
551 | "verror": "1.10.0"
552 | },
553 | "dependencies": {
554 | "assert-plus": {
555 | "version": "1.0.0",
556 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
557 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
558 | },
559 | "core-util-is": {
560 | "version": "1.0.2",
561 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
562 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
563 | },
564 | "extsprintf": {
565 | "version": "1.3.0",
566 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
567 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
568 | },
569 | "http-signature": {
570 | "version": "1.2.0",
571 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
572 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
573 | "requires": {
574 | "assert-plus": "1.0.0",
575 | "jsprim": "1.4.0",
576 | "sshpk": "1.13.1"
577 | }
578 | },
579 | "lodash": {
580 | "version": "4.17.4",
581 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
582 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
583 | },
584 | "lru-cache": {
585 | "version": "4.1.1",
586 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
587 | "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
588 | "requires": {
589 | "pseudomap": "1.0.2",
590 | "yallist": "2.1.2"
591 | }
592 | },
593 | "mime": {
594 | "version": "1.4.1",
595 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
596 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
597 | },
598 | "negotiator": {
599 | "version": "0.6.1",
600 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
601 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
602 | },
603 | "once": {
604 | "version": "1.4.0",
605 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
606 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
607 | "requires": {
608 | "wrappy": "1.0.2"
609 | }
610 | },
611 | "pseudomap": {
612 | "version": "1.0.2",
613 | "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
614 | "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
615 | },
616 | "qs": {
617 | "version": "6.5.1",
618 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
619 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A=="
620 | },
621 | "semver": {
622 | "version": "5.4.1",
623 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
624 | "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg=="
625 | },
626 | "uuid": {
627 | "version": "3.1.0",
628 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz",
629 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g=="
630 | },
631 | "verror": {
632 | "version": "1.10.0",
633 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
634 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
635 | "requires": {
636 | "assert-plus": "1.0.0",
637 | "core-util-is": "1.0.2",
638 | "extsprintf": "1.3.0"
639 | }
640 | },
641 | "wrappy": {
642 | "version": "1.0.2",
643 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
644 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
645 | },
646 | "yallist": {
647 | "version": "2.1.2",
648 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
649 | "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
650 | }
651 | }
652 | },
653 | "restify-cors-middleware": {
654 | "version": "1.0.1",
655 | "resolved": "https://registry.npmjs.org/restify-cors-middleware/-/restify-cors-middleware-1.0.1.tgz",
656 | "integrity": "sha1-Y4jQ1S0obiJlATPRUaidSLIvoKs=",
657 | "requires": {
658 | "assert-plus": "1.0.0"
659 | },
660 | "dependencies": {
661 | "assert-plus": {
662 | "version": "1.0.0",
663 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
664 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
665 | }
666 | }
667 | },
668 | "restify-errors": {
669 | "version": "5.0.0",
670 | "resolved": "https://registry.npmjs.org/restify-errors/-/restify-errors-5.0.0.tgz",
671 | "integrity": "sha512-+vby9Kxf7qlzvbZSTIEGkIixkeHG+pVCl34dk6eKnL+ua4pCezpdLT/1/eabzPZb65ADrgoc04jeWrrF1E1pvQ==",
672 | "requires": {
673 | "assert-plus": "1.0.0",
674 | "lodash": "4.17.4",
675 | "safe-json-stringify": "1.0.4",
676 | "verror": "1.10.0"
677 | },
678 | "dependencies": {
679 | "assert-plus": {
680 | "version": "1.0.0",
681 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
682 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
683 | },
684 | "core-util-is": {
685 | "version": "1.0.2",
686 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
687 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
688 | },
689 | "extsprintf": {
690 | "version": "1.3.0",
691 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
692 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
693 | },
694 | "lodash": {
695 | "version": "4.17.4",
696 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
697 | "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
698 | },
699 | "verror": {
700 | "version": "1.10.0",
701 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
702 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
703 | "requires": {
704 | "assert-plus": "1.0.0",
705 | "core-util-is": "1.0.2",
706 | "extsprintf": "1.3.0"
707 | }
708 | }
709 | }
710 | },
711 | "safe-buffer": {
712 | "version": "5.1.1",
713 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
714 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
715 | },
716 | "safe-json-stringify": {
717 | "version": "1.0.4",
718 | "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz",
719 | "integrity": "sha1-gaCY9Efku8P/MxKiQ1IbwGDvWRE=",
720 | "optional": true
721 | },
722 | "select-hose": {
723 | "version": "2.0.0",
724 | "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
725 | "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo="
726 | },
727 | "spdy": {
728 | "version": "3.4.7",
729 | "resolved": "https://registry.npmjs.org/spdy/-/spdy-3.4.7.tgz",
730 | "integrity": "sha1-Qv9B7OXMD5mjpsKKq7c/XDsDrLw=",
731 | "requires": {
732 | "debug": "2.6.9",
733 | "handle-thing": "1.2.5",
734 | "http-deceiver": "1.2.7",
735 | "safe-buffer": "5.1.1",
736 | "select-hose": "2.0.0",
737 | "spdy-transport": "2.0.20"
738 | },
739 | "dependencies": {
740 | "debug": {
741 | "version": "2.6.9",
742 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
743 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
744 | "requires": {
745 | "ms": "2.0.0"
746 | }
747 | },
748 | "ms": {
749 | "version": "2.0.0",
750 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
751 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
752 | }
753 | }
754 | },
755 | "spdy-transport": {
756 | "version": "2.0.20",
757 | "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.0.20.tgz",
758 | "integrity": "sha1-c15yBUxIayNU/onnAiVgBKOazk0=",
759 | "requires": {
760 | "debug": "2.6.9",
761 | "detect-node": "2.0.3",
762 | "hpack.js": "2.1.6",
763 | "obuf": "1.1.1",
764 | "readable-stream": "2.3.3",
765 | "safe-buffer": "5.1.1",
766 | "wbuf": "1.7.2"
767 | },
768 | "dependencies": {
769 | "core-util-is": {
770 | "version": "1.0.2",
771 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
772 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
773 | },
774 | "debug": {
775 | "version": "2.6.9",
776 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
777 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
778 | "requires": {
779 | "ms": "2.0.0"
780 | }
781 | },
782 | "inherits": {
783 | "version": "2.0.3",
784 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
785 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
786 | },
787 | "isarray": {
788 | "version": "1.0.0",
789 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
790 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
791 | },
792 | "ms": {
793 | "version": "2.0.0",
794 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
795 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
796 | },
797 | "readable-stream": {
798 | "version": "2.3.3",
799 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
800 | "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
801 | "requires": {
802 | "core-util-is": "1.0.2",
803 | "inherits": "2.0.3",
804 | "isarray": "1.0.0",
805 | "process-nextick-args": "1.0.7",
806 | "safe-buffer": "5.1.1",
807 | "string_decoder": "1.0.3",
808 | "util-deprecate": "1.0.2"
809 | }
810 | },
811 | "string_decoder": {
812 | "version": "1.0.3",
813 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
814 | "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
815 | "requires": {
816 | "safe-buffer": "5.1.1"
817 | }
818 | }
819 | }
820 | },
821 | "sshpk": {
822 | "version": "1.13.1",
823 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
824 | "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=",
825 | "requires": {
826 | "asn1": "0.2.3",
827 | "assert-plus": "1.0.0",
828 | "bcrypt-pbkdf": "1.0.1",
829 | "dashdash": "1.14.1",
830 | "ecc-jsbn": "0.1.1",
831 | "getpass": "0.1.7",
832 | "jsbn": "0.1.1",
833 | "tweetnacl": "0.14.5"
834 | },
835 | "dependencies": {
836 | "assert-plus": {
837 | "version": "1.0.0",
838 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
839 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
840 | }
841 | }
842 | },
843 | "stream-transform": {
844 | "version": "0.2.2",
845 | "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-0.2.2.tgz",
846 | "integrity": "sha1-dYZ0h/SVKPi/HYJJllh1PQLfeDg="
847 | },
848 | "supports-color": {
849 | "version": "4.5.0",
850 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
851 | "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=",
852 | "requires": {
853 | "has-flag": "2.0.0"
854 | }
855 | },
856 | "toobusy-js": {
857 | "version": "0.5.1",
858 | "resolved": "https://registry.npmjs.org/toobusy-js/-/toobusy-js-0.5.1.tgz",
859 | "integrity": "sha1-VRH3j2qHpqUS1E/bDvoTZyIX9lk="
860 | },
861 | "tweetnacl": {
862 | "version": "0.14.5",
863 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
864 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
865 | "optional": true
866 | },
867 | "util-deprecate": {
868 | "version": "1.0.2",
869 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
870 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
871 | },
872 | "vasync": {
873 | "version": "1.6.4",
874 | "resolved": "https://registry.npmjs.org/vasync/-/vasync-1.6.4.tgz",
875 | "integrity": "sha1-3+k2Fq0OeugBszKp2Iv8XNyOHR8=",
876 | "requires": {
877 | "verror": "1.6.0"
878 | },
879 | "dependencies": {
880 | "extsprintf": {
881 | "version": "1.2.0",
882 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.2.0.tgz",
883 | "integrity": "sha1-WtlGwi9bMrp/jNdCZxHG6KP8JSk="
884 | },
885 | "verror": {
886 | "version": "1.6.0",
887 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.6.0.tgz",
888 | "integrity": "sha1-fROyex+swuLakEBetepuW90lLqU=",
889 | "requires": {
890 | "extsprintf": "1.2.0"
891 | }
892 | }
893 | }
894 | },
895 | "verror": {
896 | "version": "1.3.6",
897 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz",
898 | "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=",
899 | "requires": {
900 | "extsprintf": "1.0.2"
901 | }
902 | },
903 | "wbuf": {
904 | "version": "1.7.2",
905 | "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.2.tgz",
906 | "integrity": "sha1-1pe5nx9ZUS3ydRvkJ2nBWAtYAf4=",
907 | "requires": {
908 | "minimalistic-assert": "1.0.0"
909 | }
910 | }
911 | }
912 | }
913 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rm-node-webserver",
3 | "version": "2.2.1",
4 | "description": "Event-driven RocketMap webserver in Node.js as a replacement for python's Flask/werkzeug development webserver.",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "https://github.com/sebastienvercammen/devkat-rm-node-webserver.git"
12 | },
13 | "keywords": [
14 | "rocketmap",
15 | "devkat",
16 | "webserver",
17 | "node.js",
18 | "restify"
19 | ],
20 | "author": "devkat",
21 | "private": true,
22 | "license": "SEE LICENSE IN LICENSE.md",
23 | "dependencies": {
24 | "cluster": "^0.7.7",
25 | "debug": "^3.1.0",
26 | "dotenv": "^4.0.0",
27 | "mysql": "^2.13.0",
28 | "restify": "^6.0.1",
29 | "restify-cors-middleware": "^1.0.1",
30 | "restify-errors": "^5.0.0",
31 | "supports-color": "^4.4.0",
32 | "toobusy-js": "^0.5.1"
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/routes/captcha.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 |
4 | router.get('/bookmarklet', function (req, res) {
5 | res.send('Not implemented yet.');
6 | });
7 |
8 | router.get('/inject.js', function (req, res) {
9 | res.send('Not implemented yet.');
10 | });
11 |
12 | router.get('/submit_token', function (req, res) {
13 | res.send('Not implemented yet.');
14 | });
15 |
16 | module.exports = router;
--------------------------------------------------------------------------------
/routes/index.js:
--------------------------------------------------------------------------------
1 | // Attach routes.
2 | module.exports = (server) => {
3 | require('./raw_data')(server);
4 | };
5 |
--------------------------------------------------------------------------------
/routes/raw_data.js:
--------------------------------------------------------------------------------
1 | // Parse config.
2 | require('dotenv').config();
3 |
4 | const debug = require('debug')('devkat:routes:raw_data');
5 | const pokemon_debug = require('debug')('devkat:db:pokemon');
6 |
7 | const errors = require('restify-errors');
8 | const corsMiddleware = require('restify-cors-middleware');
9 |
10 | const utils = require('../inc/utils.js');
11 | const blacklist = require('../inc/blacklist.js');
12 |
13 | const Pokemon = require('../models/Pokemon');
14 | const Pokestop = require('../models/Pokestop');
15 | const Gym = require('../models/Gym');
16 |
17 |
18 | /* Readability. */
19 | const isEmpty = utils.isEmpty;
20 | const isUndefined = utils.isUndefined;
21 |
22 |
23 | /* Settings. */
24 | const ROUTE_RAW_DATA = process.env.ROUTE_RAW_DATA || '/raw_data';
25 | const CORS_WHITELIST = process.env.CORS_WHITELIST || '';
26 |
27 |
28 | /* CORS. */
29 | const whitelist = CORS_WHITELIST.split(',');
30 | const cors = corsMiddleware({
31 | origins: whitelist
32 | });
33 |
34 |
35 | /* Helpers. */
36 |
37 | // Query is a combination of partials. When all completed, return response.
38 | function partialCompleted(pokemon, pokestops, gyms, res, response) {
39 | if (pokemon && pokestops && gyms) {
40 | return res.json(response);
41 | }
42 | }
43 |
44 | function queryHasRequiredParams(query, requiredParams) {
45 | for (let i = 0; i < requiredParams.length; i++) {
46 | let item = requiredParams[i];
47 |
48 | // Missing or empty parameter, bad request.
49 | if (!query.hasOwnProperty(item) || isEmpty(item)) {
50 | return false;
51 | }
52 | }
53 |
54 | return true;
55 | }
56 |
57 | function parseGetParam(param, defaultVal) {
58 | // Undefined?
59 | if (isEmpty(param)) {
60 | return defaultVal;
61 | }
62 |
63 | // Ok, we have a value.
64 | var val = param;
65 |
66 | // Truthy/falsy strings?
67 | if (val === 'true') {
68 | return true;
69 | } else if (val === 'false') {
70 | return false;
71 | }
72 |
73 | // Make sure single values adhere to defaultVal type.
74 | if (defaultVal instanceof Array && typeof val === 'string') {
75 | val = [val];
76 | }
77 |
78 | // No empty values should be left over.
79 | if (val instanceof Array) {
80 | for (let i = 0; i < val.length; i++) {
81 | let item = val[i];
82 |
83 | // Remove empty item.
84 | if (item.length === 0) {
85 | val = val.splice(i, 1);
86 | }
87 | }
88 | }
89 |
90 | // Numbers should be converted back to numeric types.
91 | // Don't use parseInt() for numeric checking, use parseFloat() instead.
92 | if (utils.isNumeric(val)) {
93 | val = parseFloat(val);
94 | }
95 |
96 | // Rest is good to go.
97 | return val;
98 | }
99 |
100 | // Node.js.
101 | module.exports = (server) => {
102 | // CORS middleware.
103 | server.pre(cors.preflight);
104 | server.use(cors.actual);
105 |
106 |
107 | /* Route. */
108 |
109 | server.get(ROUTE_RAW_DATA, (req, res, next) => {
110 | let data = req.params || {};
111 |
112 | debug('Request to %s from %s.', ROUTE_RAW_DATA, req.connection.remoteAddress);
113 |
114 |
115 | /* Verify request. */
116 |
117 | // Don't allow blacklisted fingerprints.
118 | const fingerprints = Object.keys(blacklist);
119 |
120 | for (var i = 0; i < fingerprints.length; i++) {
121 | const key = fingerprints[i];
122 | const fingerprint = blacklist[key];
123 |
124 | if (fingerprint(req)) {
125 | return next(
126 | new errors.ForbiddenError('Blacklisted fingerprint.')
127 | );
128 | }
129 | }
130 |
131 | // Make sure we have all required parameters for a correct request.
132 | const required = [
133 | 'swLat', 'swLng', 'neLat', 'neLng',
134 | /*'oSwLat', 'oSwLng', 'oNeLat', 'oNeLng'*/
135 | ];
136 |
137 | // Bad request.
138 | if (!queryHasRequiredParams(data, required)) {
139 | return next(
140 | new errors.ForbiddenError('Invalid parameters.')
141 | );
142 | }
143 |
144 |
145 | /* Parse GET params. */
146 |
147 | // Show/hide.
148 | const no_pokemon = parseGetParam(data.no_pokemon, false);
149 | const no_pokestops = parseGetParam(data.no_pokestops, false);
150 | const no_gyms = parseGetParam(data.no_gyms, false);
151 |
152 | const show_pokemon = parseGetParam(data.pokemon, true) && !no_pokemon;
153 | const show_pokestops = parseGetParam(data.pokestops, true) && !no_pokestops;
154 | const show_gyms = parseGetParam(data.gyms, true) && !no_gyms;
155 |
156 | // Previous switch settings.
157 | const last_gyms = parseGetParam(data.lastgyms, false);
158 | const last_pokestops = parseGetParam(data.lastpokestops, false);
159 | const last_pokemon = parseGetParam(data.lastpokemon, false);
160 | const last_slocs = parseGetParam(data.lastslocs, false);
161 | const last_spawns = parseGetParam(data.lastspawns, false);
162 |
163 | // Locations.
164 | const swLat = parseGetParam(data.swLat, undefined);
165 | const swLng = parseGetParam(data.swLng, undefined);
166 | const neLat = parseGetParam(data.neLat, undefined);
167 | const neLng = parseGetParam(data.neLng, undefined);
168 | const oSwLat = parseGetParam(data.oSwLat, undefined);
169 | const oSwLng = parseGetParam(data.oSwLng, undefined);
170 | const oNeLat = parseGetParam(data.oNeLat, undefined);
171 | const oNeLng = parseGetParam(data.oNeLng, undefined);
172 |
173 | // TODO: Check distance in locations. If it exceeds a certain distance,
174 | // refuse the query.
175 |
176 | // Other.
177 | const scanned = parseGetParam(data.scanned, false);
178 | const spawnpoints = parseGetParam(data.spawnpoints, false);
179 | var timestamp = parseGetParam(data.timestamp, undefined);
180 |
181 | // Convert to usable date object.
182 | if (!isEmpty(timestamp)) {
183 | debug('Request for timestamp %s.', timestamp);
184 | timestamp = new Date(timestamp);
185 | }
186 |
187 | // Query response is a combination of Pokémon + Pokéstops + Gyms, so
188 | // we have to wait until the necessary Promises have completed.
189 | var completed_pokemon = !show_pokemon;
190 | var completed_pokestops = !show_pokestops;
191 | var completed_gyms = !show_gyms;
192 |
193 | // General/optional.
194 | // TODO: Check if "lured_only" is proper var name.
195 | const lured_only = parseGetParam(data.luredonly, true);
196 |
197 | var new_area = false; // Did we zoom in/out?
198 |
199 | if (!isEmpty(oSwLat) && !isEmpty(oSwLng) && !isEmpty(oNeLat) && !isEmpty(oNeLng)) {
200 | // We zoomed in, no new area uncovered.
201 | if (oSwLng < swLng && oSwLat < swLat && oNeLat > neLat && oNeLng > neLng) {
202 | new_area = false;
203 | } else if (!(oSwLat === swLat && oSwLng === swLng && oNeLat === neLat && oNeLng === neLng)) {
204 | new_area = true; // We moved.
205 | }
206 | }
207 |
208 |
209 | /* Prepare response. */
210 | var response = {};
211 |
212 | // UTC timestamp.
213 | response.timestamp = Date.now();
214 |
215 | // Values for next request.
216 | response.lastgyms = show_gyms;
217 | response.lastpokestops = show_pokestops;
218 | response.lastpokemon = show_pokemon;
219 | response.lastslocs = scanned;
220 | response.lastspawns = spawnpoints;
221 |
222 | // Pass current coords as old coords.
223 | response.oSwLat = swLat;
224 | response.oSwLng = swLng;
225 | response.oNeLat = neLat;
226 | response.oNeLng = neLng;
227 |
228 | // Handle Pokémon.
229 | if (show_pokemon) {
230 | // Pokémon IDs, whitelist or blacklist.
231 | let ids = [];
232 | let excluded = [];
233 |
234 | if (!isEmpty(data.ids)) {
235 | ids = parseGetParam(data.ids.split(','), []);
236 | }
237 | if (!isEmpty(data.eids)) {
238 | excluded = parseGetParam(data.eids.split(','), []);
239 | }
240 | if (!isEmpty(data.reids)) {
241 | // TODO: Check this implementation of reids. In original, it's
242 | // separate to other query types.
243 | let reids = parseGetParam(data.reids.split(','), []);
244 | ids += reids;
245 | response.reids = reids;
246 | }
247 |
248 | // TODO: Change .then() below w/ custom "completed" flags into proper
249 | // Promise queue.
250 |
251 | // Completion handler.
252 | let foundMons = (pokes) => {
253 | response.pokemons = pokes;
254 | completed_pokemon = true;
255 |
256 | pokemon_debug('Found %s relevant Pokémon results.', pokes.length);
257 |
258 | return partialCompleted(completed_pokemon, completed_pokestops, completed_gyms, res, response);
259 | };
260 |
261 | // TODO: Rewrite below workflow. We reimplemented the old Python code,
262 | // but it's kinda ugly.
263 |
264 | // Whitelist query?
265 | if (ids.length > 0) {
266 | // Run query async.
267 | Pokemon.get_active_by_ids(ids, excluded, swLat, swLng, neLat, neLng).then(foundMons).catch(utils.handle_error);
268 | } else if (!last_pokemon) {
269 | // First query from client?
270 | Pokemon.get_active(null, swLat, swLng, neLat, neLng).then(foundMons).catch(utils.handle_error);
271 | } else {
272 | // If map is already populated only request modified Pokémon
273 | // since last request time.
274 | Pokemon.get_active(excluded, swLat, swLng, neLat, neLng, timestamp).then((pokes) => {
275 | // If screen is moved add newly uncovered Pokémon to the
276 | // ones that were modified since last request time.
277 | if (new_area) {
278 | debug('Request for new viewport.');
279 |
280 | Pokemon.get_active(excluded, swLat, swLng, neLat, neLng, null, oSwLat, oSwLng, oNeLat, oNeLng).then((new_pokes) => {
281 | // Add the new ones to the old result and pass to handler.
282 | return foundMons(pokes.concat(new_pokes));
283 | }).catch(utils.handle_error);
284 | } else {
285 | // Unchanged viewport.
286 | return foundMons(pokes);
287 | }
288 | }).catch(utils.handle_error);
289 | }
290 |
291 | // TODO: On first visit, send in-memory data for viewport.
292 | }
293 |
294 | // Handle Pokéstops.
295 | if (show_pokestops) {
296 | // Completion handler.
297 | let foundPokestops = function (stops) {
298 | response.pokestops = stops;
299 | completed_pokestops = true;
300 |
301 | return partialCompleted(completed_pokemon, completed_pokestops, completed_gyms, res, response);
302 | };
303 |
304 | // First query from client?
305 | if (!last_pokestops) {
306 | Pokestop.get_stops(swLat, swLng, neLat, neLng, lured_only).then(foundPokestops).catch(utils.handle_error);
307 | } else {
308 | // If map is already populated only request modified Pokéstops
309 | // since last request time.
310 | Pokestop.get_stops(swLat, swLng, neLat, neLng, lured_only, timestamp).then(function (pokestops) {
311 | // If screen is moved add newly uncovered Pokéstops to the
312 | // ones that were modified since last request time.
313 | if (new_area) {
314 | Pokestop.get_stops(swLat, swLng, neLat, neLng, lured_only, null, oSwLat, oSwLng, oNeLat, oNeLng).then(function (new_pokestops) {
315 | // Add the new ones to the old result and pass to handler.
316 | return foundPokestops(pokestops.concat(new_pokestops));
317 | }).catch(utils.handle_error);
318 | } else {
319 | // Unchanged viewport.
320 | return foundPokestops(pokestops);
321 | }
322 | }).catch(utils.handle_error);
323 | }
324 | }
325 |
326 | // Handle gyms.
327 | if (show_gyms) {
328 | // Completion handler.
329 | let foundGyms = (gyms) => {
330 | // RM uses an object w/ gym_id as keys. Can't remember why, this might be a case of "old code".
331 | // TODO: Look at RM's code for this and optimize.
332 | const gyms_obj = {};
333 |
334 | while (gyms.length > 0) {
335 | const gym = gyms.pop();
336 |
337 | // Attach to result object.
338 | gyms_obj[gym.gym_id] = gym;
339 | }
340 |
341 | response.gyms = gyms_obj;
342 | completed_gyms = true;
343 |
344 | return partialCompleted(completed_pokemon, completed_pokestops, completed_gyms, res, response);
345 | };
346 |
347 | // First query from client?
348 | if (!last_gyms) {
349 | Gym.get_gyms(swLat, swLng, neLat, neLng).then(foundGyms).catch(utils.handle_error);
350 | } else {
351 | // If map is already populated only request modified Gyms
352 | // since last request time.
353 | Gym.get_gyms(swLat, swLng, neLat, neLng, timestamp).then((gyms) => {
354 | // If screen is moved add newly uncovered Gyms to the
355 | // ones that were modified since last request time.
356 | if (new_area) {
357 | Gym.get_gyms(swLat, swLng, neLat, neLng, null, oSwLat, oSwLng, oNeLat, oNeLng).then((new_gyms) => {
358 | // Add the new ones to the old result and pass to handler.
359 | return foundGyms(gyms.concat(new_gyms));
360 | }).catch(utils.handle_error);
361 | } else {
362 | // Unchanged viewport.
363 | return foundGyms(gyms);
364 | }
365 | }).catch(utils.handle_error);
366 | }
367 | }
368 |
369 | // A request for nothing?
370 | if (!show_pokemon && !show_pokestops && !show_gyms) {
371 | return res.json(response);
372 | }
373 | });
374 | };
--------------------------------------------------------------------------------