├── LICENSE ├── README.md ├── app.js ├── config ├── development.json └── production.json ├── package-lock.json ├── package.json ├── run.sh ├── src ├── cluster │ ├── baseworker.js │ ├── master.js │ ├── runner.js │ └── worker.js ├── dal │ ├── auth │ │ └── mongo.js │ ├── doctors │ │ └── mongo.js │ ├── drugs │ │ └── mongo.js │ ├── entities.json │ ├── foundations │ │ └── mongo.js │ ├── groups │ │ └── mongo.js │ ├── hospitals │ │ └── mongo.js │ ├── index.js │ ├── mongodal.js │ ├── pharmacies │ │ └── mongo.js │ ├── services │ │ └── mongo.js │ ├── specializations │ │ └── mongo.js │ ├── subscriptions │ │ └── mongo.js │ ├── tickets │ │ └── mongo.js │ ├── tokens │ │ └── mongo.js │ └── uploads │ │ └── mongo.js ├── lib │ ├── doctors │ │ └── index.js │ ├── drugs │ │ └── index.js │ ├── faq │ │ └── index.js │ ├── foundations │ │ └── index.js │ ├── groups │ │ └── index.js │ ├── hospitals │ │ └── index.js │ ├── maintenance.js │ ├── notifications │ │ └── index.js │ ├── pharmacies │ │ └── index.js │ ├── services │ │ └── index.js │ ├── specializations │ │ └── index.js │ ├── subscriptions │ │ └── index.js │ ├── tickets │ │ └── index.js │ ├── tokens │ │ └── index.js │ ├── uploads │ │ └── index.js │ ├── users │ │ └── index.js │ └── utils.js ├── models │ ├── doctors.js │ ├── drug.js │ ├── foundations.js │ ├── group.js │ ├── hospitals.js │ ├── location.js │ ├── pharmacies.js │ ├── price.js │ ├── rates.js │ ├── service.js │ ├── specialization.js │ ├── tokens.js │ ├── uploads.js │ └── user.js ├── notifications │ ├── backends │ │ ├── base.js │ │ ├── email.js │ │ └── index.js │ ├── events.js │ ├── formatters.js │ ├── renderer.js │ └── templates │ │ ├── book │ │ ├── body.html │ │ └── subject.html │ │ ├── question │ │ ├── body.html │ │ └── subject.html │ │ ├── registration │ │ ├── body.html │ │ └── subject.html │ │ ├── subscription │ │ ├── body.html │ │ └── subject.html │ │ └── ticket │ │ ├── body.html │ │ └── subject.html ├── routes │ ├── auth.js │ ├── doctors.js │ ├── drugs.js │ ├── faq.js │ ├── foundations.js │ ├── group.js │ ├── hospitals.js │ ├── maintenance.js │ ├── notifications.js │ ├── paginator.js │ ├── pharmacies.js │ ├── responses.js │ ├── routes.js │ ├── services.js │ ├── specializations.js │ ├── subscriptions.js │ ├── tickets.js │ ├── tokens.js │ └── uploads.js ├── utils │ └── logger.js └── validators │ ├── auth.js │ ├── faq.js │ ├── subscriptions.js │ └── tickets.js └── tests └── example.test.js /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 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 General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Api 2 | 3 | It's a base data-module for the site. It is written with using of `Javascript`,`Node.js`,`express` and `MongoDB`. 4 | 5 | # Database 6 | 7 | We are using `MongoDB` as a primary database for the project 8 | 9 | # Api 10 | 11 | We are using `Node.js` and `express` as a base for the our api-platform. 12 | 13 | # How to start 14 | 15 | You need to install `npm install` first, then you need to run `node app.js` 16 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const {startServer} = require('./src/cluster/runner'); 3 | 4 | startServer(); 5 | -------------------------------------------------------------------------------- /config/development.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "maxProcess": 1, 4 | "port": 8080 5 | }, 6 | "logger": { 7 | "level": "error" 8 | }, 9 | "mongo": { 10 | "port": 27017, 11 | "host": "localhost", 12 | "dbName": "medpoint_test" 13 | }, 14 | "notifications": { 15 | "email": { 16 | "host": "smtp.yandex.ru", 17 | "port": 587, 18 | "auth": { 19 | "user": "medpoints.online@yandex.ru", 20 | "pass": "Fgdh34#hfi(dd)daSx%dS" 21 | } 22 | } 23 | }, 24 | "adminEmail": "medpoints.online@yandex.ru" 25 | } 26 | -------------------------------------------------------------------------------- /config/production.json: -------------------------------------------------------------------------------- 1 | { 2 | "server": { 3 | "maxProcess": 0, 4 | "port": 8080 5 | }, 6 | "logger": { 7 | "level": "debug" 8 | }, 9 | "mongo": { 10 | "port": 27017, 11 | "host": "localhost", 12 | "dbName": "medpoint" 13 | } 14 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "medpoints", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "node ./node_modules/jest/bin/jest.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/MedPoints/api.git" 12 | }, 13 | "keywords": [ 14 | "API" 15 | ], 16 | "author": "", 17 | "license": "ISC", 18 | "bugs": { 19 | "url": "https://github.com/MedPoints/api/issues" 20 | }, 21 | "homepage": "https://github.com/MedPoints/api#readme", 22 | "dependencies": { 23 | "bluebird": "^3.5.1", 24 | "body-parser": "^1.18.3", 25 | "bunyan": "1.8.12", 26 | "config": "1.30.0", 27 | "express": "4.16.3", 28 | "handlebars": "4.0.12", 29 | "joi": "13.6.0", 30 | "mongodb": "3.0.7", 31 | "nodemailer": "4.6.8", 32 | "uuid": "3.3.2" 33 | }, 34 | "devDependencies": { 35 | "jest": "22.4.3", 36 | "sinon": "5.0.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | node app.js | ./node_modules/.bin/bunyan --color 4 | exit ${PIPESTATUS[0]} 5 | -------------------------------------------------------------------------------- /src/cluster/baseworker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Promise = require('bluebird'); 4 | 5 | class BaseWorker { 6 | /** 7 | * @param {Object} options 8 | * @param {Logger} options.logger 9 | */ 10 | constructor(options={}){ 11 | this._options = options; 12 | this._logger = options.logger; 13 | this._onClose = []; 14 | this._errorHandling(); 15 | } 16 | init(config){ 17 | throw new Error('NOT_IMPLEMENTED_METHOD'); 18 | } 19 | _errorHandling(){ 20 | process.on('SIGTERM', async () => { 21 | this._logger.warn('SIGTERM signal was handled'); 22 | await Promise.all(this._onClose); 23 | process.exit(); 24 | }); 25 | 26 | process.on('uncaughtException', (ex) => { 27 | this._logger.error(ex.stack || ex, 'uncaughtException'); 28 | }); 29 | process.on('unhandledRejection', (rejection) => { 30 | this._logger.error(rejection, 'unhandledRejection'); 31 | }); 32 | process.on('message', async (msg) => { 33 | if (msg === 'shutdown') { 34 | await Promise.all(this._onClose); 35 | process.exit(); 36 | } 37 | }); 38 | } 39 | } 40 | 41 | exports.BaseWorker = BaseWorker; 42 | -------------------------------------------------------------------------------- /src/cluster/master.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const os = require('os'); 4 | const cluster = require('cluster'); 5 | 6 | const {BaseWorker} = require('./baseworker'); 7 | 8 | class Master extends BaseWorker { 9 | /** 10 | * @param {Object} options 11 | * @param {Logger} options.logger 12 | */ 13 | constructor(options={}){ 14 | super(options); 15 | this._workers = {}; 16 | } 17 | 18 | /** 19 | * @param {Object} config 20 | * @param {Number} [config.maxProcess] 21 | */ 22 | init(config){ 23 | this._logger.info('init main worker'); 24 | const workers = config.maxProcess || os.cpus().length; 25 | for(let i = 0; i < workers; i++){ 26 | this._createWorkers(); 27 | } 28 | process.on('SIGTERM', () => { 29 | Object.keys(this._workers).forEach((pid) => { 30 | this._logger.info({workerPid: pid}, 'close connection'); 31 | this._workers[pid].send('shutdown'); 32 | this._workers[pid].disconnect(); 33 | }); 34 | setImmediate(process.exit); 35 | }); 36 | } 37 | 38 | /** 39 | * @private 40 | */ 41 | _createWorkers(){ 42 | const worker = cluster.fork(); 43 | const pid = worker.process.pid; 44 | this._logger.info({workerPid: pid}, 'create new worker'); 45 | this._workers[pid] = worker; 46 | worker.once('exit', () => { 47 | this._logger.info({workerPid: pid}, 'worker died. Restart'); 48 | delete this._workers[pid]; 49 | this._createWorkers(); 50 | }); 51 | worker.on('message', ({cmd}) => { 52 | switch(cmd){ 53 | case 'EADDRINUSE': 54 | this._logger.fatal({workerPid: pid}, 'fatal error'); 55 | setImmediate(process.exit); 56 | break; 57 | default: 58 | break; 59 | } 60 | }); 61 | } 62 | } 63 | 64 | exports.Master = Master; 65 | -------------------------------------------------------------------------------- /src/cluster/runner.js: -------------------------------------------------------------------------------- 1 | const config = require('config'); 2 | const cluster = require('cluster'); 3 | 4 | const {Master} = require('./master'); 5 | const {Worker} = require('./worker'); 6 | 7 | const settings = config.get('server'); 8 | const logger = require('../utils/logger').getLogger('WORKER'); 9 | 10 | exports.startServer = () => { 11 | let worker; 12 | if(cluster.isMaster){ 13 | worker = new Master({logger: logger}); 14 | }else{ 15 | worker = new Worker({logger: logger}); 16 | } 17 | worker.init(settings); 18 | }; 19 | -------------------------------------------------------------------------------- /src/cluster/worker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const routes = require('../routes/routes'); 4 | const {BaseWorker} = require('./baseworker'); 5 | const DAL = require('../dal/index'); 6 | 7 | 8 | class Worker extends BaseWorker { 9 | /** 10 | * @param {Object} options 11 | * @param {Logger} options.logger 12 | */ 13 | constructor(options={}){ 14 | super(options); 15 | } 16 | 17 | /** 18 | * @param {Object} config 19 | * @param {Number|String} [config.port] 20 | */ 21 | init(config){ 22 | const {_logger: log} = this; 23 | DAL.initDAL(); 24 | const app = routes.initServer({log: this._logger}); 25 | const server = app.listen(config.port, '0.0.0.0', () => { 26 | log.info({port: config.port}, 'start server'); 27 | }); 28 | this._onClose.push(async () => { 29 | await server.close(); 30 | log.info('server connection closed'); 31 | }); 32 | } 33 | } 34 | 35 | exports.Worker = Worker; 36 | -------------------------------------------------------------------------------- /src/dal/auth/mongo.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const collectionName = "users"; 4 | 5 | exports.getUserById = async function (id) { 6 | const collection = this.mongo.collection(collectionName); 7 | const [user] = await collection.find({ _id: id }).limit(1).toArray(); 8 | return user; 9 | }; 10 | 11 | exports.deleteUserById = async function (id) { 12 | const collection = this.mongo.collection(collectionName); 13 | await collection.remove({ _id: id }); 14 | }; 15 | 16 | exports.register = async function (user) { 17 | const collection = this.mongo.collection(collectionName); 18 | await collection.insert(user); 19 | }; 20 | 21 | exports.getUserByConfirmId = async function (token) { 22 | const collection = this.mongo.collection(collectionName); 23 | const [user] = await collection 24 | .find({ confirmationToken: token }) 25 | .limit(1) 26 | .toArray(); 27 | return user; 28 | }; 29 | 30 | exports.confirmUser = async function (id) { 31 | const collection = this.mongo.collection(collectionName); 32 | await collection.update({ _id: id }, { $set: { confirmed: true } }); 33 | }; 34 | 35 | exports.updateUser = async function (id, profile) { 36 | const collection = this.mongo.collection(collectionName); 37 | await collection.update({ _id: id }, profile, { upsert: false }); 38 | }; 39 | 40 | exports.addFavorite = async function (id, favorite) { 41 | const collection = this.mongo.collection(collectionName); 42 | await collection.update( 43 | { 44 | _id: id, 45 | }, 46 | { 47 | $push: { 48 | favorites: favorite, 49 | }, 50 | } 51 | ); 52 | }; 53 | 54 | exports.removeFavorite = async function (id, favorite) { 55 | const collection = this.mongo.collection(collectionName); 56 | await collection.update( 57 | { 58 | _id: id, 59 | }, 60 | { 61 | $pull: { 62 | favorites: { 63 | id: { $eq: favorite.id }, 64 | }, 65 | }, 66 | } 67 | ); 68 | }; 69 | 70 | exports.getCount = async function (filter = {}) { 71 | const collection = this.mongo.collection(collectionName); 72 | return collection.count(filter || {}); 73 | }; 74 | -------------------------------------------------------------------------------- /src/dal/doctors/mongo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Promise = require('bluebird'); 4 | const {DoctorRate} = require('../../models/rates'); 5 | const {ObjectId} = require('mongodb'); 6 | const {DoctorCreate, DoctorResponse} = require("../../models/doctors"); 7 | 8 | const collectionName = 'doctors'; 9 | 10 | /** 11 | * @param {String} id -- doctor id 12 | * @returns {Promise} 13 | */ 14 | exports.getDoctorById = async function(id){ 15 | const [result] = await exports.getDoctors({_id: new ObjectId(id)}, null, true); 16 | if (!result) { 17 | return null; 18 | } 19 | return new DoctorResponse(result || {}); 20 | }; 21 | 22 | exports.getDoctors = async function(filter, paginator, raw=false){ 23 | const collection = this.mongo.collection(collectionName); 24 | let doctors = collection.aggregate([ 25 | {$match: filter}, 26 | { 27 | $lookup: { 28 | from: 'hospitals', 29 | let: {sid: {$toString: "$_id"}}, 30 | pipeline: [ 31 | {$unwind: '$doctors'}, 32 | {$match: {$expr: {$eq: ['$$sid', '$doctors']}}}, 33 | { 34 | $project: { 35 | id: '$id', 36 | name: 1, 37 | slug: 1, 38 | departments: 1, 39 | location: 1, 40 | coordinations: 1 41 | } 42 | }], 43 | as: 'hospitals', 44 | } 45 | }, 46 | { 47 | $addFields: { 48 | serviceIds: { 49 | $map: { 50 | input: '$services', 51 | as: 'id', 52 | in: {$toObjectId: '$$id'} 53 | } 54 | } 55 | } 56 | }, 57 | { 58 | $lookup: { 59 | from: 'services', 60 | localField: 'serviceIds', 61 | foreignField: '_id', 62 | as: 'services' 63 | } 64 | }, 65 | { 66 | $addFields: { 67 | hospital: {$arrayElemAt: ['$hospitals', 0]} 68 | } 69 | }, 70 | { 71 | $project: { 72 | _id: 0, 73 | id: '$_id', 74 | name: 1, 75 | slug: 1, 76 | prefix: 1, 77 | statement: 1, 78 | ratings: 1, 79 | specializations: 1, 80 | services: { 81 | $map: { 82 | input: '$services', 83 | as: 'service', 84 | in: { 85 | id: '$$service._id', 86 | name: '$$service.name' 87 | } 88 | } 89 | }, 90 | hospital: { 91 | id: '$hospital._id', 92 | name: '$hospital.name', 93 | departments: '$hospital.departments', 94 | location: '$hospital.location', 95 | }, 96 | coordinations: '$hospitals.coordinations', 97 | education: 1, 98 | } 99 | } 100 | ]); 101 | if(paginator){ 102 | const offset = paginator.getOffset(); 103 | doctors = doctors.skip(offset).limit(paginator.count); 104 | } 105 | doctors = await doctors.toArray(); 106 | if(raw){ 107 | return doctors; 108 | } 109 | return doctors.map(d => new DoctorResponse(d)); 110 | }; 111 | 112 | exports.getDoctorsBySpecialization = async function(specialization, paginator){ 113 | const collection = this.mongo.collection(collectionName); 114 | const offset = paginator.getOffset(); 115 | const doctors = await collection.find({specialization}).skip(offset).limit(paginator.count).toArray(); 116 | return doctors.map(d => new DoctorResponse(d)); 117 | }; 118 | 119 | exports.getDoctorsWithPages = async function(filter, paginator){ 120 | const [doctors, total] = await Promise.all([ 121 | exports.getDoctors(filter, paginator), 122 | exports.getCount(filter) 123 | ]); 124 | return { 125 | data: doctors, 126 | meta: { 127 | pages: Math.ceil(total / paginator.count), 128 | current: paginator.page, 129 | total, 130 | } 131 | } 132 | }; 133 | 134 | exports.getCount = async function(filter = {}){ 135 | const collection = this.mongo.collection(collectionName); 136 | return collection.count(filter || {}); 137 | }; 138 | 139 | 140 | /** 141 | * 142 | * @param doctor 143 | * @returns {Promise} 144 | */ 145 | exports.saveDoctor = async function(doctor){ 146 | const collection = this.mongo.collection(collectionName); 147 | const entity = new DoctorCreate(doctor); 148 | await collection.insert(entity); 149 | }; 150 | 151 | 152 | /** 153 | * 154 | * @param {String} id 155 | * @param {DoctorResponse} doctor 156 | */ 157 | exports.updateDoctor = async function(id, doctor){ 158 | const collection = this.mongo.collection(collectionName); 159 | delete doctor.id; 160 | await collection.update({_id: new ObjectId(id)}, {$set: doctor}); 161 | }; 162 | 163 | /** 164 | * @param {String} id 165 | */ 166 | exports.deleteDoctor = async function(id){ 167 | const collection = this.mongo.collection(collectionName); 168 | await collection.remove({_id: new ObjectId(id)}); 169 | }; 170 | 171 | 172 | /** 173 | * @param {String} id 174 | * @param {Object} rate 175 | * @returns {Promise} 176 | */ 177 | exports.changeRateOfDoctor = async function(id, rate){ 178 | const collection = this.mongo.collection(collectionName); 179 | const entity = new DoctorRate(rate); 180 | await collection.update({_id: new ObjectId(id)}, { 181 | $push: {ratings: entity} 182 | }); 183 | }; 184 | -------------------------------------------------------------------------------- /src/dal/drugs/mongo.js: -------------------------------------------------------------------------------- 1 | const collectionName = 'drugs'; 2 | 3 | const ObjectId = require('mongodb').ObjectId; 4 | 5 | const {DrugModelCreate, DrugModelResponse} = require('../../models/drug'); 6 | 7 | exports.getAllDrugs = async function(filter, paginator){ 8 | const collection = this.mongo.collection(collectionName); 9 | const offset = paginator.getOffset(); 10 | const result = await collection.find(filter).skip(offset).limit(paginator.count).toArray(); 11 | return result.map(c => new DrugModelResponse(c)); 12 | }; 13 | 14 | exports.getDrugById = async function(id){ 15 | return exports.getDrugByFilter({_id: ObjectId(id)}); 16 | }; 17 | 18 | exports.getDrugsByInterval = async function(filter){ 19 | const collection = this.mongo.collection(collectionName); 20 | const result = await collection.find(filter).toArray(); 21 | return result.map(el => el._id.toString()); 22 | }; 23 | 24 | exports.saveDrug = async function(drug){ 25 | const collection = this.mongo.collection(collectionName); 26 | const entity = new DrugModelCreate(drug); 27 | entity.timestamp = drug.timestamp; 28 | await collection.insert(entity); 29 | return entity; 30 | }; 31 | 32 | exports.updateDrug = async function(id, drug){ 33 | const collection = this.mongo.collection(collectionName); 34 | await collection.update({_id: ObjectId(id)}, drug); 35 | }; 36 | 37 | exports.deleteDrug = async function(id){ 38 | const collection = this.mongo.collection(collectionName); 39 | await collection.remove({_id: new ObjectId(id)}); 40 | }; 41 | 42 | exports.getDrugByFilter = async function(filter){ 43 | const collection = this.mongo.collection(collectionName); 44 | const [result] = await collection.find(filter).limit(1).toArray(); 45 | if (!result) { 46 | return null; 47 | } 48 | return new DrugModelResponse(result || {}); 49 | }; 50 | 51 | exports.getCount = async function(filter={}){ 52 | const collection = this.mongo.collection(collectionName); 53 | return collection.count(filter || {}); 54 | }; 55 | 56 | exports.getDrugsWithPages = async function(filter, paginator) { 57 | if(filter.ids){ 58 | filter._id = { 59 | $in : filter.ids.map(id => new ObjectId(id)) 60 | }; 61 | delete filter.ids; 62 | } 63 | 64 | const [doctors, total] = await Promise.all([ 65 | exports.getAllDrugs(filter, paginator), 66 | exports.getCount(filter) 67 | ]); 68 | return { 69 | data: doctors, 70 | meta: { 71 | pages: Math.ceil(total / paginator.count), 72 | current: paginator.page, 73 | total, 74 | } 75 | } 76 | }; 77 | 78 | -------------------------------------------------------------------------------- /src/dal/entities.json: -------------------------------------------------------------------------------- 1 | { 2 | "auth": { 3 | "mongo": true 4 | }, 5 | "hospitals": { 6 | "mongo": true 7 | }, 8 | "doctors": { 9 | "mongo": true 10 | }, 11 | "drugs": { 12 | "mongo": true 13 | }, 14 | "pharmacies": { 15 | "mongo": true 16 | }, 17 | "services": { 18 | "mongo": true 19 | }, 20 | "groups": { 21 | "mongo": true 22 | }, 23 | "specializations": { 24 | "mongo": true 25 | }, 26 | "tickets": { 27 | "mongo": true 28 | }, 29 | "subscriptions": { 30 | "mongo": true 31 | }, 32 | "uploads": { 33 | "mongo": true 34 | }, 35 | "foundations": { 36 | "mongo": true 37 | }, 38 | "tokens": { 39 | "mongo": true 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/dal/foundations/mongo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const collectionName = 'foundations'; 4 | 5 | exports.getFoundationsByPublicKey = async function(publicKey){ 6 | return await exports.getFoundations({publicKey: publicKey}); 7 | } 8 | 9 | exports.getFoundations = async function(filter){ 10 | const collection = this.mongo.collection(collectionName); 11 | let foundations = await collection.find(filter).toArray(); 12 | return foundations; 13 | } 14 | 15 | exports.addFoundation = async function(foundation){ 16 | const collection = this.mongo.collection(collectionName); 17 | await collection.insert(foundation); 18 | }; 19 | 20 | -------------------------------------------------------------------------------- /src/dal/groups/mongo.js: -------------------------------------------------------------------------------- 1 | const collectionName = 'drug_groups'; 2 | 3 | const {ObjectId} = require('mongodb'); 4 | 5 | const {GroupCreate, GroupResponse} = require('../../models/group'); 6 | 7 | exports.getAllCategories = async function(filter, paginator){ 8 | const collection = this.mongo.collection(collectionName); 9 | const offset = paginator.getOffset(); 10 | const result = await collection.find(filter).skip(offset).limit(paginator.count).toArray(); 11 | return result.map(c => new GroupResponse(c)); 12 | }; 13 | 14 | exports.getCategoryById = async function(id){ 15 | return exports.getCategoryByFilter({_id: new ObjectId(id)}); 16 | }; 17 | 18 | exports.getCategoryByName = async function(name){ 19 | return exports.getCategoryByFilter({name: name}); 20 | }; 21 | 22 | exports.getGroupsByInterval = async function(filter){ 23 | const collection = this.mongo.collection(collectionName); 24 | const result = await collection.find(filter).toArray(); 25 | return result.map(el => { 26 | return { 27 | id: el._id.toString(), 28 | drugs: el.drugs, 29 | }; 30 | }); 31 | }; 32 | 33 | exports.saveCategory = async function(category){ 34 | const collection = this.mongo.collection(collectionName); 35 | const entity = new GroupCreate(category); 36 | entity.timestamp = category.timestamp; 37 | await collection.insert(entity); 38 | return entity; 39 | }; 40 | 41 | exports.updateCategory = async function(id, entity){ 42 | const collection = this.mongo.collection(collectionName); 43 | await collection.update({_id: new ObjectId(id)}, entity); 44 | }; 45 | 46 | exports.getCategoryByFilter = async function(filter){ 47 | const collection = this.mongo.collection(collectionName); 48 | const [result] = await collection.find(filter).limit(1).toArray(); 49 | return new GroupResponse(result || {}); 50 | }; 51 | 52 | exports.deleteCategory = async function(id){ 53 | const collection = this.mongo.collection(collectionName); 54 | await collection.remove({_id: new ObjectId(id)}); 55 | }; 56 | 57 | exports.getCount = async function(filter={}){ 58 | const collection = this.mongo.collection(collectionName); 59 | return collection.count(filter || {}); 60 | }; 61 | 62 | exports.getGroupsWithPages = async function(filter, paginator) { 63 | const [doctors, total] = await Promise.all([ 64 | exports.getAllCategories(filter, paginator), 65 | exports.getCount(filter) 66 | ]); 67 | return { 68 | data: doctors, 69 | meta: { 70 | pages: Math.ceil(total / paginator.count), 71 | current: paginator.page, 72 | total, 73 | } 74 | } 75 | }; 76 | 77 | exports.deleteGroup = async function(id){ 78 | const collection = this.mongo.collection(collectionName); 79 | await collection.remove({_id: new ObjectId(id)}); 80 | }; -------------------------------------------------------------------------------- /src/dal/hospitals/mongo.js: -------------------------------------------------------------------------------- 1 | const collectionName = 'hospitals'; 2 | 3 | const ObjectId = require('mongodb').ObjectId; 4 | const {HospitalResponse, HospitalCreate} = require("../../models/hospitals"); 5 | 6 | /** 7 | * @typedef {Object} OpeningHours -- opening hours model 8 | * @property {String} dayOfWeek -- day of week 9 | * @property {Boolean} isClosed -- is closed today 10 | * @property {String} open -- time of opening 11 | * @property {String} closed -- time of closing 12 | */ 13 | 14 | /** 15 | * @typedef {Object} Coordinations -- coordination model 16 | * @property {Number} lat 17 | * @property {Number} lon 18 | */ 19 | 20 | /** 21 | * @typedef {Object} Hospital -- hospital model 22 | * @property {String} _id 23 | * @property {String} name -- hospital name 24 | * @property {String} chain -- chain of hospital 25 | * @property {Array} departments -- departments of hospitals 26 | * @property {String} specialiazion -- hospital specialization 27 | * @property {Coordinations} coordinations -- hospital coordinations 28 | * @property {String} address -- hospital address 29 | * @property {Array} photos -- links for photos 30 | * @property {Array} openingHours 31 | * @property {String} email 32 | * @property {String} phone 33 | * @property {String} website 34 | * @property {Array} doctors 35 | */ 36 | 37 | 38 | /** 39 | * @param {String} id 40 | * @returns {Promise} 41 | */ 42 | exports.getHospitalById = async function(id){ 43 | const result = await hospitalQuery.call(this, {_id: new ObjectId(id)}); 44 | return new HospitalResponse(result); 45 | }; 46 | 47 | /** 48 | * @param {String} name 49 | * @return {Promise} 50 | */ 51 | exports.getHospitalByName = async function(name){ 52 | const result = await hospitalQuery.call(this, {name: {$regex: name, $options: 'i'}}); 53 | if (!result) { 54 | return null; 55 | } 56 | return new HospitalResponse(result); 57 | }; 58 | 59 | exports.getHospitalsByCustomFilter = async function(filter){ 60 | const collection = this.mongo.collection(collectionName); 61 | const result = await collection.find(filter).toArray(); 62 | return result.map(r => new HospitalResponse(r)); 63 | }; 64 | 65 | exports.getAllHospitals = async function(filter, paginator) { 66 | const collection = this.mongo.collection(collectionName); 67 | const offset = paginator.getOffset(); 68 | const result = await collection.find(filter).skip(offset).limit(paginator.count).toArray(); 69 | return result.map(r => new HospitalResponse(r)); 70 | }; 71 | 72 | exports.getHospitalsWithPages = async function(filter, paginator) { 73 | const [hospitals, total] = await Promise.all([ 74 | exports.getAllHospitals(filter, paginator), 75 | exports.getCount(filter) 76 | ]); 77 | return { 78 | data: hospitals, 79 | meta: { 80 | pages: Math.ceil(total / paginator.count), 81 | current: paginator.page, 82 | total, 83 | } 84 | } 85 | }; 86 | 87 | exports.getHospitalsGroupedByLocation= async () => { 88 | const collection = this.mongo.collection(collectionName); 89 | return await collection.aggregate([ 90 | { 91 | $group: { 92 | _id: "$location.country", 93 | count: {$sum: 1}, 94 | hospitals: { 95 | "$push": { 96 | id: {$toString: "$_id"}, 97 | name: "$name", 98 | slug: 1, 99 | address: "$location.address", 100 | coordinations: "$coordinations" 101 | } 102 | } 103 | } 104 | }, 105 | {$project: {_id: 0, name: "$_id", count: 1, hospitals: 1}} 106 | ]).toArray(); 107 | }; 108 | 109 | exports.getCount = async function(filter={}){ 110 | const collection = this.mongo.collection(collectionName); 111 | return collection.count(filter || {}); 112 | }; 113 | 114 | /** 115 | * @param {Object} hospital 116 | */ 117 | exports.saveHospital = async function(hospital){ 118 | const collection = this.mongo.collection(collectionName); 119 | const entity = new HospitalCreate(hospital); 120 | await collection.insert(entity); 121 | }; 122 | 123 | /** 124 | * 125 | * @param {String} id 126 | * @param {Hospital} hospital 127 | */ 128 | exports.updateHospital = async function(id, hospital){ 129 | const collection = this.mongo.collection(collectionName); 130 | await collection.update({_id: ObjectId(id)}, hospital); 131 | }; 132 | 133 | 134 | /** 135 | * @param {String} id 136 | */ 137 | exports.deleteHospital = async function(id){ 138 | const collection = this.mongo.collection(collectionName); 139 | await collection.remove({_id: ObjectId(id)}); 140 | }; 141 | 142 | /** 143 | * @param {Object} filter 144 | * @returns {Promise} 145 | */ 146 | async function hospitalQuery(filter){ 147 | const collection = this.mongo.collection(collectionName); 148 | const hospitals = await collection.find(filter).limit(1).toArray(); 149 | return hospitals[0]; 150 | } 151 | -------------------------------------------------------------------------------- /src/dal/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const config = require('config'); 3 | const path = require('path'); 4 | 5 | const utils = require('../utils/logger'); 6 | 7 | const logger = utils.getLogger('DAL'); 8 | 9 | const ENTITIES = 'entities.json'; 10 | 11 | const dalTypes = { 12 | mongo: require('./mongodal').dalType 13 | }; 14 | 15 | const DALS = {}; 16 | 17 | exports.initDAL = function(){ 18 | const pathToFile = path.join(__dirname, ENTITIES); 19 | logger.debug('start loading dals'); 20 | let entities; 21 | try{ 22 | entities = JSON.parse(fs.readFileSync(pathToFile)); 23 | }catch(err){ 24 | logger.error('error while loading dal', err); 25 | throw err; 26 | } 27 | const keys = Object.keys(entities); 28 | for(const entity of keys){ 29 | logger.debug(`load entity ${entity}`); 30 | const scopes = Object.keys(entities[entity]); 31 | for(const scope of scopes){ 32 | DALS[entity] = wrapDALMethods(entity, scope); 33 | } 34 | } 35 | }; 36 | 37 | function wrapDALMethods(entity, type){ 38 | const module = require(`./${entity}/${type}`); 39 | if(!module){ 40 | throw new Error('MODULE_NOT_FOUND'); 41 | } 42 | const dalType = dalTypes[type]; 43 | if(!dalType){ 44 | throw new Error('UNSUPPORTED_INTERFACE'); 45 | } 46 | const cfg = config.get(type); 47 | const connectionInterface = new dalType(cfg || {}); 48 | module.open = async function(){ 49 | this[type] = await connectionInterface.createConnection(); 50 | return this; 51 | }; 52 | module.close = function(){ 53 | connectionInterface.close(); 54 | }; 55 | return module; 56 | } 57 | 58 | exports.open = async function(name){ 59 | const dal = DALS[name]; 60 | if(!dal){ 61 | throw new Error('NOT_EXISTS_DAL'); 62 | } 63 | return dal.open(); 64 | }; 65 | -------------------------------------------------------------------------------- /src/dal/mongodal.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {MongoClient} = require('mongodb'); 4 | 5 | 6 | class MongoDal { 7 | /** 8 | * @param {Object} config 9 | */ 10 | constructor(config){ 11 | this._config = {}; 12 | this._initConfig(config); 13 | this._client = null; 14 | this._connectionCount = 0; 15 | } 16 | 17 | /** 18 | * @returns {Promise} 19 | */ 20 | async createConnection(){ 21 | if(!this._client){ 22 | this._client = await MongoClient.connect(this._config.url, {}); 23 | } 24 | this._connectionCount++; 25 | return this._client.db(this._config.dbName); 26 | } 27 | 28 | close(){ 29 | this._connectionCount--; 30 | if(this._connectionCount === 0){ 31 | this._client.close(); 32 | this._client = null; 33 | } else if (this._connectionCount < 0){ 34 | throw new Error('CONNECTION_ALREADY_CLOSED'); 35 | } 36 | } 37 | 38 | /** 39 | * @param {Object} config 40 | * @param {String} config.host 41 | * @param {String} config.port 42 | * @param {String} config.dbName 43 | * @private 44 | */ 45 | _initConfig(config){ 46 | this._config.url = `mongodb://${config.host}:${config.port}`; 47 | this._config.dbName = config.dbName; 48 | } 49 | } 50 | 51 | exports.dalType = MongoDal; 52 | -------------------------------------------------------------------------------- /src/dal/pharmacies/mongo.js: -------------------------------------------------------------------------------- 1 | const collectionName = 'pharmacies'; 2 | 3 | const ObjectId = require('mongodb').ObjectId; 4 | 5 | const {PharmaciesCreate, PharmaciesResponse} = require('../../models/pharmacies'); 6 | const {PharmacyRate} = require('../../models/rates'); 7 | 8 | exports.getPharmacyById = async function(id){ 9 | return pharmaciesQuery.call(this, {_id: ObjectId(id)}); 10 | }; 11 | 12 | 13 | exports.getPharmacyByName = async function(name){ 14 | return pharmaciesQuery.call(this, {name}); 15 | }; 16 | 17 | exports.getAllPharmaciesWithoutPages = async function(filter){ 18 | const collection = this.mongo.collection(collectionName); 19 | const result = await collection.find(filter).toArray(); 20 | return result.map(r => new PharmaciesResponse(r)); 21 | }; 22 | 23 | exports.getAllPharmacies = async function(filter, paginator){ 24 | const collection = this.mongo.collection(collectionName); 25 | const offset = paginator.getOffset(); 26 | const result = await collection.find(filter).skip(offset).limit(paginator.count).toArray(); 27 | return result.map(r => new PharmaciesResponse(r)); 28 | }; 29 | 30 | exports.getPharmaciesWithPages = async function(filter, paginator) { 31 | const [pharmacies, total] = await Promise.all([ 32 | exports.getAllPharmacies(filter, paginator), 33 | exports.getCount(filter) 34 | ]); 35 | return { 36 | data: pharmacies, 37 | meta: { 38 | pages: Math.ceil(total / paginator.count), 39 | current: paginator.page, 40 | total, 41 | } 42 | } 43 | }; 44 | 45 | exports.savePharmacy = async function(pharmacy){ 46 | const collection = this.mongo.collection(collectionName); 47 | const entity = new PharmaciesCreate(pharmacy); 48 | await collection.insert(entity); 49 | }; 50 | 51 | exports.getCount = async function(filter={}){ 52 | const collection = this.mongo.collection(collectionName); 53 | return collection.count(filter || {}); 54 | }; 55 | 56 | exports.updatePharmacy = async function(id, pharmacy){ 57 | const collection = this.mongo.collection(collectionName); 58 | await collection.update({_id: ObjectId(id)}, pharmacy); 59 | }; 60 | 61 | exports.deletePharmacy = async function(id){ 62 | const collection = this.mongo.collection(collectionName); 63 | await collection.remove({_id: new ObjectId(id)}); 64 | }; 65 | 66 | exports.changeRateOfPharmacy = async function(id, rate){ 67 | const collection = this.mongo.collection(collectionName); 68 | const entity = new PharmacyRate(rate); 69 | await collection.update({_id: new ObjectId(id)}, { 70 | $push: {ratings: entity} 71 | }); 72 | }; 73 | 74 | async function pharmaciesQuery(filter){ 75 | const collection = this.mongo.collection(collectionName); 76 | const [pharmacy] = await collection.find(filter).limit(1).toArray(); 77 | return new PharmaciesResponse(pharmacy || {}); 78 | } 79 | 80 | -------------------------------------------------------------------------------- /src/dal/services/mongo.js: -------------------------------------------------------------------------------- 1 | const collectionName = 'services'; 2 | 3 | const ObjectId = require('mongodb').ObjectId; 4 | 5 | const {ServiceCreate, ServiceResponse} = require('../../models/service'); 6 | 7 | exports.getServiceById = async function(id, raw=false){ 8 | return ServicesQuery.call(this, {_id: new ObjectId(id)}, raw); 9 | }; 10 | 11 | 12 | exports.getServiceByName = async function(name){ 13 | return ServicesQuery.call(this, {name: {$regex: name, $options: 'i'}}); 14 | }; 15 | 16 | exports.getAllServices = async function(filter, paginator){ 17 | const collection = this.mongo.collection(collectionName); 18 | const offset = paginator.getOffset(); 19 | const result = await collection.find(filter).skip(offset).limit(paginator.count).toArray(); 20 | return result.map(r => new ServiceResponse(r)); 21 | }; 22 | 23 | exports.getServicesWithPages = async function(filter, paginator) { 24 | const [services, total] = await Promise.all([ 25 | exports.getAllServices(filter, paginator), 26 | exports.getCount(filter) 27 | ]); 28 | return { 29 | data: services, 30 | meta: { 31 | pages: Math.ceil(total / paginator.count), 32 | current: paginator.page, 33 | total, 34 | } 35 | } 36 | }; 37 | 38 | exports.saveService = async function(service){ 39 | const collection = this.mongo.collection(collectionName); 40 | const entity = new ServiceCreate(service); 41 | await collection.insert(entity); 42 | }; 43 | 44 | exports.getCount = async function(filter={}){ 45 | const collection = this.mongo.collection(collectionName); 46 | return collection.count(filter || {}); 47 | }; 48 | 49 | exports.updateService = async function(id, hospital){ 50 | const collection = this.mongo.collection(collectionName); 51 | await collection.update({_id: ObjectId(id)}, hospital); 52 | }; 53 | 54 | exports.deleteService = async function(id){ 55 | const collection = this.mongo.collection(collectionName); 56 | await collection.remove({_id: ObjectId(id)}); 57 | }; 58 | 59 | async function ServicesQuery(filter, raw=false){ 60 | const collection = this.mongo.collection(collectionName); 61 | const [service] = await collection.find(filter).limit(1).toArray(); 62 | if (!service) { 63 | return null; 64 | } 65 | if(raw){ 66 | return service; 67 | } 68 | return new ServiceResponse(service || {}); 69 | } 70 | 71 | 72 | -------------------------------------------------------------------------------- /src/dal/specializations/mongo.js: -------------------------------------------------------------------------------- 1 | const {ObjectId} = require('mongodb'); 2 | 3 | const collectionName = 'specializations'; 4 | 5 | const {SpecializationCreateModel, SpecializationResponseModel} = require('../../models/specialization'); 6 | 7 | exports.getSpecializations = async function(filter, paginator){ 8 | const collection = this.mongo.collection(collectionName); 9 | const offset = paginator.getOffset(); 10 | const specializations = await collection.find(filter).skip(offset).limit(paginator.count).toArray(); 11 | return specializations.map(specialization => new SpecializationResponseModel(specialization)); 12 | }; 13 | 14 | exports.getSpecializationById = async function(id){ 15 | const collection = this.mongo.collection(collectionName); 16 | const [specialization] = await collection.find({_id: ObjectId(id)}).limit(1).toArray(); 17 | return new SpecializationResponseModel(specialization || {}); 18 | }; 19 | 20 | 21 | exports.getSpecializationsWithPages = async function(filter, paginator) { 22 | const [doctors, total] = await Promise.all([ 23 | exports.getSpecializations(filter, paginator), 24 | exports.getCount(filter) 25 | ]); 26 | return { 27 | data: doctors, 28 | meta: { 29 | pages: Math.ceil(total / paginator.count), 30 | current: paginator.page, 31 | total, 32 | } 33 | } 34 | }; 35 | 36 | 37 | exports.saveSpecialization = async function(entity) { 38 | const collection = this.mongo.collection(collectionName); 39 | const doc = new SpecializationCreateModel(entity); 40 | await collection.insert(doc); 41 | }; 42 | 43 | exports.getCount = async function(filter){ 44 | const collection = this.mongo.collection(collectionName); 45 | return collection.count(filter); 46 | }; 47 | -------------------------------------------------------------------------------- /src/dal/subscriptions/mongo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const collectionName = 'subscriptions'; 4 | 5 | exports.getSubscription = async function(email) { 6 | const collection = await this.mongo.collection(collectionName); 7 | const [subscription] = await collection.find({_id: email}).limit(1).toArray(); 8 | return subscription; 9 | }; 10 | 11 | exports.addSubscription = async function(email){ 12 | const collection = await this.mongo.collection(collectionName); 13 | await collection.insert({_id: email}); 14 | }; 15 | -------------------------------------------------------------------------------- /src/dal/tickets/mongo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const collectionName = 'tickets'; 4 | 5 | exports.createTicket = async function(ticket){ 6 | const collection = await this.mongo.collection(collectionName); 7 | await collection.insert(ticket) 8 | }; 9 | 10 | exports.getTicketsByUser = async function(userId){ 11 | const collection = await this.mongo.collection(collectionName); 12 | return collection.find({userId}).toArray(); 13 | }; 14 | -------------------------------------------------------------------------------- /src/dal/tokens/mongo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const collectionName = 'tokens'; 4 | 5 | exports.getTokensByPublicKey = async function(publicKey){ 6 | return await exports.getTokens({publicKey: publicKey}); 7 | } 8 | 9 | exports.countTokensByPublicKey = async function(publicKey){ 10 | const tokens = await exports.getTokens({publicKey: publicKey}); 11 | let tokensCount = 0; 12 | 13 | for (const token of tokens) { 14 | tokensCount += token.balanceUSD; 15 | } 16 | 17 | return { 18 | count: tokensCount, 19 | }; 20 | } 21 | 22 | exports.getTokens = async function(filter){ 23 | const collection = this.mongo.collection(collectionName); 24 | let tokens = await collection.find(filter).toArray(); 25 | return tokens; 26 | } 27 | 28 | exports.addToken = async function(token){ 29 | const collection = this.mongo.collection(collectionName); 30 | await collection.insert(token); 31 | }; 32 | 33 | -------------------------------------------------------------------------------- /src/dal/uploads/mongo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const collectionName = 'uploads'; 4 | 5 | exports.getUploadsByPublicKey = async function(publicKey){ 6 | return await exports.getUploads({publicKey: publicKey}); 7 | } 8 | 9 | exports.getUploads = async function(filter){ 10 | const collection = this.mongo.collection(collectionName); 11 | let uploads = await collection.find(filter).toArray(); 12 | return uploads; 13 | } 14 | 15 | exports.addUpload = async function(upload){ 16 | const collection = this.mongo.collection(collectionName); 17 | await collection.insert(upload); 18 | }; 19 | 20 | -------------------------------------------------------------------------------- /src/lib/doctors/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Promise = require('bluebird'); 4 | const ObjectId = require('mongodb').ObjectId; 5 | const dal = require('../../dal/index'); 6 | const {ResponseWithMeta} = require('../../routes/responses'); 7 | 8 | const log = require('../../utils/logger').getLogger('DOCTORS'); 9 | 10 | /** 11 | * @param {String} name 12 | * @param {String} specializations 13 | * @param {Paginator} paginator 14 | * @returns {Promise|Object>} 15 | */ 16 | exports.getDoctors = async function({name, specialization, service, hospital, filter}, paginator){ 17 | const doctorDAL = await dal.open('doctors'); 18 | const hospitalDAL = await dal.open('hospitals'); 19 | const servicesDAL = await dal.open('services'); 20 | try{ 21 | const query = {}; 22 | if(hospital){ 23 | const hosp = await hospitalDAL.getHospitalById(hospital); 24 | if(!hosp.id){ 25 | return new ResponseWithMeta({ 26 | data: [], 27 | meta: { 28 | pages: 0, 29 | current: paginator.page, 30 | } 31 | }); 32 | } 33 | query._id = {$in: hosp.doctors.map(id => new ObjectId(id))}; 34 | } 35 | else if(name){ 36 | query.name = {$regex: name, $options: 'i'}; 37 | }else if(specialization){ 38 | query.specialization = specialization; 39 | }else if(service){ 40 | query.services = service; 41 | }else if(filter && filter.city && filter.city !== 'worldwide'){ 42 | const hospFilter = {}; 43 | hospFilter['location.city'] = filter.city; 44 | const hospitals = await hospitalDAL.getHospitalsByCustomFilter(hospFilter) || []; 45 | if(hospitals.length === 0){ 46 | return new ResponseWithMeta({ 47 | data: [], 48 | meta: { 49 | pages: 0, 50 | current: paginator.page, 51 | } 52 | }); 53 | } 54 | query._id = { 55 | $in: hospitals.reduce((total, hospital) => { 56 | hospital.doctors.forEach((doc) => total.push(new ObjectId(doc))); 57 | return total; 58 | }, []) 59 | }; 60 | } 61 | const doctors = await doctorDAL.getDoctorsWithPages(query, paginator) || {}; 62 | return new ResponseWithMeta(doctors) 63 | }catch(err){ 64 | log.error({id, name}, 'getDoctor error', err); 65 | throw err; 66 | }finally{ 67 | doctorDAL.close(); 68 | hospitalDAL.close(); 69 | servicesDAL.close(); 70 | } 71 | }; 72 | 73 | exports.getDoctorById = async function(id) { 74 | const doctorDAL = await dal.open('doctors'); 75 | try{ 76 | return await doctorDAL.getDoctorById(id); 77 | }catch(err){ 78 | log.error({id, name}, 'getDoctorById error', err); 79 | throw err; 80 | }finally{ 81 | doctorDAL.close(); 82 | } 83 | }; 84 | 85 | exports.getServicesByDoctorId = async function(id){ 86 | const doctorDAL = await dal.open('doctors'); 87 | const servicesDAL = await dal.open('services'); 88 | try{ 89 | const doctor = await doctorDAL.getDoctorById(id); 90 | if(!doctor){ 91 | return []; 92 | } 93 | const services = doctor.services || []; 94 | return await Promise.map(services, async (service) => servicesDAL.getServiceById(service.id)); 95 | }catch(err){ 96 | log.error({id}, 'getServicesByDoctorId error', err); 97 | throw err; 98 | }finally{ 99 | doctorDAL.close(); 100 | servicesDAL.close(); 101 | } 102 | }; 103 | 104 | exports.getHospitalsByDoctor = async function({id, service}){ 105 | const hospitalDAL = await dal.open('hospitals'); 106 | const serviceDAL = await dal.open('services'); 107 | try{ 108 | const filter = {doctors: id}; 109 | const [hospitals, srv] = await Promise.all([ 110 | hospitalDAL.getHospitalsByCustomFilter(filter), 111 | serviceDAL.getServiceById(service, true), 112 | ]); 113 | const providers = srv && srv.providers && srv.providers.hospitals || []; 114 | return hospitals.filter(({id}) => providers.find((p) => p.id === id.toString()) !== undefined); 115 | }finally{ 116 | hospitalDAL.close(); 117 | serviceDAL.close(); 118 | } 119 | }; 120 | 121 | /** 122 | * @param {Object} doctor 123 | * @returns {Promise} 124 | */ 125 | exports.saveDoctor = async (doctor) => { 126 | const doctorsDal = await dal.open('doctors'); 127 | try{ 128 | await doctorsDal.saveDoctor(doctor); 129 | }catch(err){ 130 | log.error('saveDoctor error', err); 131 | throw err; 132 | }finally{ 133 | doctorsDal.close() 134 | } 135 | }; 136 | 137 | exports.updateDoctor = async (doctor) => { 138 | const doctorsDal = await dal.open('doctors'); 139 | try{ 140 | await doctorsDal.updateDoctor(doctor.id, doctor); 141 | }catch(err){ 142 | log.error('updateDoctor error', err); 143 | throw err; 144 | }finally{ 145 | doctorsDal.close(); 146 | } 147 | }; 148 | 149 | exports.deleteDoctor = async (id) => { 150 | const doctorsDal = await dal.open('doctors'); 151 | try{ 152 | await doctorsDal.deleteDoctor(id); 153 | }catch(err){ 154 | log.error('deleteDoctor error', err); 155 | throw err; 156 | }finally{ 157 | doctorsDal.close(); 158 | } 159 | }; 160 | 161 | exports.getCount = async (filter = {}) => { 162 | const doctorsDal = await dal.open('doctors'); 163 | try{ 164 | return doctorsDal.getCount(filter); 165 | }catch(err){ 166 | log.error('getCount error', err); 167 | throw err; 168 | }finally{ 169 | doctorsDal.close(); 170 | } 171 | }; 172 | 173 | exports.changeRateOfDoctor = async (id, rate) => { 174 | const doctorsDal = await dal.open('doctors'); 175 | try{ 176 | await doctorsDal.changeRateOfDoctor(id, rate); 177 | }catch(err){ 178 | log.error('changeRateOfDoctor error', err); 179 | throw err; 180 | }finally{ 181 | doctorsDal.close(); 182 | } 183 | }; 184 | -------------------------------------------------------------------------------- /src/lib/drugs/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Promise = require('bluebird'); 4 | const dal = require('../../dal/index'); 5 | const ResponseWithMeta = require("../../routes/responses").ResponseWithMeta; 6 | 7 | const log = require('../../utils/logger').getLogger('DRUGS'); 8 | 9 | exports.getDrugs = async ({name, id, groupId, pharmacyId, filter}, paginator) => { 10 | const drugsDal = await dal.open('drugs'); 11 | const pharmacyDal = await dal.open("pharmacies"); 12 | try{ 13 | if(id){ 14 | let drug = await drugsDal.getDrugById(id); 15 | drug.providers.pharmacies = await pharmacyDal.getCount({drugs: {$in: [drug.id.toString()]}}); 16 | return drug; 17 | } 18 | const query = {}; 19 | if (groupId) { 20 | query['group.id'] = groupId; 21 | }else if(name) { 22 | query.name = {$regex: `^${name}`, $options: 'i'}; 23 | } 24 | 25 | if(pharmacyId){ 26 | const pharmacy = await pharmacyDal.getPharmacyById(pharmacyId); 27 | query.ids = pharmacy.drugs.map(x => x); 28 | } 29 | 30 | if(filter){ 31 | if(filter.insurance){ 32 | query['is_covered_by_insurance'] = filter.insurance === 'on'; 33 | } 34 | if(filter.prescription){ 35 | query['is_by_prescription'] = filter.prescription === 'on'; 36 | } 37 | if(filter.maxPrice){ 38 | const maxPrice = Number(filter.maxPrice); 39 | query['price.mpts'] = {$lte: maxPrice}; 40 | } 41 | if(filter.city && filter.city !== 'worldwide'){ 42 | const pharmQuery = {}; 43 | pharmQuery['location.city'] = filter.city; 44 | const pharmacies = await pharmacyDal.getAllPharmaciesWithoutPages(pharmQuery); 45 | query.ids = pharmacies.reduce((total, {drugs}) => { 46 | total.push(...drugs.map(drug => new ObjectId(drug))); 47 | return total; 48 | }, []); 49 | } 50 | } 51 | 52 | const result = await drugsDal.getDrugsWithPages(query, paginator); 53 | await Promise.each(result.data, async (drug) => { 54 | drug.providers.pharmacies = await pharmacyDal.getCount({drugs: {$in: [drug.id.toString()]}}); 55 | }); 56 | 57 | return new ResponseWithMeta(result); 58 | }catch(err){ 59 | log.error('getDrugs error', err); 60 | throw err; 61 | }finally{ 62 | drugsDal.close(); 63 | pharmacyDal.close(); 64 | } 65 | }; 66 | 67 | exports.getDrugsByInterval = async function(interval){ 68 | const drugsDal = await dal.open('drugs'); 69 | try{ 70 | return await drugsDal.getDrugsByInterval({"timestamp": {$lte: Date.now() - 60000*interval}}); 71 | }catch(err){ 72 | log.error({}, 'getDrugsByInterval error', err); 73 | throw err; 74 | }finally{ 75 | drugsDal.close(); 76 | } 77 | }; 78 | 79 | exports.saveDrug = async function(entity){ 80 | const drugsDal = await dal.open('drugs'); 81 | const groupsDal = await dal.open('groups'); 82 | try{ 83 | const drugResult = await drugsDal.saveDrug(entity); 84 | await groupsDal.updateCategory(entity.group.id, {$push: {drugs: drugResult._id.toString()}}); 85 | return drugResult; 86 | }catch(err){ 87 | log.error({}, 'saveDrug error', err); 88 | throw err; 89 | }finally{ 90 | drugsDal.close(); 91 | groupsDal.close(); 92 | } 93 | }; 94 | 95 | exports.getCount = async function(filter = {}) { 96 | const drugsDal = await dal.open('drugs'); 97 | try{ 98 | return drugsDal.getCount(filter); 99 | }catch(err){ 100 | log.error({}, 'getCount error', err); 101 | throw err; 102 | }finally{ 103 | drugsDal.close(); 104 | } 105 | }; 106 | 107 | exports.updateDrug = async function(entity){ 108 | const drugsDal = await dal.open('drugs'); 109 | const groupsDal = await dal.open('groups'); 110 | try{ 111 | const drug = await drugsDal.getDrugById(entity.id); 112 | if (drug.group.id !== entity.group.id) { 113 | await groupsDal.updateCategory(drug.group.id, {$pull: {drugs: entity.id}}); 114 | await groupsDal.updateCategory(entity.group.id, {$push: {drugs: entity.id}}); 115 | } 116 | const id = entity.id; 117 | delete entity.id; 118 | await drugsDal.updateDrug(id, {$set: entity}); 119 | }catch(err){ 120 | log.error({}, 'updateDrug error', err); 121 | throw err; 122 | }finally{ 123 | drugsDal.close(); 124 | groupsDal.close(); 125 | } 126 | }; 127 | 128 | exports.deleteDrug = async function(id){ 129 | const drugsDal = await dal.open('drugs'); 130 | const groupsDal = await dal.open('groups'); 131 | try{ 132 | const drug = await drugsDal.getDrugById(id); 133 | await groupsDal.updateCategory(drug.group.id, {$pull: {drugs: id}}); 134 | await drugsDal.deleteDrug(id); 135 | }catch(err){ 136 | log.error('drugsDal error', err); 137 | throw err; 138 | }finally{ 139 | drugsDal.close(); 140 | groupsDal.close(); 141 | } 142 | }; -------------------------------------------------------------------------------- /src/lib/faq/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const config = require('config'); 4 | 5 | const notifications = require('../../notifications/events'); 6 | 7 | exports.createQuestion = async (question) => { 8 | const email = config.get('adminEmail'); 9 | notifications.raise('new_question', email, question); 10 | return {status: 'ok'}; 11 | }; 12 | -------------------------------------------------------------------------------- /src/lib/foundations/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const dal = require('../../dal/index'); 4 | 5 | exports.getFoundationsByPublicKey = async (publicKey) => { 6 | const foundationsDAL = await dal.open('foundations'); 7 | try{ 8 | return await foundationsDAL.getFoundationsByPublicKey(publicKey); 9 | }catch(err){ 10 | log.error({publicKey}, 'getFoundationsByPublicKey error', err); 11 | throw err; 12 | }finally{ 13 | foundationsDAL.close(); 14 | } 15 | }; 16 | 17 | exports.addFoundation = async (foundation) => { 18 | const foundationsDAL = await dal.open('foundations'); 19 | try{ 20 | await foundationsDAL.addFoundation(foundation); 21 | }catch(err){ 22 | log.error('addFoundation error', err); 23 | throw err; 24 | }finally{ 25 | foundationsDAL.close() 26 | } 27 | }; -------------------------------------------------------------------------------- /src/lib/groups/index.js: -------------------------------------------------------------------------------- 1 | const dal = require('../../dal/index'); 2 | const ResponseWithMeta = require("../../routes/responses").ResponseWithMeta; 3 | 4 | const log = require('../../utils/logger').getLogger('DRUGS_GROUPS'); 5 | 6 | 7 | exports.getAllGroups = async function({name}, paginator){ 8 | const groupsDal = await dal.open('groups'); 9 | try{ 10 | const filter = {}; 11 | if(name) { 12 | filter.name = name; 13 | } 14 | const result = await groupsDal.getGroupsWithPages(filter, paginator); 15 | return new ResponseWithMeta(result); 16 | }catch(err){ 17 | log.error({id, name}, 'getAllGroups error', err); 18 | throw err; 19 | }finally{ 20 | groupsDal.close(); 21 | } 22 | }; 23 | 24 | exports.getGroup = async function({id}){ 25 | const groupsDal = await dal.open('groups'); 26 | try{ 27 | return groupsDal.getCategoryById(id); 28 | }catch(err){ 29 | log.error({id, name}, 'getGroup error', err); 30 | throw err; 31 | }finally{ 32 | groupsDal.close(); 33 | } 34 | }; 35 | 36 | exports.getGroupsByInterval = async function(interval){ 37 | const groupsDal = await dal.open('groups'); 38 | try{ 39 | return await groupsDal.getGroupsByInterval({"timestamp": {$lte: Date.now() - 60000*interval}}); 40 | }catch(err){ 41 | log.error({}, 'getGroupsByInterval error', err); 42 | throw err; 43 | }finally{ 44 | groupsDal.close(); 45 | } 46 | }; 47 | 48 | exports.saveGroup = async function(entity){ 49 | const groupsDal = await dal.open('groups'); 50 | try{ 51 | return groupsDal.saveCategory(entity); 52 | }catch(err){ 53 | log.error({id, name}, 'saveGroup error', err); 54 | throw err; 55 | }finally{ 56 | groupsDal.close(); 57 | } 58 | }; 59 | 60 | exports.updateGroup = async function(entity){ 61 | const groupsDal = await dal.open('groups'); 62 | try{ 63 | await groupsDal.updateCategory(entity.id, {$set: {name: entity.name}}); 64 | }catch(err){ 65 | log.error({id, name}, 'updateGroup error', err); 66 | throw err; 67 | }finally{ 68 | groupsDal.close(); 69 | } 70 | }; 71 | 72 | exports.deleteGroup = async function(id){ 73 | const groupsDal = await dal.open('groups'); 74 | try{ 75 | await groupsDal.deleteGroup(id); 76 | }catch(err){ 77 | log.error('groupsDal error', err); 78 | throw err; 79 | }finally{ 80 | groupsDal.close(); 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /src/lib/hospitals/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ObjectId = require('mongodb').ObjectId; 4 | const Promise = require('bluebird'); 5 | const dal = require('../../dal/index'); 6 | const ResponseWithMeta = require("../../routes/responses").ResponseWithMeta; 7 | const LocationsResponse = require("../../routes/responses").LocationsResponse; 8 | 9 | const log = require('../../utils/logger').getLogger('HOSPITALS'); 10 | 11 | const DOCTORS_DAL_NAME = 'doctors'; 12 | const HOSPITALS_DAL_NAME = 'hospitals'; 13 | 14 | /** 15 | * 16 | * @param {String} id 17 | * @param {String} name 18 | * @param {String} country 19 | * @param {String} specializationId 20 | * @param {String} service 21 | * @param {Object} filter 22 | * @returns {Promise} 23 | */ 24 | exports.getHospital = async ({id, name, country, specializationId, service, filter}, paginator) => { 25 | const [ 26 | hospitalsDal, 27 | doctorsDal, 28 | serviceDal 29 | ] = await Promise.all([dal.open(HOSPITALS_DAL_NAME), dal.open(DOCTORS_DAL_NAME), dal.open('services')]); 30 | const getCountOfServicesAndDoctors = async (hospital) => { 31 | const services = new Set(); 32 | await Promise.each(hospital.doctors, async (id) => { 33 | const doctor = await doctorsDal.getDoctorById(id); 34 | for(const service of doctor.services){ 35 | services.add(service); 36 | } 37 | }); 38 | 39 | hospital.services = services.size; 40 | hospital.doctors = hospital.doctors.length; 41 | return hospital; 42 | }; 43 | try{ 44 | const query = {}; 45 | if(id){ 46 | const hospital = await hospitalsDal.getHospitalById(id); 47 | return await getCountOfServicesAndDoctors(hospital); 48 | } 49 | if(name){ 50 | query.name = {$regex: name, $options: 'i'}; 51 | } 52 | if(country){ 53 | query['location.country'] = country; 54 | } 55 | if(specializationId){ 56 | query['specializations.id'] = { $eq: specializationId}; 57 | } 58 | if(filter){ 59 | if(filter.city && filter.city !== 'worldwide'){ 60 | query['location.city'] = filter.city; 61 | } 62 | } 63 | if(service){ 64 | const hospitals = new Set(); 65 | const doctors = await doctorsDal.getDoctors({services: service}); 66 | doctors.forEach(({hospital}) => hospitals.add(hospital.id)); 67 | query._id = {$in: Array.from(hospitals).map(id => new ObjectId(id))}; 68 | } 69 | const result = await hospitalsDal.getHospitalsWithPages(query, paginator) || {}; 70 | result.data = await Promise.map(result.data, getCountOfServicesAndDoctors); 71 | return new ResponseWithMeta(result); 72 | }catch(err){ 73 | log.error({id, name, err}, 'getHospital error'); 74 | throw err; 75 | }finally{ 76 | doctorsDal.close(); 77 | hospitalsDal.close(); 78 | serviceDal.close(); 79 | } 80 | }; 81 | 82 | /** 83 | * 84 | * @returns {Promise} 85 | */ 86 | exports.getHospitalsLocations = async () => { 87 | const hospitalsDal = await dal.open(HOSPITALS_DAL_NAME); 88 | try{ 89 | const [worldsCount, groupedHospitals] = await Promise.all([ 90 | hospitalsDal.getCount(), 91 | hospitalsDal.getHospitalsGroupedByLocation() 92 | ]); 93 | return new LocationsResponse({ worldsCount : worldsCount, locations : groupedHospitals}); 94 | }catch(err){ 95 | log.error('getHospitalLocations error', err); 96 | throw err 97 | }finally{ 98 | hospitalsDal.close(); 99 | } 100 | }; 101 | 102 | 103 | /** 104 | * @param {Object} hospital 105 | * @returns {Promise} 106 | */ 107 | exports.saveHospital = async (hospital) => { 108 | const hospitalsDal = await dal.open(HOSPITALS_DAL_NAME); 109 | try{ 110 | await hospitalsDal.saveHospital(hospital); 111 | }catch(err){ 112 | log.error('saveHospital error', err); 113 | throw err; 114 | }finally{ 115 | hospitalsDal.close() 116 | } 117 | }; 118 | 119 | /** 120 | * @param {Object} hospital 121 | * @returns {Promise} 122 | */ 123 | exports.updateHospital = async (hospital) => { 124 | const hospitalsDal = await dal.open(HOSPITALS_DAL_NAME); 125 | try{ 126 | const model = buildHospitalModel(hospital); 127 | await hospitalsDal.updateHospital(model._id, model); 128 | }catch(err){ 129 | log.error('updateHospital error', err); 130 | throw err; 131 | }finally{ 132 | hospitalsDal.close() 133 | } 134 | }; 135 | 136 | exports.getCount = async function(filter = {}) { 137 | const hospitalsDal = await dal.open(HOSPITALS_DAL_NAME); 138 | try{ 139 | return hospitalsDal.getCount(filter); 140 | }catch(err){ 141 | log.error({}, 'getCount error', err); 142 | throw err; 143 | }finally{ 144 | hospitalsDal.close(); 145 | } 146 | }; 147 | 148 | /** 149 | * @param {String} id 150 | * @returns {Promise} 151 | */ 152 | exports.deleteHospital = async (id) => { 153 | const hospitalsDal = await dal.open(HOSPITALS_DAL_NAME); 154 | try{ 155 | await hospitalsDal.deleteHospital(id); 156 | }catch(err){ 157 | log.err('deleteHospital error', err); 158 | throw err; 159 | }finally{ 160 | hospitalsDal.close(); 161 | } 162 | }; 163 | 164 | /** 165 | * @param {Object} hospital 166 | * @returns {Hospital} 167 | */ 168 | function buildHospitalModel(hospital){ 169 | const model = {}; 170 | for(const key in hospital){ 171 | switch(key){ 172 | case '_id': 173 | case 'id': 174 | model._id = hospital[key]; 175 | break; 176 | case 'name': 177 | case 'specialization': 178 | case 'chain': 179 | case 'type': 180 | case 'email': 181 | case 'phone': 182 | case 'website': 183 | case 'departments': 184 | model[key] = hospital[key]; 185 | break; 186 | case 'coordinations': 187 | if(typeof hospital[key] === 'object'){ 188 | const coord = hospital[key]; 189 | if(!coord.lat || !coord.lon){ 190 | log.warning({coord}, 'wrong schema of coordination'); 191 | } 192 | model[key] = coord; 193 | }else{ 194 | log.warning({coord}, 'wrong schema of coordination'); 195 | } 196 | break; 197 | case 'photos': 198 | case 'doctors': 199 | if(!Array.isArray(hospital[key])){ 200 | log.warning({key}, 'wrong schema'); 201 | }else{ 202 | model[key] = hospital[key]; 203 | } 204 | break; 205 | break; 206 | default: 207 | break; 208 | } 209 | } 210 | return model; 211 | } 212 | -------------------------------------------------------------------------------- /src/lib/maintenance.js: -------------------------------------------------------------------------------- 1 | const dal = require('../dal/index'); 2 | const Promise = require('bluebird'); 3 | 4 | /** 5 | * @param {Array} data 6 | * @returns {Promise} 7 | */ 8 | exports.importDrugs = async function(data){ 9 | const drugsDal = await dal.open('drugs'); 10 | const groupDal = await dal.open('groups'); 11 | try{ 12 | await Promise.each(data, async (group) => { 13 | const category = await groupDal.saveCategory({name: group.name}); 14 | category.drugs = await Promise.map(group.drugs, async (drug) => { 15 | drug.group = category; 16 | const newDrug = await drugsDal.saveDrug(drug); 17 | return newDrug._id.toString(); 18 | }); 19 | await groupDal.updateCategory(category._id, category); 20 | }); 21 | }catch(e){ 22 | console.log(e); 23 | }finally{ 24 | drugsDal.close(); 25 | groupDal.close(); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /src/lib/notifications/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Promise = require('bluebird'); 4 | 5 | const dal = require('../../dal/index'); 6 | const notification = require('../../notifications/events'); 7 | 8 | exports.renderBook = async ({email, data}) => { 9 | const [ 10 | doctorDAL, 11 | hospitalDAL, 12 | serviceDAL 13 | ] = await Promise.all([ 14 | dal.open('doctors'), 15 | dal.open('hospitals'), 16 | dal.open('services'), 17 | ]); 18 | const {doctor, hospital, service, date} = data; 19 | try{ 20 | const [ 21 | doctorData, 22 | hospitalData, 23 | serviceData, 24 | ] = await Promise.all([ 25 | doctorDAL.getDoctorById(doctor), 26 | hospitalDAL.getHospitalById(hospital), 27 | serviceDAL.getServiceById(service) 28 | ]); 29 | notification.raise('book', email, { 30 | doctor: {name: doctorData.name, id: doctor}, 31 | hospital: {name: hospitalData.name, id: hospital}, 32 | service: {name: serviceData.name, id: service}, 33 | date, 34 | }); 35 | } finally{ 36 | doctorDAL.close(); 37 | hospitalDAL.close(); 38 | serviceDAL.close(); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /src/lib/pharmacies/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const dal = require('../../dal/index'); 4 | const {ResponseWithMeta} = require('../../routes/responses'); 5 | 6 | const log = require('../../utils/logger').getLogger('PHARMACIES'); 7 | 8 | 9 | exports.getPharmacies = async function({id, name, drugId, filter}, paginator){ 10 | const pharmaciesDAL = await dal.open('pharmacies'); 11 | try{ 12 | let query = {}; 13 | if(id){ 14 | return await pharmaciesDAL.getPharmacyById(id); 15 | }else if(name){ 16 | query.name = {$regex: name, $options: 'i'}; 17 | } 18 | if(drugId){ 19 | query.drugs = drugId; 20 | } 21 | if(filter){ 22 | if(filter.city && filter.city !== 'worldwide'){ 23 | query['location.city'] = filter.city; 24 | } 25 | } 26 | const result = await pharmaciesDAL.getPharmaciesWithPages(query, paginator) || {}; 27 | return new ResponseWithMeta(result) 28 | }catch(err){ 29 | log.error({id, name}, 'getPharmacies error', err); 30 | throw err; 31 | }finally{ 32 | pharmaciesDAL.close(); 33 | } 34 | }; 35 | 36 | exports.savePharmacy = async function(pharmacy){ 37 | const pharmaciesDAL = await dal.open('pharmacies'); 38 | try{ 39 | await pharmaciesDAL.savePharmacy(pharmacy) 40 | }catch(err){ 41 | log.error({id, name}, 'savePharmacy error', err); 42 | throw err; 43 | }finally{ 44 | pharmaciesDAL.close(); 45 | } 46 | }; 47 | 48 | exports.updatePharmacy = async function(pharmacy){ 49 | const pharmaciesDAL = await dal.open('pharmacies'); 50 | try{ 51 | await pharmaciesDAL.updatePharmacy(pharmacy.id, pharmacy); 52 | } 53 | catch(err){ 54 | log.error({id, name}, 'updatePharmacy error', err); 55 | throw err; 56 | }finally{ 57 | pharmaciesDAL.close(); 58 | } 59 | }; 60 | 61 | exports.getCount = async function(filter = {}) { 62 | const pharmaciesDal = await dal.open('pharmacies'); 63 | try{ 64 | return pharmaciesDal.getCount(filter); 65 | }catch(err){ 66 | log.error({}, 'getCount error', err); 67 | throw err; 68 | }finally{ 69 | pharmaciesDal.close(); 70 | } 71 | }; 72 | 73 | exports.deletePharmacy = async function(id){ 74 | const pharmaciesDAL = await dal.open('pharmacies'); 75 | try{ 76 | await pharmaciesDAL.deletePharmacy(id); 77 | }catch(err){ 78 | log.error({id, name}, 'deletePharmacy error', err); 79 | throw err; 80 | }finally{ 81 | pharmaciesDAL.close(); 82 | } 83 | }; 84 | 85 | exports.changeRateOfPharmacy = async function(id, rate){ 86 | const pharmacyDal = await dal.open('pharmacies'); 87 | try{ 88 | await pharmacyDal.changeRateOfPharmacy(id, rate); 89 | }catch(err){ 90 | log.error('changeRateOfPharmacy error', err); 91 | throw err; 92 | }finally{ 93 | pharmacyDal.close(); 94 | } 95 | }; 96 | -------------------------------------------------------------------------------- /src/lib/services/index.js: -------------------------------------------------------------------------------- 1 | const Promise = require('bluebird'); 2 | const ObjectId = require('mongodb').ObjectId; 3 | 4 | const dal = require('../../dal/index'); 5 | const {ResponseWithMeta} = require('../../routes/responses'); 6 | 7 | const log = require('../../utils/logger').getLogger('SERVICES'); 8 | 9 | 10 | exports.getServices = async function({id, name, hospital, doctor, filter}, paginator){ 11 | const servicesDAL = await dal.open('services'); 12 | const doctorsDAL = await dal.open('doctors'); 13 | const hospitalDAL = await dal.open('hospitals'); 14 | try{ 15 | const query = {}; 16 | if(id){ 17 | return servicesDAL.getServiceById(id); 18 | }else if(name){ 19 | query.name = {$regex: name, $options: 'i'}; 20 | } 21 | if(hospital){ 22 | const h = await hospitalDAL.getHospitalById(hospital); 23 | const doctors = await doctorsDAL.getDoctors({_id: {$in: h.doctors.map(doctor => new ObjectId(doctor))}}, null); 24 | const services = new Set(); 25 | for(const doctor of doctors){ 26 | for(const service of doctor.services){ 27 | services.add(service.id); 28 | } 29 | } 30 | query._id = {$in: Array.from(services).map(id => new ObjectId(id))}; 31 | }else if(filter){ 32 | if(filter.city && filter.city !== 'worldwide'){ 33 | const hospFilter = {}; 34 | hospFilter['location.city'] = filter.city; 35 | const hospitals = await hospitalDAL.getHospitalsByCustomFilter(hospFilter) || []; 36 | if(hospitals.length === 0){ 37 | return new ResponseWithMeta({ 38 | data: [], 39 | meta: { 40 | pages: 0, 41 | current: paginator.page, 42 | } 43 | }); 44 | } 45 | const doctorIds = hospitals.reduce((total, {doctors}) => { 46 | total.push(...doctors.map(doctor => new ObjectId(doctor))); 47 | return total; 48 | }, []); 49 | const doctors = await doctorsDAL.getDoctors({_id: {$in: doctorIds}}, null); 50 | const services = new Set(); 51 | for(const doctor of doctors){ 52 | for(const service of doctor.services){ 53 | services.add(service.id); 54 | } 55 | } 56 | query._id = {$in: Array.from(services).map(id => new ObjectId(id))}; 57 | } 58 | if(filter.insurance){ 59 | query['is_covered_by_insurance'] = filter.insurance === 'on'; 60 | } 61 | if(filter.maxPrice){ 62 | const maxPrice = Number(filter.maxPrice); 63 | query['price.mpts'] = {$lte: maxPrice}; 64 | } 65 | } 66 | const result = await servicesDAL.getServicesWithPages(query, paginator) || {}; 67 | if (!(result && result.data && result.data.length > 0)) { 68 | return new ResponseWithMeta(result); 69 | } 70 | await Promise.each(result.data, async (service) => { 71 | const doctors = await doctorsDAL.getDoctors({services: service.id.toString()}); 72 | const hospitals = new Set(); 73 | doctors.forEach(({hospital}) => { 74 | hospitals.add(hospital.id); 75 | }); 76 | service.providers.doctors = doctors; 77 | service.providers.hospitals = await Promise.map(Array.from(hospitals), async (id) => hospitalDAL.getHospitalById(id)); 78 | }); 79 | return new ResponseWithMeta(result) 80 | }catch(err){ 81 | log.error({id, name}, 'getServices error', err); 82 | throw err; 83 | }finally{ 84 | doctorsDAL.close(); 85 | hospitalDAL.close(); 86 | servicesDAL.close(); 87 | } 88 | }; 89 | 90 | exports.saveService = async function(service){ 91 | const servicesDAL = await dal.open('services'); 92 | try{ 93 | await servicesDAL.saveService(service) 94 | }catch(err){ 95 | log.error({id, name}, 'saveService error', err); 96 | throw err; 97 | }finally{ 98 | servicesDAL.close(); 99 | } 100 | }; 101 | 102 | exports.updateService = async function(service){ 103 | const servicesDAL = await dal.open('services'); 104 | try{ 105 | await servicesDAL.updateService(service.id, service); 106 | } 107 | catch(err){ 108 | log.error({id, name}, 'updateService error', err); 109 | throw err; 110 | }finally{ 111 | servicesDAL.close(); 112 | } 113 | }; 114 | 115 | exports.getCount = async function(filter = {}) { 116 | const servicesDAL = await dal.open('services'); 117 | try{ 118 | return servicesDAL.getCount(filter); 119 | }catch(err){ 120 | log.error({}, 'getCount error', err); 121 | throw err; 122 | }finally{ 123 | servicesDAL.close(); 124 | } 125 | }; 126 | 127 | exports.deleteService = async function(id){ 128 | const servicesDAL = await dal.open('Services'); 129 | try{ 130 | await servicesDAL.deleteService(id); 131 | }catch(err){ 132 | log.error({id, name}, 'deleteService error', err); 133 | throw err; 134 | }finally{ 135 | servicesDAL.close(); 136 | } 137 | }; 138 | -------------------------------------------------------------------------------- /src/lib/specializations/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Promise = require('bluebird'); 4 | const dal = require('../../dal/index'); 5 | const {ResponseWithMeta} = require('../../routes/responses'); 6 | 7 | exports.getSpecializations = async function(paginator){ 8 | const [specDAL, doctorsDAL] = await Promise.all([dal.open('specializations'), dal.open('doctors')]); 9 | try{ 10 | const result = await specDAL.getSpecializationsWithPages({}, paginator); 11 | result.data = await Promise.map(result.data, async (specialization) => { 12 | specialization.count = await doctorsDAL.getCount({specialization: specialization.name}); 13 | return specialization; 14 | }); 15 | return new ResponseWithMeta(result); 16 | } 17 | finally{ 18 | specDAL.close(); 19 | doctorsDAL.close(); 20 | } 21 | }; 22 | 23 | exports.saveSpecialization = async function(specialization) { 24 | const specDAL = await dal.open('specializations'); 25 | try{ 26 | await specDAL.saveSpecialization(specialization); 27 | }finally{ 28 | specDAL.close(); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /src/lib/subscriptions/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const config = require('config'); 4 | const dal = require('../../dal/index'); 5 | const notifications = require('../../notifications/events'); 6 | 7 | exports.addSubscription = async (email) => { 8 | const subDAL = await dal.open('subscriptions'); 9 | try{ 10 | const subscription = await subDAL.getSubscription(email); 11 | if (subscription) { 12 | return 'ALREADY_EXISTS'; 13 | } 14 | await subDAL.addSubscription(email); 15 | notifications.raise('subscription', config.get('adminEmail'), {email}); 16 | return 'OK' 17 | } finally{ 18 | subDAL.close(); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /src/lib/tickets/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Promise = require('bluebird'); 4 | const config = require('config'); 5 | const uuid = require('uuid/v4'); 6 | 7 | const utils = require('../utils'); 8 | 9 | const dal = require('../../dal/index'); 10 | const notifications = require('../../notifications/events'); 11 | 12 | /** 13 | * @param {Object} ticket 14 | * @returns {Promise} 15 | */ 16 | exports.createTicket = async (ticket) => { 17 | const [ticketDal, authDal] = await Promise.all([ 18 | dal.open('tickets'), 19 | dal.open('auth') 20 | ]); 21 | try{ 22 | const id = uuid(); 23 | const userId = utils.createUserId(ticket.publicKey, ticket.privateKey); 24 | const user = await authDal.getUserById(userId); 25 | if (!user) { 26 | throw new Error('USER_NOT_FOUND'); 27 | } 28 | const {email: userEmail} = user; 29 | const email = config.get('adminEmail'); 30 | await ticketDal.createTicket(Object.assign({}, ticket, { 31 | _id: id, 32 | dateCreated: new Date(), 33 | status: 'open', 34 | userId, 35 | })); 36 | notifications.raise('ticket', email, { 37 | userEmail, 38 | title: ticket, 39 | subject: ticket.subject, 40 | text: ticket.text, 41 | }); 42 | return {status: 'OK'}; 43 | }finally{ 44 | authDal.close(); 45 | ticketDal.close(); 46 | } 47 | }; 48 | 49 | /** 50 | * @param {String} publicKey 51 | * @param {String} privateKey 52 | */ 53 | exports.getTicketsByUser = async ({publicKey, privateKey}) => { 54 | const ticketDal = await dal.open('tickets'); 55 | try{ 56 | const userId = utils.createUserId(publicKey, privateKey); 57 | return await ticketDal.getTicketsByUser(userId); 58 | }finally{ 59 | ticketDal.close(); 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /src/lib/tokens/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const dal = require('../../dal/index'); 4 | 5 | exports.countTokensByPublicKey = async (publicKey) => { 6 | const tokensDAL = await dal.open('tokens'); 7 | try{ 8 | return await tokensDAL.countTokensByPublicKey(publicKey); 9 | }catch(err){ 10 | log.error({publicKey}, 'countTokensByPublicKey error', err); 11 | throw err; 12 | }finally{ 13 | tokensDAL.close(); 14 | } 15 | }; 16 | 17 | exports.getTokensByPublicKey = async (publicKey) => { 18 | const tokensDAL = await dal.open('tokens'); 19 | try{ 20 | return await tokensDAL.getTokensByPublicKey(publicKey); 21 | }catch(err){ 22 | log.error({publicKey}, 'getTokensByPublicKey error', err); 23 | throw err; 24 | }finally{ 25 | tokensDAL.close(); 26 | } 27 | }; 28 | 29 | exports.addToken = async (token) => { 30 | const tokensDAL = await dal.open('tokens'); 31 | try{ 32 | await tokensDAL.addToken(token); 33 | }catch(err){ 34 | log.error('addToken error', err); 35 | throw err; 36 | }finally{ 37 | tokensDAL.close() 38 | } 39 | }; -------------------------------------------------------------------------------- /src/lib/uploads/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const dal = require('../../dal/index'); 4 | 5 | exports.getUploadsByPublicKey = async (publicKey) => { 6 | const uploadsDAL = await dal.open('uploads'); 7 | try{ 8 | return await uploadsDAL.getUploadsByPublicKey(publicKey); 9 | }catch(err){ 10 | log.error({publicKey}, 'getUploadsByPublicKey error', err); 11 | throw err; 12 | }finally{ 13 | uploadsDAL.close(); 14 | } 15 | }; 16 | 17 | exports.addUpload = async (upload) => { 18 | const uploadsDAL = await dal.open('uploads'); 19 | try{ 20 | await uploadsDAL.addUpload(upload); 21 | }catch(err){ 22 | log.error('addUpload error', err); 23 | throw err; 24 | }finally{ 25 | uploadsDAL.close() 26 | } 27 | }; -------------------------------------------------------------------------------- /src/lib/users/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const Promise = require("bluebird"); 4 | const uuid = require("uuid/v4"); 5 | const utils = require("../utils"); 6 | 7 | const doctorsModule = require("../doctors/index"); 8 | const hospitalsModule = require("../hospitals/index"); 9 | const pharmacyModule = require("../pharmacies/index"); 10 | 11 | const User = require("../../models/user"); 12 | 13 | const dal = require("../../dal/index"); 14 | const notifications = require("../../notifications/events"); 15 | 16 | exports.register = async ({ 17 | publicKey, 18 | privateKey, 19 | firstName, 20 | lastName, 21 | email, 22 | }) => { 23 | const authDAL = await dal.open("auth"); 24 | try { 25 | const confirmationToken = uuid(); 26 | const base64ConfirmationToken = Buffer.from(confirmationToken).toString( 27 | "base64" 28 | ); 29 | const id = utils.createUserId(publicKey, privateKey); 30 | const existedUser = await authDAL.getUserById(id); 31 | if (existedUser) { 32 | if (existedUser.confirmed) { 33 | throw new Error("USER_ALREADY_EXISTS"); 34 | } 35 | await authDAL.deleteUserById(id); 36 | } 37 | const user = { 38 | _id: id, 39 | publicKey, 40 | privateKey, 41 | confirmationToken, 42 | confirmed: false, 43 | firstName, 44 | lastName, 45 | email, 46 | }; 47 | await authDAL.register(user); 48 | notifications.raise("registration", email, { 49 | token: base64ConfirmationToken, 50 | firstName, 51 | lastName, 52 | }); 53 | return new User(user); 54 | } finally { 55 | authDAL.close(); 56 | } 57 | }; 58 | 59 | exports.confirm = async ({ token }) => { 60 | const authDAL = await dal.open("auth"); 61 | try { 62 | const decodedToken = Buffer.from(token, "base64").toString(); 63 | const user = await authDAL.getUserByConfirmId(decodedToken); 64 | if (!user) { 65 | throw new Error("CONFIRMATION_FAILED"); 66 | } 67 | if (user.confirmed) { 68 | throw new Error("ALREADY_CONFIRMED"); 69 | } 70 | await authDAL.confirmUser(user._id); 71 | return "OK"; 72 | } finally { 73 | authDAL.close(); 74 | } 75 | }; 76 | 77 | exports.auth = async ({ publicKey, privateKey }) => { 78 | const authDAL = await dal.open("auth"); 79 | try { 80 | const id = utils.createUserId(publicKey, privateKey); 81 | const user = await authDAL.getUserById(id); 82 | if (!user) { 83 | throw new Error("USER_NOT_EXIST"); 84 | } 85 | if (!user.confirmed) { 86 | throw new Error("USER_NOT_CONFIRMED"); 87 | } 88 | return new User(user); 89 | } finally { 90 | authDAL.close(); 91 | } 92 | }; 93 | 94 | exports.updateProfile = async (profile) => { 95 | const authDAL = await dal.open("auth"); 96 | try { 97 | const id = utils.createUserId(profile.publicKey, profile.privateKey); 98 | const user = await authDAL.getUserById(id); 99 | if (!user) { 100 | throw new Error("USER_NOT_EXIST"); 101 | } 102 | if (!user.confirmed) { 103 | throw new Error("USER_NOT_CONFIRMED"); 104 | } 105 | await authDAL.updateUser(id, Object.assign({}, user, profile)); 106 | return "OK"; 107 | } finally { 108 | authDAL.close(); 109 | } 110 | }; 111 | 112 | exports.getUserInfo = async ({ publicKey, privateKey }) => { 113 | const authDAL = await dal.open("auth"); 114 | try { 115 | const id = utils.createUserId(publicKey, privateKey); 116 | const user = await authDAL.getUserById(id); 117 | return new User(user); 118 | } finally { 119 | authDAL.close(); 120 | } 121 | }; 122 | 123 | exports.addToFavorites = async ({ publicKey, privateKey }, favorite) => { 124 | const authDAL = await dal.open("auth"); 125 | try { 126 | const userId = utils.createUserId(publicKey, privateKey); 127 | const user = await authDAL.getUserById(userId); 128 | if (!user) { 129 | throw new Error("USER_NOT_EXIST"); 130 | } 131 | if (user.favorites && user.favorites.find(({ id }) => id === favorite.id)) { 132 | return "ALREADY_ADDED"; 133 | } 134 | await authDAL.addFavorite(userId, favorite); 135 | return "OK"; 136 | } finally { 137 | authDAL.close(); 138 | } 139 | }; 140 | 141 | exports.removeFromFavorites = async ({ publicKey, privateKey }, favorite) => { 142 | const authDAL = await dal.open("auth"); 143 | try { 144 | const userId = utils.createUserId(publicKey, privateKey); 145 | const user = await authDAL.getUserById(userId); 146 | if (!user) { 147 | throw new Error("USER_NOT_EXIST"); 148 | } 149 | if (!user.favorites.find(({ id }) => id === favorite.id)) { 150 | throw new Error("FAVORITE_NOT_EXIST"); 151 | } 152 | await authDAL.removeFavorite(userId, favorite); 153 | return "OK"; 154 | } finally { 155 | authDAL.close(); 156 | } 157 | }; 158 | 159 | exports.getFavorites = async ({ publicKey, privateKey }) => { 160 | const authDAL = await dal.open("auth"); 161 | const userId = utils.createUserId(publicKey, privateKey); 162 | try { 163 | const result = { 164 | clinics: [], 165 | pharmacies: [], 166 | doctors: [], 167 | }; 168 | 169 | const user = await authDAL.getUserById(userId); 170 | if (!user) { 171 | throw new Error("USER_NOT_EXIST"); 172 | } 173 | if (!user.favorites || user.favorites.length === 0) { 174 | return result; 175 | } 176 | const clinicsFavs = user.favorites.filter(({ type }) => type === "clinic"); 177 | const doctorFavs = user.favorites.filter(({ type }) => type === "doctor"); 178 | const pharmacyFavs = user.favorites.filter( 179 | ({ type }) => type === "pharmacy" 180 | ); 181 | 182 | await Promise.all([ 183 | Promise.each(clinicsFavs, async ({ id }) => { 184 | const clinic = await hospitalsModule.getHospital({ id }); 185 | result.clinics.push(clinic); 186 | }), 187 | Promise.each(doctorFavs, async ({ id }) => { 188 | const doctor = await doctorsModule.getDoctorById(id); 189 | result.doctors.push(doctor); 190 | }), 191 | Promise.each(pharmacyFavs, async ({ id }) => { 192 | const pharmacy = await pharmacyModule.getPharmacies({ id }); 193 | result.pharmacies.push(pharmacy); 194 | }), 195 | ]); 196 | 197 | return result; 198 | } finally { 199 | authDAL.close(); 200 | } 201 | }; 202 | 203 | exports.getCount = async (filter = {}) => { 204 | const authDAL = await dal.open("auth"); 205 | try { 206 | return authDAL.getCount(filter); 207 | } catch (err) { 208 | log.error("getCount error", err); 209 | throw err; 210 | } finally { 211 | authDAL.close(); 212 | } 213 | }; 214 | -------------------------------------------------------------------------------- /src/lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const crypto = require('crypto'); 4 | 5 | exports.createUserId = (publicKey, privateKey) => crypto.createHash('sha256').update(`${publicKey}:${privateKey}`).digest('hex'); 6 | -------------------------------------------------------------------------------- /src/models/doctors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ObjectId = require("mongodb").ObjectId; 4 | 5 | 6 | class BaseDoctorModel { 7 | constructor({name, slug, prefix, specializations, services, statement, education, ratings}) { 8 | this.name = name; 9 | this.slug = slug; 10 | this.prefix = prefix; 11 | this.specializations = specializations || []; 12 | this.services = services || []; 13 | this.statement = statement || ''; 14 | this.ratings = ratings || []; 15 | this.education = (education || []).map(ed => new Education(ed)); 16 | } 17 | } 18 | 19 | class DoctorCreate extends BaseDoctorModel { 20 | constructor(entity) { 21 | super(entity); 22 | this._id = new ObjectId(entity._id || entity.id); 23 | } 24 | } 25 | 26 | class DoctorResponse extends BaseDoctorModel { 27 | constructor(entity) { 28 | super(entity); 29 | this.hospital = entity.hospital || {}; 30 | this.coordinations = entity.coordinations || []; 31 | this.id = entity._id || entity.id; 32 | this.rate = this.ratings.reduce((result, r) => result + r.rate, 0); 33 | if (this.ratings.length > 0) { 34 | this.rate /= this.ratings.length; 35 | this.rate = Math.floor(this.rate); 36 | } 37 | const round = (num) => Number(num.toFixed(2)); 38 | const opinion = this.ratings.reduce((result, rate) => { 39 | const {knowledge, skills, attention, priceQuality} = rate.commonRate; 40 | result.knowledge += knowledge; 41 | result.skills += skills; 42 | result.attention += attention; 43 | result.priceQuality += priceQuality; 44 | return result; 45 | }, {knowledge: 0, skills: 0, attention: 0, priceQuality: 0}); 46 | if (this.ratings.length !== 0) { 47 | opinion.knowledge = round(opinion.knowledge / this.ratings.length); 48 | opinion.skills = round(opinion.skills / this.ratings.length); 49 | opinion.attention = round(opinion.attention / this.ratings.length); 50 | opinion.priceQuality = round(opinion.priceQuality / this.ratings.length); 51 | } 52 | this.opinion = opinion; 53 | } 54 | } 55 | 56 | class Education { 57 | constructor({graduated, university, degree}) { 58 | this.graduated = graduated || ''; 59 | this.university = university || ''; 60 | this.degree = degree || ''; 61 | } 62 | } 63 | 64 | exports.DoctorCreate = DoctorCreate; 65 | exports.DoctorResponse = DoctorResponse; 66 | -------------------------------------------------------------------------------- /src/models/drug.js: -------------------------------------------------------------------------------- 1 | const {ObjectId} = require('mongodb'); 2 | 3 | const Price = require('./price'); 4 | const {GroupDrugField} = require('./group'); 5 | 6 | class BaseDrugModel { 7 | constructor({name, slug, short_descr, full_descr, group, price, is_covered_by_insurance, is_by_prescription}) { 8 | this.name = name; 9 | this.slug = slug; 10 | this.short_descr = short_descr; 11 | this.full_descr = full_descr; 12 | this.price = new Price(price); 13 | this.is_covered_by_insurance = is_covered_by_insurance; 14 | this.is_by_prescription = is_by_prescription; 15 | this.group = new GroupDrugField(group || {}); 16 | } 17 | } 18 | 19 | class DrugModelCreate extends BaseDrugModel { 20 | constructor(entity) { 21 | super(entity); 22 | this._id = ObjectId(entity.id || entity._id); 23 | this.providers = entity.providers; 24 | } 25 | } 26 | 27 | class DrugModelResponse extends BaseDrugModel { 28 | constructor(entity) { 29 | super(entity); 30 | this.id = entity.id || entity._id; 31 | this.providers = {pharmacies: entity.providers}; 32 | } 33 | } 34 | 35 | exports.DrugModelCreate = DrugModelCreate; 36 | exports.DrugModelResponse = DrugModelResponse; 37 | -------------------------------------------------------------------------------- /src/models/foundations.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Foundation { 4 | constructor({publicKey, transactionId, name, link, treatment, timestamp}){ 5 | this.publicKey = publicKey; 6 | this.transactionId = transactionId; 7 | this.name = name; 8 | this.link = link; 9 | this.treatment = treatment; 10 | this.timestamp = timestamp; 11 | } 12 | } 13 | 14 | exports.Foundation = Foundation; 15 | -------------------------------------------------------------------------------- /src/models/group.js: -------------------------------------------------------------------------------- 1 | const {ObjectId} = require("mongodb"); 2 | 3 | class BaseGroupModel { 4 | constructor({name, slug, drugs}) { 5 | this.name = name; 6 | this.slug = slug; 7 | this.drugs = drugs || []; 8 | } 9 | } 10 | 11 | class GroupCreate extends BaseGroupModel { 12 | constructor(entity) { 13 | super(entity); 14 | this._id = ObjectId(entity.id || entity._id); 15 | } 16 | } 17 | 18 | class GroupDrugField extends BaseGroupModel { 19 | constructor(entity) { 20 | super(entity); 21 | this.id = (entity.id || entity._id).toString(); 22 | delete this.drugs; 23 | } 24 | } 25 | 26 | class GroupResponse extends BaseGroupModel { 27 | constructor(entity) { 28 | super(entity); 29 | this.id = entity.id || entity._id; 30 | this.drugs = this.drugs.length; 31 | } 32 | } 33 | 34 | class GroupDrugModelResponse extends GroupResponse { 35 | constructor(entity) { 36 | super(entity); 37 | delete this.drugs; 38 | } 39 | } 40 | 41 | 42 | exports.GroupCreate = GroupCreate; 43 | exports.GroupResponse = GroupResponse; 44 | exports.GroupDrugField = GroupDrugField; 45 | exports.GroupDrugModelResponse = GroupDrugModelResponse; 46 | -------------------------------------------------------------------------------- /src/models/hospitals.js: -------------------------------------------------------------------------------- 1 | const ObjectId = require("mongodb").ObjectId; 2 | const LocationModel = require('./location'); 3 | 4 | class HospitalBaseModel { 5 | constructor({name, slug, network, departments, specializations, coordinations, email, phone, site, location, photos, doctors, workTime, work_time, ratings}) { 6 | this.name = name || ''; 7 | this.slug = slug || ''; 8 | this.network = network || ''; 9 | this.specializations = specializations || []; 10 | this.departments = departments || []; 11 | this.coordinations = coordinations || {}; 12 | this.email = email || ''; 13 | this.phone = phone || ''; 14 | this.site = site || ''; 15 | this.location = new LocationModel(location || {}); 16 | this.photos = photos || []; 17 | this.doctors = doctors || []; 18 | this.work_time = workTime || work_time; 19 | this.ratings = ratings || []; 20 | } 21 | } 22 | 23 | class HospitalCreate extends HospitalBaseModel { 24 | constructor(entity) { 25 | super(entity); 26 | this._id = ObjectId(entity.id || entity._id); 27 | } 28 | } 29 | 30 | class HospitalResponse extends HospitalBaseModel { 31 | constructor(entity) { 32 | super(entity); 33 | this.id = entity.id || entity._id; 34 | } 35 | } 36 | 37 | exports.HospitalBaseModel = HospitalBaseModel; 38 | exports.HospitalCreate = HospitalCreate; 39 | exports.HospitalResponse = HospitalResponse; 40 | -------------------------------------------------------------------------------- /src/models/location.js: -------------------------------------------------------------------------------- 1 | class LocationModel { 2 | constructor({address, city, state, country, postCode}) { 3 | this.address = address; 4 | this.city = city; 5 | this.state = state; 6 | this.country = country; 7 | this.postCode = postCode; 8 | } 9 | } 10 | 11 | module.exports = LocationModel; 12 | -------------------------------------------------------------------------------- /src/models/pharmacies.js: -------------------------------------------------------------------------------- 1 | const ObjectId = require("mongodb").ObjectId; 2 | const LocationModel = require('./location'); 3 | 4 | class BasePharmaciesModel { 5 | constructor({name, slug, network, coordinations, location, workTime, work_time, photos, drugs, short_descr, site, ratings}){ 6 | this.name = name; 7 | this.slug = slug; 8 | this.network = network; 9 | this.coordinations = coordinations || {}; 10 | this.location = new LocationModel(location); 11 | this.work_time = workTime || work_time; 12 | this.photos = photos || []; 13 | this.drugs = drugs || []; 14 | this.short_descr = short_descr || ""; 15 | this.site = site || ''; 16 | this.ratings = ratings || []; 17 | } 18 | } 19 | 20 | class PharmaciesCreate extends BasePharmaciesModel { 21 | constructor(entity){ 22 | super(entity); 23 | this._id = ObjectId(entity._id || entity.id); 24 | } 25 | } 26 | 27 | class PharmaciesResponse extends BasePharmaciesModel { 28 | constructor(entity) { 29 | super(entity); 30 | this.id = entity._id || entity.id; 31 | this.rate = this.ratings.reduce((result, r) => result + r.rate, 0); 32 | if (this.ratings.length > 0) { 33 | this.rate /= this.ratings.length; 34 | this.rate = Math.floor(this.rate); 35 | } 36 | const round = (num) => Number(num.toFixed(2)); 37 | const opinion = this.ratings.reduce((result, rate) => { 38 | const {service, priceQuality} = rate.commonRate; 39 | result.service += service; 40 | result.priceQuality += priceQuality; 41 | return result; 42 | }, {service: 0, priceQuality: 0}); 43 | if (this.ratings.length !== 0) { 44 | opinion.service = round(opinion.service / this.ratings.length); 45 | opinion.priceQuality = round(opinion.priceQuality / this.ratings.length); 46 | } 47 | this.opinion = opinion; 48 | } 49 | } 50 | 51 | exports.PharmaciesCreate = PharmaciesCreate; 52 | exports.PharmaciesResponse = PharmaciesResponse; 53 | -------------------------------------------------------------------------------- /src/models/price.js: -------------------------------------------------------------------------------- 1 | class PriceModel { 2 | constructor({dollars, mpts}) { 3 | this.dollars = dollars; 4 | this.mpts = mpts; 5 | } 6 | } 7 | 8 | module.exports = PriceModel; 9 | -------------------------------------------------------------------------------- /src/models/rates.js: -------------------------------------------------------------------------------- 1 | class Rate { 2 | constructor({rate, comment}) { 3 | this.rate = Math.min(rate || 0, 10); 4 | this.comment = comment; 5 | this.dateCreated = new Date(); 6 | } 7 | } 8 | 9 | class DoctorRate extends Rate { 10 | constructor({rate, comment, commonRate}){ 11 | super({rate, comment, commonRate}); 12 | this.commonRate = new DoctorCommonRate(commonRate || {}) 13 | } 14 | } 15 | 16 | class DoctorCommonRate { 17 | constructor({knowledge, skills, attention, priceQuality}) { 18 | this.knowledge = Math.min(knowledge || 0, 10); 19 | this.skills = Math.min(skills || 0, 10); 20 | this.attention = Math.min(attention || 0, 10); 21 | this.priceQuality = Math.min(priceQuality || 0, 10); 22 | } 23 | } 24 | 25 | class PharmacyRate extends Rate { 26 | constructor(rate) { 27 | super(rate); 28 | this.commonRate = new PharmacyCommonRate(rate.commonRate || {}); 29 | } 30 | } 31 | 32 | class PharmacyCommonRate { 33 | constructor(entity) { 34 | this.service = Math.min(entity.service || 0, 10); 35 | this.priceQuality = Math.min(entity.priceQuality, 10); 36 | } 37 | } 38 | 39 | exports.DoctorRate = DoctorRate; 40 | exports.DoctorCommonRate = DoctorCommonRate; 41 | exports.PharmacyRate = PharmacyRate; 42 | exports.PharmacyCommonRate = PharmacyCommonRate; 43 | -------------------------------------------------------------------------------- /src/models/service.js: -------------------------------------------------------------------------------- 1 | const ObjectId = require("mongodb").ObjectId; 2 | 3 | const Price = require('./price'); 4 | 5 | class BaseServiceModel { 6 | constructor({name, slug, short_descr, full_descr, is_covered_by_insurance, price, time, providers}){ 7 | this.name = name; 8 | this.slug = slug; 9 | this.short_descr = short_descr; 10 | this.full_descr = full_descr; 11 | this.is_covered_by_insurance = is_covered_by_insurance; 12 | this.price = new Price(price); 13 | this.time = time; 14 | this.providers = providers || {}; 15 | } 16 | } 17 | 18 | class ServiceCreate extends BaseServiceModel { 19 | constructor(entity) { 20 | super(entity); 21 | this._id = ObjectId(entity.id || entity._id); 22 | } 23 | } 24 | 25 | class ServiceResponse extends BaseServiceModel { 26 | constructor(entity) { 27 | super(entity); 28 | this.id = entity.id || entity._id; 29 | } 30 | } 31 | 32 | 33 | exports.ServiceCreate = ServiceCreate; 34 | exports.ServiceResponse = ServiceResponse; 35 | -------------------------------------------------------------------------------- /src/models/specialization.js: -------------------------------------------------------------------------------- 1 | const {ObjectId} = require('mongodb'); 2 | 3 | 4 | class BaseSpecializationModel { 5 | constructor({name, slug}){ 6 | this.name = name; 7 | this.slug = slug; 8 | } 9 | } 10 | 11 | 12 | class SpecializationCreateModel extends BaseSpecializationModel { 13 | constructor(entity){ 14 | super(entity); 15 | this._id = ObjectId(entity.id || entity._id); 16 | } 17 | } 18 | 19 | class SpecializationResponseModel extends BaseSpecializationModel { 20 | constructor(entity){ 21 | super(entity); 22 | this.id = entity.id || entity._id; 23 | } 24 | } 25 | 26 | 27 | exports.SpecializationCreateModel = SpecializationCreateModel; 28 | exports.SpecializationResponseModel = SpecializationResponseModel; 29 | -------------------------------------------------------------------------------- /src/models/tokens.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Token { 4 | constructor({publicKey, transactionId, name, type, balanceUSD, timestamp}){ 5 | this.publicKey = publicKey; 6 | this.transactionId = transactionId; 7 | this.name = name; 8 | this.type = type; 9 | this.balanceUSD = balanceUSD; 10 | this.timestamp = timestamp; 11 | } 12 | } 13 | 14 | exports.Token = Token; 15 | -------------------------------------------------------------------------------- /src/models/uploads.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class Upload { 4 | constructor({publicKey, transactionId, fullname, filename, timestamp, extension}){ 5 | this.publicKey = publicKey; 6 | this.transactionId = transactionId; 7 | this.fullname = fullname; 8 | this.filename = filename; 9 | this.timestamp = timestamp; 10 | this.extension = extension; 11 | } 12 | } 13 | 14 | exports.Upload = Upload; 15 | -------------------------------------------------------------------------------- /src/models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class User { 4 | constructor({publicKey, privateKey, email, firstName, lastName, gender, favorites}){ 5 | this.publicKey = publicKey; 6 | this.privateKey = privateKey; 7 | this.email = email; 8 | this.firstName = firstName; 9 | this.lastName = lastName; 10 | this.gender = gender; 11 | this.favorites = favorites || []; 12 | } 13 | } 14 | 15 | module.exports = User; 16 | -------------------------------------------------------------------------------- /src/notifications/backends/base.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class BaseNotification { 4 | constructor(config){ 5 | this.config = config; 6 | } 7 | } 8 | 9 | module.exports = BaseNotification; 10 | -------------------------------------------------------------------------------- /src/notifications/backends/email.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const nodemailer = require('nodemailer'); 4 | const BaseNotification = require('./base'); 5 | 6 | 7 | class EmailNotification extends BaseNotification { 8 | constructor(config){ 9 | super(config); 10 | this.transporter = nodemailer.createTransport({ 11 | host: this.config.host, 12 | port: this.config.port, 13 | secure: false, 14 | auth: this.config.auth, 15 | }); 16 | } 17 | 18 | async send({to, subject, message}){ 19 | return this.transporter.sendMail({ 20 | from: `"MedPoints" <${this.config.auth.user}>`, 21 | to, 22 | subject, 23 | html: message, 24 | }); 25 | } 26 | } 27 | 28 | module.exports = EmailNotification; 29 | -------------------------------------------------------------------------------- /src/notifications/backends/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const config = require('config'); 4 | 5 | const Email = require('./email'); 6 | 7 | module.exports = { 8 | email: new Email(config.get('notifications.email')), 9 | }; 10 | -------------------------------------------------------------------------------- /src/notifications/events.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {EventEmitter} = require('events'); 4 | const {render} = require('./renderer'); 5 | const {registration, ticket, question, subscription, book} = require('./formatters'); 6 | const backends = require('./backends/index'); 7 | 8 | class NotificationEvents{ 9 | constructor(){ 10 | this.emitter = new EventEmitter(); 11 | this._initEvents(); 12 | } 13 | 14 | raise(event, destination, data){ 15 | this.emitter.emit(event, {destination, data}); 16 | } 17 | 18 | _initEvents(){ 19 | const {emitter} = this; 20 | function onEvent(tmp, messageFormatter) { 21 | return async ({destination, data}) => { 22 | const input = messageFormatter(data); 23 | const template = await render({ 24 | template: tmp, 25 | data: input 26 | }); 27 | await backends.email.send({ 28 | to: destination, 29 | subject: template.subject, 30 | message: template.message, 31 | }); 32 | }; 33 | } 34 | emitter.on('registration', onEvent('registration', registration)); 35 | emitter.on('ticket', onEvent('ticket', ticket)); 36 | emitter.on('new_question', onEvent('question', question)); 37 | emitter.on('subscription', onEvent('subscription', subscription)); 38 | emitter.on('book', onEvent('book', book)); 39 | } 40 | } 41 | 42 | module.exports = new NotificationEvents(); 43 | -------------------------------------------------------------------------------- /src/notifications/formatters.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.registration = function({token, firstName, lastName}){ 4 | const fullname = `${firstName} ${lastName}`; 5 | const link = `https://medpoints.online/confirmation?token=${token}`; 6 | return { 7 | fullname, 8 | link, 9 | }; 10 | }; 11 | 12 | exports.ticket = function(ticket){ 13 | return ticket; 14 | }; 15 | 16 | exports.question = function(question){ 17 | return question; 18 | }; 19 | 20 | exports.subscription = function(email){ 21 | return email; 22 | }; 23 | 24 | exports.book = function({doctor, hospital, service, date}){ 25 | return {doctor, hospital, service, date}; 26 | }; 27 | -------------------------------------------------------------------------------- /src/notifications/renderer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const handlebars = require('handlebars'); 6 | const Promise = require('bluebird'); 7 | 8 | const log = require('../utils/logger').getLogger('RENDERER'); 9 | 10 | const readFileAsync = Promise.promisify(fs.readFile, {context: fs}); 11 | 12 | exports.render = async function(renderParams){ 13 | try{ 14 | const pathToTemplate = path.join(__dirname, 'templates', renderParams.template); 15 | const pathToBody = path.join(pathToTemplate, 'body.html'); 16 | const pathToSubject = path.join(pathToTemplate, 'subject.html'); 17 | const [subject, body] = await Promise.all([ 18 | readFileAsync(pathToSubject), 19 | readFileAsync(pathToBody), 20 | ]); 21 | const template = handlebars.compile(body.toString()); 22 | return {subject: subject.toString(), message: template(renderParams.data)}; 23 | }catch(err){ 24 | log.error(err, 'render_err'); 25 | throw err; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /src/notifications/templates/book/body.html: -------------------------------------------------------------------------------- 1 | The booking is confirmed
2 |
3 | Doctor - {{doctor.name}}
4 | Hospital - {{hospital.name}}
5 | Service - {{service.name}}
6 | Date - {{date}}
7 | -------------------------------------------------------------------------------- /src/notifications/templates/book/subject.html: -------------------------------------------------------------------------------- 1 | Successful booking 2 | -------------------------------------------------------------------------------- /src/notifications/templates/question/body.html: -------------------------------------------------------------------------------- 1 |

{{name}}

2 |

{{text}}

3 | 4 |

from {{email}}

5 | -------------------------------------------------------------------------------- /src/notifications/templates/question/subject.html: -------------------------------------------------------------------------------- 1 | FAQ -------------------------------------------------------------------------------- /src/notifications/templates/registration/body.html: -------------------------------------------------------------------------------- 1 | Hi, {{fullname}}
2 |

Please, confirm your registration!

3 | Click 4 | -------------------------------------------------------------------------------- /src/notifications/templates/registration/subject.html: -------------------------------------------------------------------------------- 1 | Confirmation 2 | -------------------------------------------------------------------------------- /src/notifications/templates/subscription/body.html: -------------------------------------------------------------------------------- 1 | {{email}} 2 | -------------------------------------------------------------------------------- /src/notifications/templates/subscription/subject.html: -------------------------------------------------------------------------------- 1 | New subscription 2 | -------------------------------------------------------------------------------- /src/notifications/templates/ticket/body.html: -------------------------------------------------------------------------------- 1 |

{{title}}

2 |

{{subject}}

3 |

{{text}}

4 | 5 |

from {{userEmail}}

6 | -------------------------------------------------------------------------------- /src/notifications/templates/ticket/subject.html: -------------------------------------------------------------------------------- 1 | Ticket 2 | -------------------------------------------------------------------------------- /src/routes/auth.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const { Router } = require("express"); 4 | 5 | const users = require("../lib/users/index"); 6 | 7 | const validator = require("../validators/auth"); 8 | 9 | const router = new Router(); 10 | 11 | router.get( 12 | "/:publicKey/:privateKey", 13 | validator.getUserInfo, 14 | async (req, res, next) => { 15 | try { 16 | res.result = await users.getUserInfo(req.params); 17 | next(); 18 | } catch (e) { 19 | next(e); 20 | } 21 | } 22 | ); 23 | 24 | router.post( 25 | "/register", 26 | validator.registerValidator, 27 | async (req, res, next) => { 28 | try { 29 | res.result = await users.register(req.body); 30 | next(); 31 | } catch (e) { 32 | next(e); 33 | } 34 | } 35 | ); 36 | 37 | router.get("/confirm", validator.confirmValidator, async (req, res, next) => { 38 | try { 39 | res.result = await users.confirm(req.query); 40 | // if (res.result === 'OK') { 41 | // res.redirect(301, 'http://medpoints.online/confirm'); 42 | // return; 43 | // } 44 | next(); 45 | } catch (err) { 46 | next(err); 47 | } 48 | }); 49 | 50 | router.post("/auth", validator.authValidator, async (req, res, next) => { 51 | try { 52 | res.result = await users.auth(req.body); 53 | next(); 54 | } catch (err) { 55 | next(err); 56 | } 57 | }); 58 | 59 | router.put( 60 | "/update", 61 | validator.updateProfileValidator, 62 | async (req, res, next) => { 63 | try { 64 | res.result = await users.updateProfile(req.body); 65 | next(); 66 | } catch (err) { 67 | next(err); 68 | } 69 | } 70 | ); 71 | 72 | router.get("/count", async (req, res, next) => { 73 | try { 74 | res.result = await users.getCount(); 75 | next(); 76 | } catch (err) { 77 | next(err); 78 | } 79 | }); 80 | 81 | router.get("/:publicKey/:privateKey/favorites", async (req, res, next) => { 82 | try { 83 | res.result = await users.getFavorites(req.params); 84 | next(); 85 | } catch (err) { 86 | next(err); 87 | } 88 | }); 89 | 90 | router.post( 91 | "/:publicKey/:privateKey/favorites", 92 | validator.addToFavorites, 93 | async (req, res, next) => { 94 | try { 95 | res.result = await users.addToFavorites(req.params, req.body); 96 | next(); 97 | } catch (err) { 98 | next(err); 99 | } 100 | } 101 | ); 102 | 103 | router.post( 104 | "/:publicKey/:privateKey/favorites/remove", 105 | async (req, res, next) => { 106 | try { 107 | res.result = await users.removeFromFavorites(req.params, req.body); 108 | next(); 109 | } catch (err) { 110 | next(err); 111 | } 112 | } 113 | ); 114 | 115 | exports.module = router; 116 | exports.name = "users"; 117 | -------------------------------------------------------------------------------- /src/routes/doctors.js: -------------------------------------------------------------------------------- 1 | const {Router} = require('express'); 2 | 3 | const doctors = require('../lib/doctors/index'); 4 | 5 | const router = new Router(); 6 | 7 | router.get('/', async (req, res, next) => { 8 | const {id, name, specialization, service, hospital, filter} = req.query; 9 | const paginator = req.paginator; 10 | try{ 11 | if(id){ 12 | res.result = await doctors.getDoctorById(id); 13 | } else{ 14 | res.result = await doctors.getDoctors({name, specialization, service, hospital, filter}, paginator); 15 | } 16 | next(); 17 | }catch(err){ 18 | next(err); 19 | } 20 | }); 21 | 22 | router.get('/:id/services', async (req, res, next) => { 23 | const {id} = req.params; 24 | try{ 25 | res.result = await doctors.getServicesByDoctorId(id); 26 | next(); 27 | }catch(err){ 28 | next(err); 29 | } 30 | }); 31 | 32 | router.get('/:id/hospitals', async (req, res, next) => { 33 | const {id} = req.params; 34 | const {service} = req.query; 35 | try{ 36 | res.result = await doctors.getHospitalsByDoctor({id, service}); 37 | next(); 38 | }catch(err){ 39 | next(err); 40 | } 41 | }); 42 | 43 | 44 | router.post('/', async (req, res, next) => { 45 | const doctor = req.body; 46 | try{ 47 | await doctors.saveDoctor(doctor); 48 | res.result = 'OK'; 49 | next(); 50 | }catch(err){ 51 | next(err); 52 | } 53 | }); 54 | 55 | router.put('/', async (req, res, next) => { 56 | const doctor = req.body; 57 | try{ 58 | await doctors.updateDoctor(doctor); 59 | res.result = 'OK'; 60 | next(); 61 | }catch(err){ 62 | next(err); 63 | } 64 | }); 65 | 66 | router.delete('/', async (req, res, next) => { 67 | const {id} = req.query; 68 | try{ 69 | await doctors.deleteDoctor(id); 70 | res.result = 'OK'; 71 | next(); 72 | }catch(err){ 73 | next(err); 74 | } 75 | }); 76 | 77 | router.get('/count', async (req, res, next) => { 78 | try { 79 | res.result = await doctors.getCount(); 80 | next(); 81 | }catch(err){ 82 | next(err); 83 | } 84 | }); 85 | 86 | router.post('/:id/rating/', async (req, res, next) => { 87 | const id = req.params.id; 88 | const rate = req.body; 89 | try{ 90 | await doctors.changeRateOfDoctor(id, rate); 91 | res.result = 'OK'; 92 | next(); 93 | }catch(err){ 94 | next(err); 95 | } 96 | }); 97 | 98 | 99 | exports.module = router; 100 | exports.name = 'doctors'; 101 | -------------------------------------------------------------------------------- /src/routes/drugs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {Router} = require('express'); 4 | 5 | const drugs = require('../lib/drugs/index'); 6 | 7 | const router = new Router(); 8 | 9 | router.get('/', async (req, res, next) => { 10 | const {id, name, groupId, pharmacyId, filter} = req.query; 11 | const paginator = req.paginator; 12 | try{ 13 | res.result = await drugs.getDrugs({name, id, groupId, pharmacyId, filter}, paginator); 14 | next(); 15 | }catch(err){ 16 | next(err); 17 | } 18 | }); 19 | 20 | router.get('/timer', async (req, res, next) => { 21 | const {interval} = req.query; 22 | try{ 23 | res.result = await drugs.getDrugsByInterval(interval); 24 | next(); 25 | }catch(err){ 26 | next(err); 27 | } 28 | }); 29 | 30 | 31 | router.post('/', async (req, res, next) => { 32 | try{ 33 | res.result = await drugs.saveDrug(req.body); 34 | next(); 35 | }catch(err){ 36 | next(err); 37 | } 38 | }); 39 | 40 | router.get('/count', async (req, res, next) => { 41 | try { 42 | res.result = await drugs.getCount(); 43 | next(); 44 | }catch(err){ 45 | next(err); 46 | } 47 | }); 48 | 49 | router.put('/', async (req, res, next) => { 50 | try{ 51 | await drugs.updateDrug(req.body); 52 | res.result = 'OK'; 53 | next(); 54 | }catch(err){ 55 | next(err); 56 | } 57 | }); 58 | 59 | router.delete('/', async (req, res, next) => { 60 | const {id} = req.body; 61 | try{ 62 | await drugs.deleteDrug(id); 63 | res.result = 'OK'; 64 | next(); 65 | }catch(err){ 66 | next(err); 67 | } 68 | }); 69 | 70 | 71 | exports.name = 'drugs'; 72 | exports.module = router; 73 | -------------------------------------------------------------------------------- /src/routes/faq.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {Router} = require('express'); 4 | 5 | const tickets = require('../lib/faq/index'); 6 | const validator = require('../validators/faq'); 7 | 8 | const router = new Router(); 9 | 10 | 11 | router.post('/create', validator.createQuestionValidator, async (req, res, next) => { 12 | try{ 13 | res.result = await tickets.createQuestion(req.body); 14 | next(); 15 | }catch(e){ 16 | next(e); 17 | } 18 | }); 19 | 20 | exports.module = router; 21 | exports.name = 'faq'; 22 | -------------------------------------------------------------------------------- /src/routes/foundations.js: -------------------------------------------------------------------------------- 1 | const {Router} = require('express'); 2 | 3 | const foundations = require('../lib/foundations/index'); 4 | 5 | const router = new Router(); 6 | 7 | router.get('/:publicKey', async (req, res, next) => { 8 | const {publicKey} = req.params; 9 | try{ 10 | res.result = await foundations.getFoundationsByPublicKey(publicKey); 11 | next(); 12 | }catch(e){ 13 | next(e); 14 | } 15 | }); 16 | 17 | router.post('/', async (req, res, next) => { 18 | const foundation = req.body; 19 | try{ 20 | await foundations.addFoundation(foundation); 21 | res.result = 'OK'; 22 | next(); 23 | }catch(err){ 24 | next(err); 25 | } 26 | }); 27 | 28 | exports.module = router; 29 | exports.name = 'foundations'; -------------------------------------------------------------------------------- /src/routes/group.js: -------------------------------------------------------------------------------- 1 | const {Router} = require('express'); 2 | 3 | const group = require('../lib/groups/index'); 4 | 5 | const router = new Router(); 6 | 7 | router.get('/', async (req, res, next) => { 8 | const {id, name} = req.query; 9 | try{ 10 | if(!id && !name){ 11 | res.result = await group.getAllGroups({name}, req.paginator); 12 | }else { 13 | res.result = await group.getGroup(id); 14 | } 15 | next(); 16 | }catch(err){ 17 | next(err); 18 | } 19 | next(); 20 | }); 21 | 22 | router.get('/timer', async (req, res, next) => { 23 | const {interval} = req.query; 24 | try{ 25 | res.result = await group.getGroupsByInterval(interval); 26 | next(); 27 | }catch(err){ 28 | next(err); 29 | } 30 | }); 31 | 32 | router.post('/', async (req, res, next) => { 33 | try{ 34 | res.result = await group.saveGroup(req.body); 35 | next(); 36 | }catch(err){ 37 | next(err); 38 | } 39 | }); 40 | 41 | router.put('/', async (req, res, next) => { 42 | try{ 43 | await group.updateGroup(req.body); 44 | res.result = 'OK'; 45 | next(); 46 | }catch(err){ 47 | next(err); 48 | } 49 | }); 50 | 51 | router.delete('/', async (req, res, next) => { 52 | const {id} = req.body; 53 | try{ 54 | await group.deleteGroup(id); 55 | res.result = 'OK'; 56 | next(); 57 | }catch(err){ 58 | next(err); 59 | } 60 | }); 61 | 62 | exports.name = 'groups'; 63 | exports.module = router; 64 | -------------------------------------------------------------------------------- /src/routes/hospitals.js: -------------------------------------------------------------------------------- 1 | const {Router} = require('express'); 2 | 3 | const hospital = require('../lib/hospitals/index'); 4 | 5 | const router = new Router(); 6 | 7 | router.get('/', async (req, res, next) => { 8 | const {id, name, country, specializationId, service, filter} = req.query; 9 | try{ 10 | res.result = await hospital.getHospital({id, name, country, specializationId, service, filter}, req.paginator); 11 | next(); 12 | }catch(err){ 13 | next(err); 14 | } 15 | }); 16 | 17 | 18 | router.get('/locations', async (req, res, next) => { 19 | try{ 20 | res.result = await hospital.getHospitalsLocations(); 21 | next(); 22 | }catch(err){ 23 | next(err); 24 | } 25 | }); 26 | 27 | 28 | router.post('/', async (req, res, next) => { 29 | try{ 30 | await hospital.saveHospital(req.body); 31 | res.result = 'OK'; 32 | next(); 33 | }catch(err){ 34 | next(err); 35 | } 36 | }); 37 | 38 | router.put('/', async (req, res, next) => { 39 | const hospital = req.body; 40 | try{ 41 | await hospital.updateHospital(hospital); 42 | res.result = 'OK'; 43 | next(); 44 | }catch(err){ 45 | next(err); 46 | } 47 | }); 48 | 49 | router.get('/count', async (req, res, next) => { 50 | try { 51 | res.result = await hospital.getCount(); 52 | next(); 53 | }catch(err){ 54 | next(err); 55 | } 56 | }); 57 | 58 | router.delete('/', async (req, res, next) => { 59 | const {id} = req.query; 60 | try{ 61 | await hospital.deleteHospital(id); 62 | res.result = 'OK'; 63 | next(); 64 | }catch(err){ 65 | next(err); 66 | } 67 | }); 68 | 69 | exports.module = router; 70 | exports.name = 'hospitals'; 71 | -------------------------------------------------------------------------------- /src/routes/maintenance.js: -------------------------------------------------------------------------------- 1 | const {Router} = require('express'); 2 | 3 | const maintenance = require('../lib/maintenance'); 4 | 5 | const router = new Router(); 6 | 7 | router.post('/importDrugs', async (req, res, next) => { 8 | try{ 9 | await maintenance.importDrugs(req.body.result); 10 | res.status = 'OK'; 11 | next(); 12 | }catch(err){ 13 | next(err); 14 | } 15 | }); 16 | 17 | exports.name = 'maintenance'; 18 | exports.module = router; 19 | -------------------------------------------------------------------------------- /src/routes/notifications.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {Router} = require('express'); 4 | const notifications = require('../lib/notifications/index'); 5 | 6 | const router = new Router(); 7 | 8 | router.post('/renderBook', async (req, res, next) => { 9 | try { 10 | await notifications.renderBook(req.body); 11 | res.result = 'OK'; 12 | next(); 13 | } catch(err){ 14 | next(err); 15 | } 16 | }); 17 | 18 | exports.name = 'notifications'; 19 | exports.module = router; 20 | -------------------------------------------------------------------------------- /src/routes/paginator.js: -------------------------------------------------------------------------------- 1 | class Paginator { 2 | constructor({page, count}) { 3 | this.page = parseInt(page, 10) || 1; 4 | this.count = parseInt(count, 10) || 10; 5 | } 6 | getOffset(){ 7 | return this.count * (this.page - 1) 8 | } 9 | } 10 | 11 | exports.createPaginator = function(query){ 12 | return new Paginator(query); 13 | }; 14 | -------------------------------------------------------------------------------- /src/routes/pharmacies.js: -------------------------------------------------------------------------------- 1 | const {Router} = require('express'); 2 | 3 | const pharmacies = require('../lib/pharmacies/index'); 4 | 5 | const router = new Router(); 6 | 7 | router.get('/', async (req, res, next) => { 8 | const {id, name, drugId, filter} = req.query; 9 | const paginator = req.paginator; 10 | try{ 11 | res.result = await pharmacies.getPharmacies({name, id, drugId, filter}, paginator); 12 | next(); 13 | }catch(err){ 14 | next(err); 15 | } 16 | }); 17 | 18 | 19 | router.post('/', async (req, res, next) => { 20 | const pharmacy = req.body; 21 | try{ 22 | await pharmacies.savePharmacy(pharmacy); 23 | res.result = 'OK'; 24 | next(); 25 | }catch(err){ 26 | next(err); 27 | } 28 | }); 29 | 30 | router.put('/', async (req, res, next) => { 31 | const pharmacy = req.body; 32 | try{ 33 | await pharmacies.updatePharmacy(pharmacy); 34 | res.result = 'OK'; 35 | next(); 36 | }catch(err){ 37 | next(err); 38 | } 39 | }); 40 | 41 | router.delete('/', async (req, res, next) => { 42 | const {id} = req.query; 43 | try{ 44 | await pharmacies.deletePharmacy(id); 45 | res.result = 'OK'; 46 | next(); 47 | }catch(err){ 48 | next(err); 49 | } 50 | }); 51 | 52 | router.get('/count', async (req, res, next) => { 53 | try { 54 | res.result = await pharmacies.getCount(); 55 | next(); 56 | }catch(err){ 57 | next(err); 58 | } 59 | }); 60 | 61 | 62 | router.post('/rating/:id', async (req, res, next) => { 63 | const id = req.params.id; 64 | const rate = req.body; 65 | try{ 66 | await pharmacies.changeRateOfPharmacy(id, rate); 67 | res.result = 'OK'; 68 | next(); 69 | }catch(err){ 70 | next(err); 71 | } 72 | }); 73 | 74 | 75 | exports.module = router; 76 | exports.name = 'pharmacies'; 77 | -------------------------------------------------------------------------------- /src/routes/responses.js: -------------------------------------------------------------------------------- 1 | class ResponseWithMeta { 2 | constructor({data, meta}) { 3 | this.data = data; 4 | this.meta = meta; 5 | } 6 | } 7 | 8 | exports.ResponseWithMeta = ResponseWithMeta; 9 | 10 | 11 | class LocationsResponse{ 12 | constructor({worldsCount, locations}) { 13 | this.worldsCount = worldsCount; 14 | this.locations = locations; 15 | } 16 | } 17 | 18 | exports.LocationsResponse = LocationsResponse; 19 | -------------------------------------------------------------------------------- /src/routes/routes.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const express = require('express'); 5 | const bodyParser = require('body-parser'); 6 | const {createPaginator} = require('./paginator'); 7 | 8 | const {getMiddlewareLogger} = require('../utils/logger'); 9 | 10 | const routes = [ 11 | require('./doctors'), 12 | require('./hospitals'), 13 | require('./drugs'), 14 | require('./pharmacies'), 15 | require('./services'), 16 | require('./group'), 17 | require('./specializations'), 18 | require('./maintenance'), 19 | require('./auth'), 20 | require('./tickets'), 21 | require('./faq'), 22 | require('./subscriptions'), 23 | require('./uploads'), 24 | require('./foundations'), 25 | require('./tokens'), 26 | require('./notifications'), 27 | ]; 28 | 29 | const PREFIX = '/api'; 30 | 31 | exports.initServer = ({log}) => { 32 | const app = express(); 33 | app.use(bodyParser.json({limit: '10mb'})); 34 | app.use(getMiddlewareLogger(log)); 35 | app.use((req, res, next) => { 36 | req.paginator = createPaginator(req.query); 37 | next(); 38 | }); 39 | for(const route of routes){ 40 | app.use(path.join(PREFIX, route.name), route.module); 41 | log.debug({module: route.name}, `module was loaded`); 42 | } 43 | app.use((req, res, next) => { 44 | if(!res.result){ 45 | next(); 46 | return; 47 | } 48 | res.json({ 49 | error: null, 50 | result: res.result 51 | }); 52 | next(); 53 | }); 54 | app.use((err, req, res, next) => { 55 | let statusCode = 500; 56 | if(err.name === 'ValidationError'){ 57 | statusCode = 422; 58 | } 59 | res.status(statusCode).json({error: err.message}); 60 | next(); 61 | }); 62 | return app; 63 | }; 64 | -------------------------------------------------------------------------------- /src/routes/services.js: -------------------------------------------------------------------------------- 1 | const {Router} = require('express'); 2 | 3 | const services = require('../lib/services/index'); 4 | 5 | const router = new Router(); 6 | 7 | router.get('/', async (req, res, next) => { 8 | const {id, name, hospital, filter} = req.query; 9 | const paginator = req.paginator; 10 | try{ 11 | res.result = await services.getServices({name, id, hospital, filter}, paginator); 12 | next(); 13 | }catch(err){ 14 | next(err); 15 | } 16 | }); 17 | 18 | 19 | router.post('/', async (req, res, next) => { 20 | const pharmacy = req.body; 21 | try{ 22 | await services.saveService(pharmacy); 23 | res.result = 'OK'; 24 | next(); 25 | }catch(err){ 26 | next(err); 27 | } 28 | }); 29 | 30 | router.put('/', async (req, res, next) => { 31 | const pharmacy = req.body; 32 | try{ 33 | await services.updateService(pharmacy); 34 | res.result = 'OK'; 35 | next(); 36 | }catch(err){ 37 | next(err); 38 | } 39 | }); 40 | 41 | router.delete('/', async (req, res, next) => { 42 | const {id} = req.query; 43 | try{ 44 | await services.deleteService(id); 45 | res.result = 'OK'; 46 | next(); 47 | }catch(err){ 48 | next(err); 49 | } 50 | }); 51 | 52 | router.get('/count', async (req, res, next) => { 53 | try { 54 | res.result = await services.getCount(); 55 | next(); 56 | }catch(err){ 57 | next(err); 58 | } 59 | }); 60 | 61 | // router.post('/rating/:id', async (req, res, next) => { 62 | // const id = req.params.id; 63 | // const rate = req.body; 64 | // try{ 65 | // await pharmacies.changeRateOfDoctor(id, rate); 66 | // res.result = 'OK'; 67 | // next(); 68 | // }catch(err){ 69 | // next(err); 70 | // } 71 | // }); 72 | 73 | 74 | exports.module = router; 75 | exports.name = 'services'; 76 | -------------------------------------------------------------------------------- /src/routes/specializations.js: -------------------------------------------------------------------------------- 1 | const {Router} = require('express'); 2 | 3 | const specialization = require('../lib/specializations/index'); 4 | 5 | const router = new Router(); 6 | 7 | router.get('/', async (req, res, next) => { 8 | const paginator = req.paginator; 9 | try{ 10 | res.result = await specialization.getSpecializations(paginator); 11 | next(); 12 | }catch(err){ 13 | next(err); 14 | } 15 | }); 16 | 17 | router.post('/', async (req, res, next) => { 18 | try{ 19 | await specialization.saveSpecialization(req.body); 20 | res.result = 'OK'; 21 | next(); 22 | }catch(err){ 23 | next(err); 24 | } 25 | }); 26 | 27 | exports.name = 'specializations'; 28 | exports.module = router; 29 | -------------------------------------------------------------------------------- /src/routes/subscriptions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {Router} = require('express'); 4 | 5 | const validator = require('../validators/subscriptions'); 6 | const subscriptions = require('../lib/subscriptions/index'); 7 | 8 | const router = new Router(); 9 | 10 | router.post('/add', validator.addSubscriptions, async (req, res, next) => { 11 | try{ 12 | res.result = await subscriptions.addSubscription(req.body.email); 13 | next(); 14 | }catch(err){ 15 | next(err); 16 | } 17 | }); 18 | 19 | exports.module = router; 20 | exports.name = 'subscriptions'; 21 | -------------------------------------------------------------------------------- /src/routes/tickets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const {Router} = require('express'); 4 | 5 | const tickets = require('../lib/tickets/index'); 6 | const validator = require('../validators/tickets'); 7 | 8 | const router = new Router(); 9 | 10 | 11 | router.get('/:publicKey/:privateKey', validator.getTicketsByUser, async (req, res, next) => { 12 | try{ 13 | res.result = await tickets.getTicketsByUser(req.params); 14 | next(); 15 | }catch(e){ 16 | next(e); 17 | } 18 | }); 19 | 20 | router.post('/create', validator.createTicketValidator, async (req, res, next) => { 21 | try{ 22 | res.result = await tickets.createTicket(req.body); 23 | next(); 24 | }catch(e){ 25 | next(e); 26 | } 27 | }); 28 | 29 | exports.module = router; 30 | exports.name = 'tickets'; 31 | -------------------------------------------------------------------------------- /src/routes/tokens.js: -------------------------------------------------------------------------------- 1 | const {Router} = require('express'); 2 | 3 | const tokens = require('../lib/tokens/index'); 4 | 5 | const router = new Router(); 6 | 7 | router.get('/:publicKey', async (req, res, next) => { 8 | const {publicKey} = req.params; 9 | try{ 10 | res.result = await tokens.getTokensByPublicKey(publicKey); 11 | next(); 12 | }catch(e){ 13 | next(e); 14 | } 15 | }); 16 | 17 | router.post('/', async (req, res, next) => { 18 | const token = req.body; 19 | try{ 20 | await tokens.addToken(token); 21 | res.result = 'OK'; 22 | next(); 23 | }catch(err){ 24 | next(err); 25 | } 26 | }); 27 | 28 | router.get('/:publicKey/count', async (req, res, next) => { 29 | const {publicKey} = req.params; 30 | try{ 31 | res.result = await tokens.countTokensByPublicKey(publicKey); 32 | next(); 33 | }catch(e){ 34 | next(e); 35 | } 36 | }); 37 | 38 | exports.module = router; 39 | exports.name = 'tokens'; -------------------------------------------------------------------------------- /src/routes/uploads.js: -------------------------------------------------------------------------------- 1 | const {Router} = require('express'); 2 | 3 | const uploads = require('../lib/uploads/index'); 4 | 5 | const router = new Router(); 6 | 7 | router.get('/:publicKey', async (req, res, next) => { 8 | const {publicKey} = req.params; 9 | try{ 10 | res.result = await uploads.getUploadsByPublicKey(publicKey); 11 | next(); 12 | }catch(e){ 13 | next(e); 14 | } 15 | }); 16 | 17 | router.post('/', async (req, res, next) => { 18 | const upload = req.body; 19 | try{ 20 | await uploads.addUpload(upload); 21 | res.result = 'OK'; 22 | next(); 23 | }catch(err){ 24 | next(err); 25 | } 26 | }); 27 | 28 | exports.module = router; 29 | exports.name = 'uploads'; -------------------------------------------------------------------------------- /src/utils/logger.js: -------------------------------------------------------------------------------- 1 | const config = require('config'); 2 | const bunyan = require('bunyan'); 3 | 4 | const logSettings = config.get('logger'); 5 | 6 | const loggers = {}; 7 | 8 | /** 9 | * @param {String} name 10 | * @returns {Logger} 11 | */ 12 | exports.getLogger = (name) => { 13 | if(loggers[name]){ 14 | return loggers[name]; 15 | } 16 | const loggerOptions = { 17 | name: name, 18 | }; 19 | const logger = bunyan.createLogger(loggerOptions); 20 | loggers[name] = logger; 21 | return logger; 22 | }; 23 | 24 | 25 | /** 26 | * @param {Logger} log 27 | * @return {Function} 28 | */ 29 | exports.getMiddlewareLogger = (log) => { 30 | return (req, res, next) => { 31 | req.log = log.child({className: 'SERVER', path: req.path, queryparams: req.query, method: req.method}); 32 | req.log.info('INCOMING_REQUEST'); 33 | next(); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /src/validators/auth.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Promise = require('bluebird'); 4 | const Joi = require('joi'); 5 | 6 | const validate = Promise.promisify(Joi.validate, {context: Joi}); 7 | 8 | const log = require('../utils/logger').getLogger('VALIDATION'); 9 | 10 | const publicKey = Joi.string().required(); 11 | const privateKey = Joi.string().required(); 12 | 13 | const UserSchema = Joi.object({ 14 | publicKey, privateKey, 15 | email: Joi.string().email({minDomainAtoms: 2}).optional(), 16 | firstName: Joi.string().optional(), 17 | lastName: Joi.string().optional(), 18 | gender: Joi.string().optional(), 19 | }); 20 | 21 | const AuthSchema = Joi.object({ 22 | publicKey, privateKey 23 | }); 24 | 25 | const ConfirmSchema = Joi.object({ 26 | token: Joi.string().required() 27 | }); 28 | 29 | const FavoriteSchema = Joi.object({ 30 | id: Joi.string().required(), 31 | type: Joi.string().required(), 32 | }); 33 | 34 | exports.registerValidator = async (req, res, next) => { 35 | try{ 36 | await validate(req.body, UserSchema); 37 | next(); 38 | }catch(err){ 39 | log.error('validation error', err); 40 | next(err); 41 | } 42 | }; 43 | 44 | exports.getUserInfo = async (req, res, next) => { 45 | try{ 46 | await validate(req.params, AuthSchema); 47 | next(); 48 | }catch(err){ 49 | log.error('validation error', err); 50 | next(err); 51 | } 52 | }; 53 | 54 | exports.authValidator = async (req, res, next) => { 55 | try{ 56 | await validate(req.body, AuthSchema); 57 | next(); 58 | }catch(err){ 59 | log.error('validation error', err); 60 | next(err); 61 | } 62 | }; 63 | 64 | exports.confirmValidator = async (req, res, next) => { 65 | try{ 66 | await validate(req.query, ConfirmSchema); 67 | next(); 68 | }catch(err){ 69 | log.error('validation error', err); 70 | next(err); 71 | } 72 | }; 73 | 74 | exports.updateProfileValidator = async (req, res, next) => { 75 | try{ 76 | await validate(req.body, UserSchema); 77 | next(); 78 | }catch(err){ 79 | log.error('validation error', err); 80 | next(err); 81 | } 82 | }; 83 | 84 | exports.addToFavorites = async (req, res, next) => { 85 | try{ 86 | await validate(req.body, FavoriteSchema); 87 | next(); 88 | }catch(err){ 89 | log.error('validation error', err); 90 | next(err); 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /src/validators/faq.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Promise = require('bluebird'); 4 | const Joi = require('joi'); 5 | 6 | const validate = Promise.promisify(Joi.validate, {context: Joi}); 7 | 8 | 9 | const log = require('../utils/logger').getLogger('VALIDATION'); 10 | 11 | const CreateQuestionSchema = { 12 | name: Joi.string().required(), 13 | email: Joi.string().email({minDomainAtoms: 2}).required(), 14 | text: Joi.string().required(), 15 | }; 16 | 17 | exports.createQuestionValidator = async (req, res, next) => { 18 | try{ 19 | await validate(req.body, CreateQuestionSchema); 20 | next(); 21 | }catch(err){ 22 | log.error('validation error', err); 23 | next(err); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/validators/subscriptions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Promise = require('bluebird'); 4 | const Joi = require('joi'); 5 | 6 | const validate = Promise.promisify(Joi.validate, {context: Joi}); 7 | 8 | const log = require('../utils/logger').getLogger('VALIDATION'); 9 | 10 | const AddSubscriptionSchema = { 11 | email: Joi.string().email({minDomainAtoms: 2}).required(), 12 | }; 13 | 14 | exports.addSubscriptions = async (req, res, next) => { 15 | try{ 16 | await validate(req.body, AddSubscriptionSchema); 17 | next(); 18 | }catch(err){ 19 | log.error('validation error', err); 20 | next(err); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /src/validators/tickets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Promise = require('bluebird'); 4 | const Joi = require('joi'); 5 | 6 | const validate = Promise.promisify(Joi.validate, {context: Joi}); 7 | 8 | const log = require('../utils/logger').getLogger('VALIDATION'); 9 | 10 | const GetTicketsByUserSchema = { 11 | publicKey: Joi.string().required(), 12 | privateKey: Joi.string().required(), 13 | }; 14 | 15 | const CreateTicketSchema = Joi.object({ 16 | publicKey: Joi.string().required(), 17 | privateKey: Joi.string().required(), 18 | title: Joi.string().required(), 19 | subject: Joi.string().required(), 20 | text: Joi.string().required(), 21 | }); 22 | 23 | exports.createTicketValidator = async (req, res, next) => { 24 | try{ 25 | await validate(req.body, CreateTicketSchema); 26 | next(); 27 | }catch(err){ 28 | log.error('validation error', err); 29 | next(err); 30 | } 31 | }; 32 | 33 | exports.getTicketsByUser = async (req, res, next) => { 34 | try{ 35 | await validate(req.params, GetTicketsByUserSchema); 36 | next(); 37 | }catch(err){ 38 | log.error('validation error', err); 39 | next(err); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /tests/example.test.js: -------------------------------------------------------------------------------- 1 | const example = require('../src/lib/example/index'); 2 | 3 | describe('Test Example', () => { 4 | test('test', async () => { 5 | const result = await example.sum(1, 2); 6 | expect(result).toBe(3); 7 | }); 8 | }); --------------------------------------------------------------------------------