diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ad0f13 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# IntelliJ Idea project settings folder +.idea + +# NPM package manager temporary files and folders +node_modules +npm-debug.log +package-lock.json + +# ModelGen output folders +migrations +models +models-webservice +resolvers +schemas +test +validations +patches \ No newline at end of file diff --git a/Dockerfile.graphql_server b/Dockerfile.graphql_server new file mode 100644 index 0000000..ac4fff3 --- /dev/null +++ b/Dockerfile.graphql_server @@ -0,0 +1,15 @@ +FROM node:11.12.0-stretch-slim + +# Create app directory +WORKDIR /usr/ScienceDbStarterPack/graphql-server + +# Copy generated code into the skeleton GraphQL-Server +COPY . . + +# Clone the skeleton project and install dependencies +RUN chmod u+x ./migrateDbAndStartServer.sh && \ + rm .git* && \ + mv ./config/config_postgres_docker.json ./config/config.json && \ + npm install + +EXPOSE 3000 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md index 6aa2032..97ec8f8 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # GraphQL backend Skeleton NodeJS project for a graphQL server. -This package integrates the code generated by [code-generator](https://github.com/vsuaste/express_graphql_model_gen). +This package integrates the code generated by [code-generator](https://github.com/ScienceDb/graphql-server-model-codegen). -[Code-generator](https://github.com/vsuaste/express_graphql_model_gen) will generate four folders with the models' information: +[Code-generator](https://github.com/ScienceDb/graphql-server-model-codegen) will generate four folders with the models' information: * models * schemas * resolvers @@ -12,18 +12,26 @@ This package integrates the code generated by [code-generator](https://github.co After getting ready the generated code for the models, proceed with the server set up. ## Set up + Clone the repository and run: ``` $ npm install $ node_modules/.bin/sequelize db:migrate -$ node server.js +$ npm start ``` ``` $ node_modules/.bin/sequelize db:migrate ``` command will create the tables specified in the ```migrations``` folder. With credential as in ``` config/config.json``` file. +### ENVIRONMENT VARIABLES + +* `PORT` - The port where the app is listening, default value is `3000` +* `ALLOW_ORIGIN` - In development mode we need to specify the header `Access-Control-Allow-Origin` so the SPA application can communicate with the server, default value `http://localhost:8080`. + +## NOTE +A data base should be already configured locally as in `config/config.json` ## Example of use -If you followed the example for generating the code described [here](https://github.com/vsuaste/express_graphql_model_gen), you can try the next queries and mutations. Otherwise, just adapt the same queries and mutations for your own models generated. +If you followed the example for generating the code described [here](https://github.com/ScienceDb/graphql-server-model-codegen), you can try the next queries and mutations. Otherwise, just adapt the same queries and mutations for your own models generated. We will add the next 4 people to our table ``people``. @@ -65,7 +73,7 @@ curl -XPOST http://localhost:3000/graphql -H 'Content-Type: application/graphql' We'll search people with 'science' as substring of their email and as result we'll get only their name and last name. ``` -curl -XPOST http://localhost:3000/graphql -H 'Content-Type: application/graphql' -d '{ searchPerson(input:{field:email, value:{value:"%science%"}, operator:like}){ firstName lastName}}' +curl -XPOST http://localhost:3000/graphql -H 'Content-Type: application/graphql' -d '{ people(search:{field:email, value:{value:"%science%"}, operator:like}){ firstName lastName}}' ``` The result will be: diff --git a/acl_rules.js b/acl_rules.js index ad76120..ce28b79 100644 --- a/acl_rules.js +++ b/acl_rules.js @@ -1,8 +1,8 @@ module.exports = { aclRules: [{ - roles: 'administrator', + roles: 'admin', allows: [{ - resources: '*', + resources: ['users', 'roles'], permissions: '*' }] }, diff --git a/config/config.json b/config/config.json index 5d202e1..b0b9c84 100644 --- a/config/config.json +++ b/config/config.json @@ -4,20 +4,23 @@ "password": "test_code_gen", "database": "test_code_gen", "host": "127.0.0.1", - "dialect": "postgres" + "dialect": "postgres", + "operatorsAliases": false }, "test": { - "username": "test_code_gen", - "password": "test_code_gen", - "database": "test_code_gen", - "host": "127.0.0.1", - "dialect": "postgres" + "username": "sciencedb", + "password": "sciencedb", + "database": "sciencedb_test", + "host": "postgres", + "dialect": "postgres", + "operatorsAliases": false }, "production": { - "username": "test_code_gen", - "password": "test_code_gen", - "database": "test_code_gen", - "host": "127.0.0.1", - "dialect": "postgres" + "username": "sciencedb", + "password": "sciencedb", + "database": "sciencedb_production", + "host": "postgres", + "dialect": "postgres", + "operatorsAliases": false } } diff --git a/config/config_postgres_docker.json b/config/config_postgres_docker.json new file mode 100644 index 0000000..d120ba8 --- /dev/null +++ b/config/config_postgres_docker.json @@ -0,0 +1,26 @@ +{ + "development": { + "username": "sciencedb", + "password": "sciencedb", + "database": "sciencedb_development", + "host": "sdb_postgres", + "dialect": "postgres", + "operatorsAliases": false + }, + "test": { + "username": "sciencedb", + "password": "sciencedb", + "database": "sciencedb_test", + "host": "sdb_postgres", + "dialect": "postgres", + "operatorsAliases": false + }, + "production": { + "username": "sciencedb", + "password": "sciencedb", + "database": "sciencedb_production", + "host": "sdb_postgres", + "dialect": "postgres", + "operatorsAliases": false + } +} diff --git a/config/globals.js b/config/globals.js new file mode 100644 index 0000000..4fcb596 --- /dev/null +++ b/config/globals.js @@ -0,0 +1,10 @@ +module.exports = { + LIMIT_RECORDS : process.env.LIMIT_RECORDS || 10000, + PORT : process.env.PORT || 3000, + ALLOW_ORIGIN: process.env.ALLOW_ORIGIN || "http://localhost:8080", + REQUIRE_SIGN_IN: process.env.REQUIRE_SIGN_IN || "true", + MAIL_SERVICE: "gmail", + MAIL_HOST: "smtp.gmail.com", + MAIL_ACCOUNT: "sci.db.service@gmail.com", + MAIL_PASSWORD: "SciDbServiceQAZ" +} diff --git a/connection.js b/connection.js index 054bcdd..99b2624 100644 --- a/connection.js +++ b/connection.js @@ -1,23 +1,23 @@ -Sequelize = require('sequelize'); +const env = process.env.NODE_ENV || 'development'; +const path = require('path') +const config = require(path.join(__dirname, 'config', 'config.json'))[env]; +const Sequelize = require('sequelize'); + const Op = Sequelize.Op; -const operatorsAliases = { +config.operatorsAliases = { $eq: Op.eq, $and: Op.and, $or: Op.or, $like: Op.like, $between: Op.between, - $in: Op.in + $in: Op.in, + $gt: Op.gt, + $gte: Op.gte, + $lt: Op.lt, + $lte: Op.lte, + $ne: Op.ne }; -sequelize = new Sequelize( - 'test_code_gen', - 'test_code_gen', - 'test_code_gen', - { - dialect: 'postgres', - host: '127.0.0.1' - }, - {operatorsAliases} -); +sequelize = new Sequelize(config); module.exports = sequelize; diff --git a/migrateDbAndStartServer.sh b/migrateDbAndStartServer.sh new file mode 100755 index 0000000..5d58e45 --- /dev/null +++ b/migrateDbAndStartServer.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +# Wait until the relational database-server up and running +waited=0 +until node ./utils/testSequelizeDbServerAvailable.js +do + if [ $waited == 240 ]; then + echo -e '\nERROR: Time out reached while waiting for relational database server to be available.\n' + exit 1 + fi + sleep 2 + waited=$(expr $waited + 2) +done + +# Run the migrations +if ! ./node_modules/.bin/sequelize db:migrate; then + echo -e '\nERROR: Migrating the relational database(s) caused an error.\n' + exit 1 +fi + +# Run seeders if needed +if [ -d ./seeders ]; then + if ! ./node_modules/.bin/sequelize db:seed:all; then + echo -e '\nERROR: Seeding the relational database(s) caused an error.\n' + exit 1 + fi +fi + +# Start GraphQL-server +npm start 2> /usr/src/app/error_server.log # acl diff --git a/models_index.js b/models_index.js new file mode 100644 index 0000000..fbb8d51 --- /dev/null +++ b/models_index.js @@ -0,0 +1,64 @@ +const fs = require('fs'); +const path = require('path'); +const Sequelize = require('sequelize'); +sequelize = require('./connection'); + +var models = {}; +module.exports = models; + +// ********************************************************************************** +// IMPORT SEQUEILIZE MODELS + +//grabs all the models in your models folder, adds them to the models object +fs.readdirSync("./models") + .filter(function(file) { + return (file.indexOf('.') !== 0) && (file !== 'index.js') && (file.slice(-3) === '.js'); + }) + .forEach(function(file) { + console.log(file); + let model_file = require(path.join(__dirname,'models', file)); + let model = model_file.init(sequelize, Sequelize); + + + let validator_patch = path.join('./validations', file); + if(fs.existsSync(validator_patch)){ + model = require(`./${validator_patch}`).validator_patch(model); + } + + let patches_patch = path.join('./patches', file); + if(fs.existsSync(patches_patch)){ + model = require(`./${patches_patch}`).logic_patch(model); + } + + if(models[model.name]) + throw Error(`Duplicated model name ${model.name}`); + + models[model.name] = model; + + }); +//Important: creates associations based on associations defined in associate function in the model files +Object.keys(models).forEach(function(modelName) { + if (models[modelName].associate) { + models[modelName].associate(models); + } +}); +//update tables with association (temporary, just for testing purposes) +//this part is suppose to be done in the migration file +//sequelize.sync({force: true}); + + +// ********************************************************************************** +// IMPORT WEBSERVICES + +fs.readdirSync("./models-webservice") + .filter(function(file) { + return (file.indexOf('.') !== 0) && (file !== 'index.js') && (file.slice(-3) === '.js'); + }) + .forEach(function(file) { + let model = require(`./${path.join("./models-webservice", file)}`); + + if(models[model.name]) + throw Error(`Duplicated model name ${model.name}`); + + models[model.name] = model; + }); diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 0ff79f5..0000000 --- a/package-lock.json +++ /dev/null @@ -1,2227 +0,0 @@ -{ - "name": "second-server-graphql", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@types/geojson": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-1.0.6.tgz", - "integrity": "sha512-Xqg/lIZMrUd0VRmSRbCAewtwGZiAk3mEUDvV4op1tGl+LvyPcb/MIOSxTl9z+9+J+R4/vpjiCAT4xeKzH9ji1w==" - }, - "@types/node": { - "version": "9.4.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-9.4.6.tgz", - "integrity": "sha512-CTUtLb6WqCCgp6P59QintjHWqzf4VL1uPA27bipLAPxFqrtK1gEYllePzTICGqQ8rYsCbpnsNypXjjDzGAAjEQ==" - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "accepts": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", - "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", - "requires": { - "mime-types": "2.1.18", - "negotiator": "0.6.1" - } - }, - "acl": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/acl/-/acl-0.4.11.tgz", - "integrity": "sha1-ACzHZuvyXNqP5TK1bzZRvr2yWzo=", - "requires": { - "async": "2.6.0", - "bluebird": "3.5.1", - "lodash": "4.17.5", - "mongodb": "2.2.35", - "redis": "2.8.0" - } - }, - "adler-32": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.2.0.tgz", - "integrity": "sha1-aj5r8KY5ALoVZSgIyxXGgT0aXyU=", - "requires": { - "exit-on-epipe": "1.0.1", - "printj": "1.1.2" - } - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" - }, - "async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", - "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", - "requires": { - "lodash": "4.17.5" - } - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "requires": { - "core-js": "2.5.5", - "regenerator-runtime": "0.11.1" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "base64url": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz", - "integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs=" - }, - "bcryptjs": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", - "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" - }, - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" - }, - "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", - "requires": { - "bytes": "3.0.0", - "content-type": "1.0.4", - "debug": "2.6.9", - "depd": "1.1.2", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "on-finished": "2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "1.6.16" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "1.0.0", - "concat-map": "0.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "bson": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/bson/-/bson-1.0.6.tgz", - "integrity": "sha512-D8zmlb46xfuK2gGvKmUjIklQEouN2nQ0LEHHeZ/NoHM2LDiMk2EYzZ5Ntw/Urk+bgMDosOZxaRzXxvhI5TcAVQ==" - }, - "buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" - }, - "buffer-shims": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", - "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=" - }, - "buffer-writer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-1.0.1.tgz", - "integrity": "sha1-Iqk2kB4wKa/NdUfrRIfOtpejvwg=" - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" - }, - "busboy": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", - "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", - "requires": { - "dicer": "0.2.5", - "readable-stream": "1.1.14" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } - } - }, - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" - }, - "cfb": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.0.7.tgz", - "integrity": "sha512-KjjZFR+a/e8RDdDTr4PwR0P/HIFRI3sxArFQttml0pFkhIO4TnvS/1+dqtGXPqe5/0MHp2IzjFx1JTzmohHT+w==", - "requires": { - "commander": "2.15.1", - "printj": "1.1.2" - } - }, - "chai": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", - "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", - "requires": { - "assertion-error": "1.1.0", - "check-error": "1.0.2", - "deep-eql": "3.0.1", - "get-func-name": "2.0.0", - "pathval": "1.1.0", - "type-detect": "4.0.8" - } - }, - "charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" - }, - "cli-color": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-1.2.0.tgz", - "integrity": "sha1-OlrnT9drYmevZm5p4q+70B3vNNE=", - "requires": { - "ansi-regex": "2.1.1", - "d": "1.0.0", - "es5-ext": "0.10.42", - "es6-iterator": "2.0.3", - "memoizee": "0.4.12", - "timers-ext": "0.1.5" - } - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1", - "wrap-ansi": "2.1.0" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - } - } - }, - "cls-bluebird": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cls-bluebird/-/cls-bluebird-2.1.0.tgz", - "integrity": "sha1-N+8eCAqP+1XC9BZPU28ZGeeWiu4=", - "requires": { - "is-bluebird": "1.0.2", - "shimmer": "1.2.0" - } - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, - "codepage": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.12.2.tgz", - "integrity": "sha512-FAN+oPs/ocaPLFvIt4vEOHgWA6UJ6t+fVbbVBoXDpTpC+4JYasomYZEEjR/Miph3qQrVnIShRwwmwu4P35JW1w==", - "requires": { - "commander": "2.14.1", - "exit-on-epipe": "1.0.1" - }, - "dependencies": { - "commander": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", - "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==" - } - } - }, - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "requires": { - "delayed-stream": "1.0.0" - } - }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" - }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "config-chain": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz", - "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=", - "requires": { - "ini": "1.3.5", - "proto-list": "1.2.4" - } - }, - "content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "cookiejar": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.1.tgz", - "integrity": "sha1-Qa1XsbVVlR7BcUEqgZQrHoIA00o=" - }, - "core-js": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.5.tgz", - "integrity": "sha1-sU3ek2xkDAV5prUMq8wTLdYSfjs=" - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "cors": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", - "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", - "requires": { - "object-assign": "4.1.1", - "vary": "1.1.2" - } - }, - "crc-32": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.0.tgz", - "integrity": "sha512-1uBwHxF+Y/4yF5G48fwnKq6QsIXheor3ZLPT80yGBV1oEUwpPojlEhQbWKVw1VwcTQyMGHK1/XMmTjmlsmTTGA==", - "requires": { - "exit-on-epipe": "1.0.1", - "printj": "1.1.2" - } - }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "requires": { - "lru-cache": "4.1.2", - "shebang-command": "1.2.0", - "which": "1.3.0" - }, - "dependencies": { - "lru-cache": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", - "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", - "requires": { - "pseudomap": "1.0.2", - "yallist": "2.1.2" - } - } - } - }, - "crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" - }, - "csv-parse": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-2.2.0.tgz", - "integrity": "sha512-nNXh61kEIUbTXPWPZbrKlkkylh7BDxffDUWQPWIho+Rog4XWRV8bTR8ZVo8qngzAwbhlvtKFcsaf2hGDV1iF8Q==" - }, - "d": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", - "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", - "requires": { - "es5-ext": "0.10.42" - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" - }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "requires": { - "type-detect": "4.0.8" - } - }, - "deepmerge": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.1.0.tgz", - "integrity": "sha512-Q89Z26KAfA3lpPGhbF6XMfYAm3jIV3avViy6KOJ2JLzFbeWHOvPQUu5aSJIWXap3gDZC2y1eF5HXEPI2wGqgvw==" - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "dicer": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", - "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", - "requires": { - "readable-stream": "1.1.14", - "streamsearch": "0.1.2" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "0.0.1", - "string_decoder": "0.10.31" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" - } - } - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "dottie": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.0.tgz", - "integrity": "sha1-2hkZgci41xPKARXViYzzl8Lw3dA=" - }, - "double-ended-queue": { - "version": "2.1.0-0", - "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", - "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" - }, - "ecdsa-sig-formatter": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.9.tgz", - "integrity": "sha1-S8kmJ07Dtau1AW5+HWCSGsJisqE=", - "requires": { - "base64url": "2.0.0", - "safe-buffer": "5.1.1" - } - }, - "editorconfig": { - "version": "0.13.3", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.13.3.tgz", - "integrity": "sha512-WkjsUNVCu+ITKDj73QDvi0trvpdDWdkDyHybDGSXPfekLCqwmpD7CP7iPbvBgosNuLcI96XTDwNa75JyFl7tEQ==", - "requires": { - "bluebird": "3.5.1", - "commander": "2.15.1", - "lru-cache": "3.2.0", - "semver": "5.5.0", - "sigmund": "1.0.1" - }, - "dependencies": { - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" - } - } - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" - }, - "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "requires": { - "is-arrayish": "0.2.1" - } - }, - "es5-ext": { - "version": "0.10.42", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.42.tgz", - "integrity": "sha512-AJxO1rmPe1bDEfSR6TJ/FgMFYuTBhR5R57KW58iCkYACMyFbrkqVyzXSurYoScDGvgyMpk7uRF/lPUPPTmsRSA==", - "requires": { - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1", - "next-tick": "1.0.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.42", - "es6-symbol": "3.1.1" - } - }, - "es6-promise": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", - "integrity": "sha1-7FYjOGgDKQkgcXDDlEjiREndH8Q=" - }, - "es6-symbol": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", - "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.42" - } - }, - "es6-weak-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", - "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.42", - "es6-iterator": "2.0.3", - "es6-symbol": "3.1.1" - } - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.42" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "requires": { - "cross-spawn": "5.1.0", - "get-stream": "3.0.0", - "is-stream": "1.1.0", - "npm-run-path": "2.0.2", - "p-finally": "1.0.0", - "signal-exit": "3.0.2", - "strip-eof": "1.0.0" - } - }, - "exit-on-epipe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", - "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==" - }, - "express": { - "version": "4.16.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", - "integrity": "sha1-41xt/i1kt9ygpc1PIXgb4ymeB2w=", - "requires": { - "accepts": "1.3.5", - "array-flatten": "1.1.1", - "body-parser": "1.18.2", - "content-disposition": "0.5.2", - "content-type": "1.0.4", - "cookie": "0.3.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "1.1.2", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", - "finalhandler": "1.1.0", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "1.1.2", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "path-to-regexp": "0.1.7", - "proxy-addr": "2.0.3", - "qs": "6.5.1", - "range-parser": "1.2.0", - "safe-buffer": "5.1.1", - "send": "0.16.1", - "serve-static": "1.13.1", - "setprototypeof": "1.1.0", - "statuses": "1.3.1", - "type-is": "1.6.16", - "utils-merge": "1.0.1", - "vary": "1.1.2" - } - }, - "express-fileupload": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/express-fileupload/-/express-fileupload-0.4.0.tgz", - "integrity": "sha512-jPv3aCdTIdQrGAUXQ1e1hU0Vnl+0jE9IbzEsI7VRIevQybrUrIMUgvwNwBThnsetandW8+9ICgflAkhKwLUuLw==", - "requires": { - "busboy": "0.2.14", - "fs-extra": "4.0.3", - "md5": "2.2.1", - "streamifier": "0.1.1" - }, - "dependencies": { - "fs-extra": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", - "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", - "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "4.0.0", - "universalify": "0.1.1" - } - } - } - }, - "express-graphql": { - "version": "0.6.12", - "resolved": "https://registry.npmjs.org/express-graphql/-/express-graphql-0.6.12.tgz", - "integrity": "sha512-ouLWV0hRw4hnaLtXzzwhdC79ewxKbY2PRvm05mPc/zOH5W5WVCHDQ1SmNxEPBQdUeeSNh29aIqW9zEQkA3kMuA==", - "requires": { - "accepts": "1.3.5", - "content-type": "1.0.4", - "http-errors": "1.6.2", - "raw-body": "2.3.2" - } - }, - "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" - }, - "faker": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/faker/-/faker-4.1.0.tgz", - "integrity": "sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8=" - }, - "finalhandler": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", - "integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=", - "requires": { - "debug": "2.6.9", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "on-finished": "2.3.0", - "parseurl": "1.3.2", - "statuses": "1.3.1", - "unpipe": "1.0.0" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "requires": { - "locate-path": "2.0.0" - } - }, - "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" - } - }, - "formidable": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", - "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==" - }, - "forwarded": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" - }, - "frac": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", - "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "fs-extra": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", - "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", - "requires": { - "graceful-fs": "4.1.11", - "jsonfile": "4.0.0", - "universalify": "0.1.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "generic-pool": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.4.2.tgz", - "integrity": "sha512-H7cUpwCQSiJmAHM4c/aFu6fUfrhWXW1ncyh8ftxEPMu6AiYkHw9K8br720TGPZJbk5eOH2bynjZD1yPvdDAmag==" - }, - "get-caller-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=" - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=" - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "requires": { - "fs.realpath": "1.0.0", - "inflight": "1.0.6", - "inherits": "2.0.3", - "minimatch": "3.0.4", - "once": "1.4.0", - "path-is-absolute": "1.0.1" - } - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" - }, - "graphql": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.13.1.tgz", - "integrity": "sha512-awNp3LTrQ7dJDSX3p3PBuxNDC+WFSOrWeV6+l4Xeh2PQJVOFyQ9SZPonXRz2WZc7aIxLZsf2nDZuuuc0qyEq/A==", - "requires": { - "iterall": "1.2.2" - } - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, - "hosted-git-info": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", - "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==" - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": "1.3.1" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" - } - } - }, - "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" - }, - "inflection": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.12.0.tgz", - "integrity": "sha1-ogCTVlbW9fa8TcdQLhrstwMihBY=" - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "1.4.0", - "wrappy": "1.0.2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" - }, - "ipaddr.js": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", - "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-bluebird": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-bluebird/-/is-bluebird-1.0.2.tgz", - "integrity": "sha1-CWQ5Bg9KpBGr7hkUOoTWpVNG1uI=" - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" - }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "requires": { - "builtin-modules": "1.1.1" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "1.0.1" - } - }, - "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", - "requires": { - "is-extglob": "2.1.1" - } - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "iterall": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.2.2.tgz", - "integrity": "sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA==" - }, - "js-beautify": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.7.5.tgz", - "integrity": "sha512-9OhfAqGOrD7hoQBLJMTA+BKuKmoEtTJXzZ7WDF/9gvjtey1koVLuZqIY6c51aPDjbNdNtIXAkiWKVhziawE9Og==", - "requires": { - "config-chain": "1.1.11", - "editorconfig": "0.13.3", - "mkdirp": "0.5.1", - "nopt": "3.0.6" - } - }, - "js-string-escape": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", - "integrity": "sha1-4mJbrbwNZ8dTPp7cEGjFh65BN+8=" - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "requires": { - "graceful-fs": "4.1.11" - } - }, - "jsonwebtoken": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.2.0.tgz", - "integrity": "sha512-1Wxh8ADP3cNyPl8tZ95WtraHXCAyXupgc0AhMHjU9er98BV+UcKsO7OJUjfhIu0Uba9A40n1oSx8dbJYrm+EoQ==", - "requires": { - "jws": "3.1.4", - "lodash.includes": "4.3.0", - "lodash.isboolean": "3.0.3", - "lodash.isinteger": "4.0.4", - "lodash.isnumber": "3.0.3", - "lodash.isplainobject": "4.0.6", - "lodash.isstring": "4.0.1", - "lodash.once": "4.1.1", - "ms": "2.1.1", - "xtend": "4.0.1" - }, - "dependencies": { - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" - } - } - }, - "jwa": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.5.tgz", - "integrity": "sha1-oFUs4CIHQs1S4VN3SjKQXDDnVuU=", - "requires": { - "base64url": "2.0.0", - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.9", - "safe-buffer": "5.1.1" - } - }, - "jws": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.1.4.tgz", - "integrity": "sha1-+ei5M46KhHJ31kRLFGT2GIDgUKI=", - "requires": { - "base64url": "2.0.0", - "jwa": "1.1.5", - "safe-buffer": "5.1.1" - } - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "requires": { - "invert-kv": "1.0.0" - } - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "requires": { - "graceful-fs": "4.1.11", - "parse-json": "2.2.0", - "pify": "2.3.0", - "strip-bom": "3.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "requires": { - "p-locate": "2.0.0", - "path-exists": "3.0.0" - } - }, - "lodash": { - "version": "4.17.5", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", - "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" - }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8=" - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=" - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w=" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=" - }, - "lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" - }, - "lru-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-3.2.0.tgz", - "integrity": "sha1-cXibO39Tmb7IVl3aOKow0qCX7+4=", - "requires": { - "pseudomap": "1.0.2" - } - }, - "lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", - "requires": { - "es5-ext": "0.10.42" - } - }, - "md5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", - "integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=", - "requires": { - "charenc": "0.0.2", - "crypt": "0.0.2", - "is-buffer": "1.1.6" - } - }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" - }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "requires": { - "mimic-fn": "1.2.0" - } - }, - "memoizee": { - "version": "0.4.12", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.12.tgz", - "integrity": "sha512-sprBu6nwxBWBvBOh5v2jcsGqiGLlL2xr2dLub3vR8dnE8YB17omwtm/0NSHl8jjNbcsJd5GMWJAnTSVe/O0Wfg==", - "requires": { - "d": "1.0.0", - "es5-ext": "0.10.42", - "es6-weak-map": "2.0.2", - "event-emitter": "0.3.5", - "is-promise": "2.1.0", - "lru-queue": "0.1.0", - "next-tick": "1.0.0", - "timers-ext": "0.1.5" - } - }, - "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "merge-graphql-schemas": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/merge-graphql-schemas/-/merge-graphql-schemas-1.5.1.tgz", - "integrity": "sha512-J5+FVjI7IaMXGNNLYotSGGbHmHb4Vd2KjxB45QsO5B6tSc81bJcilbtdx30fkZv7jIs7u0F8jcf7C0KfcSDBFA==", - "requires": { - "deepmerge": "2.1.0", - "glob": "7.1.2", - "is-glob": "4.0.0" - } - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" - }, - "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" - }, - "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" - }, - "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "requires": { - "mime-db": "1.33.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "1.1.11" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - } - }, - "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "dev": true, - "requires": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "moment": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.21.0.tgz", - "integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ==" - }, - "moment-timezone": { - "version": "0.5.14", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.14.tgz", - "integrity": "sha1-TrOP+VOLgBCLpGekWPPtQmjM/LE=", - "requires": { - "moment": "2.21.0" - } - }, - "mongodb": { - "version": "2.2.35", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-2.2.35.tgz", - "integrity": "sha512-3HGLucDg/8EeYMin3k+nFWChTA85hcYDCw1lPsWR6yV9A6RgKb24BkLiZ9ySZR+S0nfBjWoIUS7cyV6ceGx5Gg==", - "requires": { - "es6-promise": "3.2.1", - "mongodb-core": "2.1.19", - "readable-stream": "2.2.7" - } - }, - "mongodb-core": { - "version": "2.1.19", - "resolved": "https://registry.npmjs.org/mongodb-core/-/mongodb-core-2.1.19.tgz", - "integrity": "sha512-Jt4AtWUkpuW03kRdYGxga4O65O1UHlFfvvInslEfLlGi+zDMxbBe3J2NVmN9qPJ957Mn6Iz0UpMtV80cmxCVxw==", - "requires": { - "bson": "1.0.6", - "require_optional": "1.0.1" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "negotiator": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", - "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" - }, - "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" - }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "requires": { - "abbrev": "1.1.1" - } - }, - "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "requires": { - "hosted-git-info": "2.6.0", - "is-builtin-module": "1.0.0", - "semver": "4.3.2", - "validate-npm-package-license": "3.0.3" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "requires": { - "path-key": "2.0.1" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1.0.2" - } - }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "requires": { - "execa": "0.7.0", - "lcid": "1.0.0", - "mem": "1.1.0" - } - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-limit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", - "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", - "requires": { - "p-try": "1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "requires": { - "p-limit": "1.2.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" - }, - "packet-reader": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-0.3.1.tgz", - "integrity": "sha1-zWLmCvjX/qinBexP+ZCHHEaHHyc=" - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "1.3.1" - } - }, - "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - }, - "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "requires": { - "pify": "2.3.0" - } - }, - "pathval": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", - "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=" - }, - "pg": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/pg/-/pg-7.4.1.tgz", - "integrity": "sha512-Pi5qYuXro5PAD9xXx8h7bFtmHgAQEG6/SCNyi7gS3rvb/ZQYDmxKchfB0zYtiSJNWq9iXTsYsHjrM+21eBcN1A==", - "requires": { - "buffer-writer": "1.0.1", - "js-string-escape": "1.0.1", - "packet-reader": "0.3.1", - "pg-connection-string": "0.1.3", - "pg-pool": "2.0.3", - "pg-types": "1.12.1", - "pgpass": "1.0.2", - "semver": "4.3.2" - } - }, - "pg-connection-string": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", - "integrity": "sha1-2hhHsglA5C7hSSvq9l1J2RskXfc=" - }, - "pg-hstore": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/pg-hstore/-/pg-hstore-2.3.2.tgz", - "integrity": "sha1-9+8FPnubiSrphq8vfL6GQy388k8=", - "requires": { - "underscore": "1.8.3" - } - }, - "pg-pool": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.3.tgz", - "integrity": "sha1-wCIDLIlJ8xKk+R+2QJzgQHa+Mlc=" - }, - "pg-types": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.12.1.tgz", - "integrity": "sha1-1kCH45A7WP+q0nnnWVxSIIoUw9I=", - "requires": { - "postgres-array": "1.0.2", - "postgres-bytea": "1.0.0", - "postgres-date": "1.0.3", - "postgres-interval": "1.1.1" - } - }, - "pgpass": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.2.tgz", - "integrity": "sha1-Knu0G2BltnkH6R2hsHwYR8h3swY=", - "requires": { - "split": "1.0.1" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "postgres-array": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.2.tgz", - "integrity": "sha1-jgsy6wO/d6XAp4UeBEHBaaJWojg=" - }, - "postgres-bytea": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", - "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" - }, - "postgres-date": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.3.tgz", - "integrity": "sha1-4tiXAu/bJY/52c7g/pG9BpdSV6g=" - }, - "postgres-interval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.1.1.tgz", - "integrity": "sha512-OkuCi9t/3CZmeQreutGgx/OVNv9MKHGIT5jH8KldQ4NLYXkvmT9nDVxEuCENlNwhlGPE374oA/xMqn05G49pHA==", - "requires": { - "xtend": "4.0.1" - } - }, - "printj": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz", - "integrity": "sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ==" - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" - }, - "proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" - }, - "proxy-addr": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", - "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", - "requires": { - "forwarded": "0.1.2", - "ipaddr.js": "1.6.0" - } - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" - }, - "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" - }, - "range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" - }, - "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", - "requires": { - "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", - "unpipe": "1.0.0" - } - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "requires": { - "load-json-file": "2.0.0", - "normalize-package-data": "2.4.0", - "path-type": "2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "requires": { - "find-up": "2.1.0", - "read-pkg": "2.0.0" - } - }, - "readable-stream": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz", - "integrity": "sha1-BwV6y+JGeyIELTb5jFrVBwVOlbE=", - "requires": { - "buffer-shims": "1.0.0", - "core-util-is": "1.0.2", - "inherits": "2.0.3", - "isarray": "1.0.0", - "process-nextick-args": "1.0.7", - "string_decoder": "1.0.3", - "util-deprecate": "1.0.2" - } - }, - "redis": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", - "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", - "requires": { - "double-ended-queue": "2.1.0-0", - "redis-commands": "1.3.5", - "redis-parser": "2.6.0" - } - }, - "redis-commands": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.5.tgz", - "integrity": "sha512-foGF8u6MXGFF++1TZVC6icGXuMYPftKXt1FBT2vrfU9ZATNtZJ8duRC5d1lEfE8hyVe3jhelHGB91oB7I6qLsA==" - }, - "redis-parser": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", - "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=" - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" - }, - "require_optional": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", - "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", - "requires": { - "resolve-from": "2.0.0", - "semver": "5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" - } - } - }, - "resolve": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.0.tgz", - "integrity": "sha512-QdgZ5bjR1WAlpLaO5yHepFvC+o3rCr6wpfE2tpJNMkXdulf2jKomQBdNRQITF3ZKHNlT71syG98yQP03gasgnA==", - "requires": { - "path-parse": "1.0.5" - } - }, - "resolve-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" - }, - "retry-as-promised": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-2.3.2.tgz", - "integrity": "sha1-zZdO5P2bX+A8vzGHHuSCIcB3N7c=", - "requires": { - "bluebird": "3.5.1", - "debug": "2.6.9" - } - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" - }, - "semver": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", - "integrity": "sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=" - }, - "send": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", - "integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", - "requires": { - "debug": "2.6.9", - "depd": "1.1.2", - "destroy": "1.0.4", - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "etag": "1.8.1", - "fresh": "0.5.2", - "http-errors": "1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "2.3.0", - "range-parser": "1.2.0", - "statuses": "1.3.1" - } - }, - "sequelize": { - "version": "4.35.2", - "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-4.35.2.tgz", - "integrity": "sha512-uK1vpgq4OkRdUG1pEGfeCkudQaoDqfX/hNICm7tqsDYHjADEU/E/Bn9+ib2JQkKhIcX4X4Sp1/88/lsofsXPzA==", - "requires": { - "bluebird": "3.5.1", - "cls-bluebird": "2.1.0", - "debug": "3.1.0", - "depd": "1.1.2", - "dottie": "2.0.0", - "generic-pool": "3.4.2", - "inflection": "1.12.0", - "lodash": "4.17.5", - "moment": "2.21.0", - "moment-timezone": "0.5.14", - "retry-as-promised": "2.3.2", - "semver": "5.5.0", - "terraformer-wkt-parser": "1.1.2", - "toposort-class": "1.0.1", - "uuid": "3.2.1", - "validator": "9.4.1", - "wkx": "0.4.4" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, - "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" - } - } - }, - "sequelize-cli": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-4.0.0.tgz", - "integrity": "sha1-TWQd+1iwNwq0QPc34bC/c38V7KU=", - "requires": { - "bluebird": "3.5.1", - "cli-color": "1.2.0", - "fs-extra": "5.0.0", - "js-beautify": "1.7.5", - "lodash": "4.17.5", - "resolve": "1.7.0", - "umzug": "2.1.0", - "yargs": "8.0.2" - } - }, - "serve-static": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", - "integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", - "requires": { - "encodeurl": "1.0.2", - "escape-html": "1.0.3", - "parseurl": "1.3.2", - "send": "0.16.1" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "requires": { - "shebang-regex": "1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "shimmer": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.0.tgz", - "integrity": "sha512-xTCx2vohXC2EWWDqY/zb4+5Mu28D+HYNSOuFzsyRDRvI/e1ICb69afwaUwfjr+25ZXldbOLyp+iDUZHq8UnTag==" - }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "spdx-correct": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", - "requires": { - "spdx-expression-parse": "3.0.0", - "spdx-license-ids": "3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "requires": { - "spdx-exceptions": "2.1.0", - "spdx-license-ids": "3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==" - }, - "split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "requires": { - "through": "2.3.8" - } - }, - "ssf": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.10.2.tgz", - "integrity": "sha512-rDhAPm9WyIsY8eZEKyE8Qsotb3j/wBdvMWBUsOhJdfhKGLfQidRjiBUV0y/MkyCLiXQ38FG6LWW/VYUtqlIDZQ==", - "requires": { - "frac": "1.1.2" - } - }, - "statuses": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", - "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" - }, - "streamifier": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/streamifier/-/streamifier-0.1.1.tgz", - "integrity": "sha1-l+mNj6TRBdYqJpHR3AfoINuN/E8=" - }, - "streamsearch": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", - "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { - "is-fullwidth-code-point": "2.0.0", - "strip-ansi": "4.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "3.0.0" - } - } - } - }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "5.1.1" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "2.1.1" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" - }, - "superagent": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.2.tgz", - "integrity": "sha512-gVH4QfYHcY3P0f/BZzavLreHW3T1v7hG9B+hpMQotGQqurOvhv87GcMCd6LWySmBuf+BDR44TQd0aISjVHLeNQ==", - "requires": { - "component-emitter": "1.2.1", - "cookiejar": "2.1.1", - "debug": "3.1.0", - "extend": "3.0.1", - "form-data": "2.3.2", - "formidable": "1.2.1", - "methods": "1.1.2", - "mime": "1.4.1", - "qs": "6.5.1", - "readable-stream": "2.2.7" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - } - } - }, - "supertest": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-3.1.0.tgz", - "integrity": "sha512-O44AMnmJqx294uJQjfUmEyYOg7d9mylNFsMw/Wkz4evKd1njyPrtCN+U6ZIC7sKtfEVQhfTqFFijlXx8KP/Czw==", - "requires": { - "methods": "1.1.2", - "superagent": "3.8.2" - } - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "3.0.0" - } - }, - "terraformer": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/terraformer/-/terraformer-1.0.8.tgz", - "integrity": "sha1-UeCtiXRvzyFh3G9lqnDkI3fItZM=", - "requires": { - "@types/geojson": "1.0.6" - } - }, - "terraformer-wkt-parser": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/terraformer-wkt-parser/-/terraformer-wkt-parser-1.1.2.tgz", - "integrity": "sha1-M2oMj8gglKWv+DKI9prt7NNpvww=", - "requires": { - "terraformer": "1.0.8" - } - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "timers-ext": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.5.tgz", - "integrity": "sha512-tsEStd7kmACHENhsUPaxb8Jf8/+GZZxyNFQbZD07HQOyooOa6At1rQqjffgvg7n+dxscQa9cjjMdWhJtsP2sxg==", - "requires": { - "es5-ext": "0.10.42", - "next-tick": "1.0.0" - } - }, - "toposort-class": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz", - "integrity": "sha1-f/0feMi+KMO6Rc1OGj9e4ZO9mYg=" - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" - }, - "type-is": { - "version": "1.6.16", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "2.1.18" - } - }, - "umzug": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/umzug/-/umzug-2.1.0.tgz", - "integrity": "sha512-BgT+ekpItEWaG+3JjLLj6yVTxw2wIH8Cr6JyKYIzukWAx9nzGhC6BGHb/IRMjpobMM1qtIrReATwLUjKpU2iOQ==", - "requires": { - "babel-runtime": "6.26.0", - "bluebird": "3.5.1", - "lodash": "4.17.5", - "resolve": "1.7.0" - } - }, - "underscore": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", - "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=" - }, - "universalify": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.1.tgz", - "integrity": "sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc=" - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" - }, - "validate-npm-package-license": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", - "requires": { - "spdx-correct": "3.0.0", - "spdx-expression-parse": "3.0.0" - } - }, - "validator": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/validator/-/validator-9.4.1.tgz", - "integrity": "sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA==" - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - }, - "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", - "requires": { - "isexe": "2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" - }, - "wkx": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.4.4.tgz", - "integrity": "sha512-eVVHka2jRaAp9QanKhLpxWs3AGDV0b8cijlavxBnn4ryXzq5N/3Xe3nkQsI0XMRA16RURwviCWuOCj4mXCmrxw==", - "requires": { - "@types/node": "9.4.6" - } - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "requires": { - "string-width": "1.0.2", - "strip-ansi": "3.0.1" - }, - "dependencies": { - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "1.1.0", - "is-fullwidth-code-point": "1.0.0", - "strip-ansi": "3.0.1" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "xlsx": { - "version": "0.12.11", - "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.12.11.tgz", - "integrity": "sha1-VIBteKe7ApmKuj1ZxJmVpvC+AB4=", - "requires": { - "adler-32": "1.2.0", - "cfb": "1.0.7", - "codepage": "1.12.2", - "commander": "2.14.1", - "crc-32": "1.2.0", - "exit-on-epipe": "1.0.1", - "ssf": "0.10.2" - }, - "dependencies": { - "commander": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", - "integrity": "sha512-+YR16o3rK53SmWHU3rEM3tPAh2rwb1yPcQX5irVn7mb0gXbwuCCrnkbV5+PBfETdfg1vui07nM6PCG1zndcjQw==" - } - } - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - }, - "yargs": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", - "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", - "requires": { - "camelcase": "4.1.0", - "cliui": "3.2.0", - "decamelize": "1.2.0", - "get-caller-file": "1.0.2", - "os-locale": "2.1.0", - "read-pkg-up": "2.0.0", - "require-directory": "2.1.1", - "require-main-filename": "1.0.1", - "set-blocking": "2.0.0", - "string-width": "2.1.1", - "which-module": "2.0.0", - "y18n": "3.2.1", - "yargs-parser": "7.0.0" - } - }, - "yargs-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", - "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", - "requires": { - "camelcase": "4.1.0" - } - } - } -} diff --git a/package.json b/package.json index a1488db..8857665 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,17 @@ "description": "Schema as string", "main": "index.js", "scripts": { + "start": "node server.js", "test": "mocha test/test.js --exit" }, "author": "vsuaste", "license": "ISC", "dependencies": { "acl": "^0.4.11", + "adm-zip": "^0.4.13", + "ajv": "^6.10.0", + "awaitify-stream": "^1.0.2", + "bcrypt": "^3.0.3", "bcryptjs": "^2.4.3", "chai": "^4.1.2", "cors": "^2.8.4", @@ -17,19 +22,30 @@ "express": "^4.16.2", "express-fileupload": "^0.4.0", "express-graphql": "^0.6.12", + "express-jwt": "^5.3.1", "faker": "^4.1.0", "graphql": "^0.13.1", + "graphql-iso-date": "^3.6.1", "jsonwebtoken": "^8.2.0", + "linked-list": "^2.0.0", "lodash": "^4.17.5", + "mathjs": "^5.2.0", "merge-graphql-schemas": "^1.5.1", + "mysql2": "^1.6.4", + "nodemailer": "^5.1.1", + "nodemailer-smtp-transport": "^2.7.4", "pg": "^7.4.1", "pg-hstore": "^2.3.2", "sequelize": "^4.35.2", "sequelize-cli": "^4.0.0", "supertest": "^3.1.0", + "sync-request": "^6.0.0", + "uuidv4": "^2.0.0", "xlsx": "^0.12.11" }, "devDependencies": { - "mocha": "^5.2.0" + "mocha": "^5.2.0", + "axios": "^0.18.0", + "form-data": "^2.3.2-rc1" } } diff --git a/server.js b/server.js index 3d1706c..0620fdb 100644 --- a/server.js +++ b/server.js @@ -1,61 +1,171 @@ var express = require('express'); var path = require('path'); var graphqlHTTP = require('express-graphql'); + var jwt = require('express-jwt'); const fileUpload = require('express-fileupload'); - var {buildSchema} = require('graphql'); + const auth = require('./utils/login'); + const bodyParser = require('body-parser'); + const globals = require('./config/globals'); + const JOIN = require('./utils/join-models'); + const simpleExport = require('./utils/simple-export'); + const {GraphQLDateTime, GraphQLDate, GraphQLTime } = require('graphql-iso-date'); + + var { + buildSchema + } = require('graphql'); var mergeSchema = require('./utils/merge-schemas'); var acl = null; var cors = require('cors'); + + /* Server */ + const APP_PORT = globals.PORT; + const app = express(); + + app.use((req, res, next)=> { + + // Website you wish to allow to connect + res.setHeader('Access-Control-Allow-Origin', globals.ALLOW_ORIGIN); + //res.setHeader('Access-Control-Expose-Headers', 'Access-Control-Allow-Origin'); + + // Request methods you wish to allow + //res.setHeader('Access-Control-Allow-Methods', + // 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); + + // Request headers you wish to allow + //res.setHeader('Access-Control-Allow-Headers', + // 'X-Requested-With,content-type,authorization,Authorization,accept,Accept'); + next(); + }); + + // Force users to sign in to get access to anything else than '/login' + console.log("REQUIRE: ",globals.REQUIRE_SIGN_IN); + if(globals.REQUIRE_SIGN_IN === "true"){ + app.use(jwt({ secret: 'something-secret'}).unless({path: ['/login']})); + } + + /* Temporary solution: acl rules set */ - if(process.argv.length > 2 && process.argv[2]=='acl') - { + if (process.argv.length > 2 && process.argv[2] == 'acl') { var node_acl = require('acl'); - var {aclRules} = require('./acl_rules'); + var { + aclRules + } = require('./acl_rules'); var acl = new node_acl(new node_acl.memoryBackend()); /* set authorization rules from file acl_rules.js */ acl.allow(aclRules); console.log("Authoization rules set!"); - /*For testing purposes*/ - acl.addUserRoles(1, 'guest'); - acl.addUserRoles(2, 'administrator'); -}else{ - console.log("Open server, no authorization rules"); -} + } else { + console.log("Open server, no authorization rules"); + } /* Schema */ -var merged_schema = mergeSchema( path.join(__dirname, './schemas')); -var Schema = buildSchema(merged_schema); + console.log('Merging Schema'); + var merged_schema = mergeSchema(path.join(__dirname, './schemas')); + console.log(merged_schema); + var Schema = buildSchema(merged_schema); + /*set scalar types for dates */ + Object.assign(Schema._typeMap.DateTime, GraphQLDateTime); + Object.assign(Schema._typeMap.Date, GraphQLDate); + Object.assign(Schema._typeMap.Time, GraphQLTime); -/* Resolvers*/ -var resolvers = require('./resolvers/index'); + /* Resolvers*/ + var resolvers = require('./resolvers/index'); + + + + +app.use(bodyParser.urlencoded({ extended: false })); +app.use(bodyParser.json()); + +app.use('/login', cors(), (req, res)=>{ + + auth.login(req.body).then( (token) =>{ + res.json({token: token}); + }).catch((err) =>{ + console.log(err); + res.status(500).send({error: "Wrong email or password. Please check your credentials."}) + }); + +}); - /* Server */ - const APP_PORT = 3000; - const app = express(); - //app.use((req, res, next)=> { - // Website you wish to allow to connect - //res.setHeader('Access-Control-Allow-Origin', '*'); - //res.setHeader('Access-Control-Expose-Headers', 'Access-Control-Allow-Origin'); - // Request methods you wish to allow - //res.setHeader('Access-Control-Allow-Methods', - // 'GET, POST, OPTIONS, PUT, PATCH, DELETE'); +app.use('/join', cors(), (req, res) => { - // Request headers you wish to allow - //res.setHeader('Access-Control-Allow-Headers', - // 'X-Requested-With,content-type,authorization,Authorization,accept,Accept'); - // next(); - //}); + // check if the Content-Type is in JSON so that bodyParser can be applied automatically + if (!req.is('application/json')) + return res.status(415).send({error: "JSON Content-Type expected"}); -app.use(fileUpload()); - /*request is passed as context by default */ - app.use('/graphql', cors(),graphqlHTTP((req)=> ({ + let context = { + request: req, + acl: acl + }; + + // select the output format + let params = req.body; + let joinModels = {}; + + if(params.outputFormat === 'TEST'){ + joinModels = new JOIN.JoinModels(context); + }else if(params.outputFormat === 'CSV'){ + joinModels = new JOIN.JoinModelsCSV(context); + }else if(params.outputFormat === 'JSON'){ + joinModels = new JOIN.JoinModelsJSON(context); + }else{ + return res.status(415).send({error: "outputFormat = TEST/CSV/JSON is required"}); + } + + // start data transmission + joinModels.run(req.body, res).then(() => { + res.end(); + }).catch(error => { + let formattedError = { + message: error.message, + details: error.originalError && error.originalError.errors ? error.originalError.errors : "", + path: error.path + }; + res.status(500).send(formattedError); + }); +}); + + + +app.use('/export', cors(), (req, res) =>{ + + let context = { + request: req, + acl : acl + } + + let body_info = req.query; + + simpleExport(context, body_info ,res).then( () =>{ + res.end(); + }).catch( error => { + let formattedError = { + message: error.message, + details: error.originalError && error.originalError.errors ? error.originalError.errors : "", + path: error.path + }; + res.status(500).send(formattedError); + }); + + +}); + + + + + + + app.use(fileUpload()); + /*request is passed as context by default */ + app.use('/graphql', cors(), graphqlHTTP((req) => ({ schema: Schema, rootValue: resolvers, pretty: true, @@ -63,12 +173,28 @@ app.use(fileUpload()); context: { request: req, acl: acl + }, + formatError(error){ + return { + message: error.message, + details: error.originalError && error.originalError.errors ? error.originalError.errors : "", + path: error.path + }; } }))); + // Error handling + app.use(function (err, req, res, next) { + if (err.name === 'UnauthorizedError') { // Send the error rather than to show it on the console + res.status(401).send(err); + } + else { + next(err); + } + }); -var server = app.listen(APP_PORT, ()=>{ + var server = app.listen(APP_PORT, () => { console.log(`App listening on port ${APP_PORT}`); }); -module.exports = server; + module.exports = server; diff --git a/test/test-data.js b/test/test-data.js deleted file mode 100644 index 47ac566..0000000 --- a/test/test-data.js +++ /dev/null @@ -1,60 +0,0 @@ - -module.exports.people = [ - { - "firstName": "Leonardo", - "lastName": "Da Vinci", - "email": "leonardo.vinci@art.com" - }, - { - "firstName": "Thomas", - "lastName": "Edison", - "email": "thomas.edison@science.com" - }, - { - "firstName": "Vicent", - "lastName": "van Gogh", - "email": "vicent.vanGogh@art.com" - }, - { - "firstName": "Albert", - "lastName": "Einstein", - "email": "albert.einstein@science.com" - }, - { - "firstName": "Ludwig", - "lastName": "Beethoven", - "email": "ludwig.beethoven@art.com" - } - ]; - -module.exports.searchPeopleByEmail = [ - { - "firstName": "Leonardo", - "lastName": "Da Vinci", - }, - { - "firstName": "Vicent", - "lastName": "van Gogh", - }, - { - "firstName": "Ludwig", - "lastName": "Beethoven", - } -]; - -module.exports.readOneBook = { - "title": "Science of music" - }; - - -module.exports.addDog = { - "name": "toto", - "breed": "chihuahua" - }; - -module.exports.deleteDog = "Item succesfully deleted"; - -module.exports.updateBook = { - "title": "Paintings II", - "genre": "Art" -}; diff --git a/test/test.js b/test/test.js deleted file mode 100644 index 58ca736..0000000 --- a/test/test.js +++ /dev/null @@ -1,97 +0,0 @@ -var server = require('../server'); -const request = require('supertest'); -const test = require('./test-data'); - -const chai = require('chai'); -const expect = chai.expect; - -describe('Testing Queries Server GraphQL', ()=>{ - it('Read all - people', (done)=>{ - request(server).post('/graphql') - .send({query: '{people{ firstName lastName email}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.people).to.deep.equal(test.people); - done(); - }); - - }); - - it('Search with filter - people by email', (done)=>{ - request(server).post('/graphql') - .send({query: '{ searchPerson(input:{field:email, value:{value:"%art%"}, operator:like}){ firstName lastName}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.searchPerson).to.deep.equal(test.searchPeopleByEmail); - done(); - }); - - }); - - it('Read one by Id - books', (done)=>{ - request(server).post('/graphql') - .send({query: '{readOneBook(id:3){title}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.readOneBook).to.deep.equal(test.readOneBook); - done(); - }); - }); -}); - -describe('Testing Mutations Server GraphQL', ()=>{ - it('Create one - dogs', (done)=>{ - request(server).post('/graphql') - .send({query: 'mutation{addDog(name:"toto", breed:"chihuahua"){name breed}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.addDog).to.deep.equal(test.addDog); - done(); - }); - }); - - it('Delete one - dogs', (done)=>{ - request(server).post('/graphql') - .send({query: 'mutation{deleteDog(id:6)}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.deleteDog).to.deep.equal(test.deleteDog); - done(); - }); - }); - - it('Update one - books', (done)=>{ - request(server).post('/graphql') - .send({query: 'mutation{updateBook(id:2, title:"Paintings II"){title genre}}'}) - .expect(200).end((err,res)=>{ - if(err){ - console.log(err); - done(err); - } - expect(res.body.data.updateBook).to.deep.equal(test.updateBook); - done(); - }); - }); - - -}); - - - - -server.close(); diff --git a/utils/check-authorization.js b/utils/check-authorization.js index 5fe8dbd..ae31364 100644 --- a/utils/check-authorization.js +++ b/utils/check-authorization.js @@ -2,27 +2,37 @@ const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const secret = 'something-secret'; -module.exports = function( context, resource, permission ) { + +/** + * @function - Given a context this function check if the user(implicit in context) + * is allowed to perform 'permission' action to the 'resource' model. + * + * @param {object} context context object contains the request info and the acl rules. + * @param {string} resource resource to which the user wants to perform an action (i.e. a model). + * @param {string} permission action that the user wants to perform to resourse (i.e. read, edit, create). + * @return {promise} it will resolve true if within the context the user is allowed to perform 'permission' action to the 'resource' model. + */ +module.exports = function( context, resource, permission ) { //if there's not authorization rules set - if (context.acl == null) return true; + if (context.acl == null) //return true; + { + return Promise.resolve(true); + } - let token = context.request.headers["authorization"]; + let token_bearer = context.request.headers["authorization"]; + let token = token_bearer.replace("Bearer ",""); + console.log("TOKEN",typeof token, token); try{ //Identify user from context let decoded = jwt.verify(token, secret); - //check for permissions of that specific user - let allowed = context.acl.isAllowed(decoded.id, resource, permission); - console.log(typeof decoded.id, ' * ' ,decoded.id); - if(allowed){ - return true; - }else{ - //no permission for user - console.log("Permission dennied..."); - return false; - } + + //check for permissions from specific roles + return context.acl.areAnyRolesAllowed(decoded.roles, resource, permission); }catch(err){ //invalid token console.log("invalid token..."); - return false; + console.log(err); + throw new Error(err); + //return false; } } diff --git a/utils/email.js b/utils/email.js new file mode 100644 index 0000000..ebd9d51 --- /dev/null +++ b/utils/email.js @@ -0,0 +1,31 @@ +const NodeMailer = require('nodemailer'); +const SmtpTransport = require('nodemailer-smtp-transport'); +const Globals = require('../config/globals'); +const path = require('path'); + +module.exports = { + sendEmail: function (dst_email, subj, message, att){ + console.log(`${dst_email}, ${message}, ${Globals.MAIL_ACCOUNT}, ${Globals.MAIL_PASSWORD}`); + + let transporter = NodeMailer.createTransport(SmtpTransport({ + service: Globals.MAIL_SERVICE, + host: Globals.MAIL_HOST, + auth: { + type: "login", + user: Globals.MAIL_ACCOUNT, + pass: Globals.MAIL_PASSWORD + } + })); + + let mailOptions = { + from: Globals.MAIL_ACCOUNT, + to: dst_email, + subject: subj, + text: message, + attachments: att + }; + + + return transporter.sendMail(mailOptions); + } +}; \ No newline at end of file diff --git a/utils/errors.js b/utils/errors.js new file mode 100644 index 0000000..f0eae8a --- /dev/null +++ b/utils/errors.js @@ -0,0 +1,18 @@ + +class customArrayError extends Error { + constructor(errors_array, message){ + super(); + this.message = message; + this.errors = errors_array; + } +} + +handleError = function(error){ + if(error.name === "SequelizeValidationError"){ + throw new customArrayError(error.errors, "Validation error"); + }else{ + throw new Error(error) + } +} + +module.exports = { handleError} diff --git a/utils/file-tools.js b/utils/file-tools.js index 931f00f..b34c313 100644 --- a/utils/file-tools.js +++ b/utils/file-tools.js @@ -1,7 +1,19 @@ const XLSX = require('xlsx'); const Promise = require('bluebird'); -const csv_parse = Promise.promisify(require('csv-parse')); +const promise_csv_parse = Promise.promisify(require('csv-parse')); +const csv_parse = require('csv-parse'); +const fs = require('fs'); +const awaitifyStream = require('awaitify-stream'); +const validatorUtil = require('./validatorUtil'); +const admZip = require('adm-zip'); + +/** + * replaceNullStringsWithLiteralNulls - Replace null entries of columns with literal null types + * + * @param {array} arrOfObjs Each item correponds to a column represented as object. + * @return {array} Each item corresponds to a column and all items have either a valid entry or null type. + */ replaceNullStringsWithLiteralNulls = function(arrOfObjs) { console.log(typeof arrOfObjs, arrOfObjs); return arrOfObjs.map(function(csvRow) { @@ -14,17 +26,33 @@ replaceNullStringsWithLiteralNulls = function(arrOfObjs) { }); } + +/** + * parseCsv - parse csv file (string) + * + * @param {string} csvStr Csv file converted to string. + * @param {string} delim Set the field delimiter in the csv file. One or multiple character. + * @param {array|boolean|function} cols Columns as in csv-parser options.(true if auto-discovered in the first CSV line). + * @return {array} Each item correponds to a column represented as object and filtered with replaceNullStringsWithLiteralNulls function. + */ exports.parseCsv = function(csvStr, delim, cols) { if (!delim) delim = "," if (typeof cols === 'undefined') cols = true return replaceNullStringsWithLiteralNulls( - csv_parse(csvStr, { + promise_csv_parse(csvStr, { delimiter: delim, columns: cols }) ) } + +/** + * parseXlsx - description + * + * @param {string} bstr Xlsx file converted to string + * @return {array} Each item correponds to a column represented as object and filtered with replaceNullStringsWithLiteralNulls function. + */ exports.parseXlsx = function(bstr) { var workbook = XLSX.read(bstr, { type: "binary" @@ -34,4 +62,167 @@ exports.parseXlsx = function(bstr) { XLSX.utils.sheet_to_json( workbook.Sheets[sheet_name_list[0]]) ); -} +}; + +/** + * Function that will delete a file if it exists and is insensitive to the + * case when a file not exist. + * + * @param {String} path - A path to the file + */ +exports.deleteIfExists = function(path) { + console.log(`Removing ${path}`); + fs.unlink(path, function(err) { + // file may be already deleted + }); +}; + +/** + * Function deletes properties that contain string values "NULL" or "null". + * + * @param {Object} pojo - A plain old JavaScript object. + * + * @return {Object} A modified clone of the argument pojo in which all String + * "NULL" or "null" values are deleted. + */ +exports.replacePojoNullValueWithLiteralNull = function(pojo) { + if (pojo === null || pojo === undefined) { + return null + } + let res = Object.assign({}, pojo); + Object.keys(res).forEach((k) => { + if (typeof res[k] === "string" && res[k].match(/\s*null\s*/i)) { + delete res[k]; + } + }); + return res +}; + + +/** + * Parse by streaming a csv file and create the records in the correspondant table + * @function + * @param {string} csvFilePath - The path where the csv file is stored. + * @param {object} model - Sequelize model, record will be created through this model. + * @param {string} delim - Set the field delimiter in the csv file. One or multiple character. + * @param {array|boolean|function} cols - Columns as in csv-parser options.(true if auto-discovered in the first CSV line). + */ +exports.parseCsvStream = async function(csvFilePath, model, delim, cols) { + + if (!delim) delim = ","; + if (typeof cols === 'undefined') cols = true; + console.log("TYPEOF", typeof model); + // Wrap all database actions within a transaction: + let transaction = await model.sequelize.transaction(); + + let addedFilePath = csvFilePath.substr(0, csvFilePath.lastIndexOf(".")) + + ".json"; + let addedZipFilePath = csvFilePath.substr(0, csvFilePath.lastIndexOf(".")) + + ".zip"; + + console.log(addedFilePath); + console.log(addedZipFilePath); + + try { + // Pipe a file read-stream through a CSV-Reader and handle records asynchronously: + let csvStream = awaitifyStream.createReader( + fs.createReadStream(csvFilePath).pipe( + csv_parse({ + delimiter: delim, + columns: cols, + cast: true + }) + ) + ); + + // Create an output file stream + let addedRecords = awaitifyStream.createWriter( + fs.createWriteStream(addedFilePath) + ); + + let record; + let errors = []; + + while (null !== (record = await csvStream.readAsync())) { + + console.log(record); + record = exports.replacePojoNullValueWithLiteralNull(record); + console.log(record); + + + try { + let result = await validatorUtil.ifHasValidatorFunctionInvoke( + 'validateForCreate', model, record); + console.log(result); + await model.create(record, { + transaction: transaction + }).then(created => { + + // this is async, here we just push new line into the parallel thread + // synchronization goes at endAsync; + addedRecords.writeAsync(`${JSON.stringify(created)}\n`); + + }).catch(error => { + console.log( + `Caught sequelize error during CSV batch upload: ${JSON.stringify(error)}` + ); + error.record = record; + errors.push(error); + }) + } catch (error) { + console.log( + `Validation error during CSV batch upload: ${JSON.stringify(error)}` + ); + error['record'] = record; + errors.push(error); + + } + + } + + // close the addedRecords file so it can be sent afterwards + await addedRecords.endAsync(); + + if (errors.length > 0) { + let message = + "Some records could not be submitted. No database changes has been applied.\n"; + message += "Please see the next list for details:\n"; + + errors.forEach(function(error) { + valErrMessages = error.errors.reduce((acc, val) => { + return acc.concat(val.dataPath).concat(" ").concat(val.message) + .concat(" ") + }) + message += + `record ${JSON.stringify(error.record)} ${error.message}: ${valErrMessages}; \n`; + }); + + throw new Error(message.slice(0, message.length - 1)); + } + + await transaction.commit(); + + // zip comitted data and return a corresponding file path + let zipper = new admZip(); + zipper.addLocalFile(addedFilePath); + await zipper.writeZip(addedZipFilePath); + + console.log(addedZipFilePath); + + // At this moment the parseCsvStream caller is responsible in deleting the + // addedZipFilePath + return addedZipFilePath; + + } catch (error) { + + await transaction.rollback(); + + exports.deleteIfExists(addedFilePath); + exports.deleteIfExists(addedZipFilePath); + + throw error; + + } finally { + exports.deleteIfExists(addedFilePath); + } +}; diff --git a/utils/graphql-sequelize-types.js b/utils/graphql-sequelize-types.js new file mode 100644 index 0000000..d10ab02 --- /dev/null +++ b/utils/graphql-sequelize-types.js @@ -0,0 +1,14 @@ +/* +Data types dictionary from graphql-type to sequelize-type +*/ + +module.exports = { + + "Int" : 'INTEGER', + "String": 'TEXT', + "Float": 'FLOAT', + "Boolean": 'BOOLEAN', + "Date": "DATEONLY", + "Time": "TIME", + "DateTime": "DATE" +} diff --git a/utils/graphql_schema.js b/utils/graphql_schema.js new file mode 100644 index 0000000..2837eb4 --- /dev/null +++ b/utils/graphql_schema.js @@ -0,0 +1,33 @@ +const path = require('path'); + +const { buildSchema } = require('graphql'); +const mergeSchema = require(path.join(__dirname,'../','utils','merge-schemas')); + +module.exports.schema = buildSchema(mergeSchema(path.join(__dirname,'../','./schemas'))); + +/** + * Each model deefined in it's *.json file can have a description field. However, we also add + * internal annotations within this filed. It is possible to search for fields of the given + * model with respect to the presence of an annotation inside of it's description. + * @function getModelFieldByAnnotation + * @param {string} model_name - The name of the data model to be explored. + * @param {string} annotation - An annotation name, for example "@original-field" + * @return {Array} - an array of model field names that contain a given annotation in it's + * description + */ +module.exports.getModelFieldByAnnotation = function(model_name, annotation){ + let schema = module.exports.schema; + + let model_type = schema.getType(model_name); + if(! schema.getType(model_name)) + throw new Error(`Model ${model_name} not exist`); + + let fields = model_type._fields; + + return Object.keys(fields).filter( a => { + let desc = fields[a].description; + if(desc) + return desc.toString().includes(annotation); + return false; + }) +}; \ No newline at end of file diff --git a/utils/helper.js b/utils/helper.js new file mode 100644 index 0000000..984b5cd --- /dev/null +++ b/utils/helper.js @@ -0,0 +1,243 @@ +const objectAssign = require('object-assign'); +const math = require('mathjs'); + + + /** + * paginate - Creates pagination argument as needed in sequelize cotaining limit and offset accordingly to the current + * page implicit in the request info. + * + * @param {object} req Request info. + * @return {object} Pagination argument. + */ + paginate = function(req) { + selectOpts = {} + if (req.query.per_page){ selectOpts['limit'] = req.query.per_page} + else{ selectOpts['limit'] = 20} + if (req.query.page) { + os = (req.query.page - 1) * selectOpts['limit'] + selectOpts['offset'] = os + } + return selectOpts + } + + + + /** + * requestedUrl - Recover baseUrl from the request. + * + * @param {object} req Request info. + * @return {string} baseUrl from request. + */ + requestedUrl = function(req) { + //console.log(req.port) + //console.log(req.headers.host) + //let port = req.port|| 2000; + return req.protocol + '://' + req.headers.host + + //(port == 80 || port == 443 ? '' : ':' + port) + + req.baseUrl; + } + + + + /** + * prevNextPageUrl - Creates request string for previous or next page int the vue-table data object. + * + * @param {object} req Request info. + * @param {boolean} isPrevious True if previous page is requestes and false if next page is requested. + * @return {string} String request for previous or next page int the vue-table data object. + */ + prevNextPageUrl = function(req, isPrevious) { + //console.log("Requested URL", req); + let baseUrl = requestedUrl(req).replace(/\?.*$/, '') + let query = ["query="+req.query.query] + i = isPrevious ? -1 : 1 + // page + p = req.query.page == '1' ? null : (req.query.page + i) + query = query.concat(['page=' + p]) + // per_page + query = query.concat(['per_page=' + (req.query.per_page || 20)]) + // filter + if (req.query.filter) query = query.concat(['filter=' + req.query.filter]) + // sort + if (req.query.sort) query = query.concat(['sort=' + req.query.sort]) + // Append query to base URL + if (query.length > 0) baseUrl += "?" + query.join("&") + return baseUrl + } + + + /** + * sort - Creates sort argument as needed in sequelize and accordingly to the order implicit in the resquest info. + * + * @param {object} req Request info. + * @return {object} Sort argument object as needed in the schema to retrieve filtered records from a given model. + */ + sort = function(req) { + let sortOpts = {} + if (req.query.sort) { + sortOpts = { + order: [req.query.sort.split('|')] + } + } + return sortOpts + } + + + /** + * search - Creates search argument as needed in sequelize and accordingly to the filter string implicit in the resquest info. + * + * @param {object} req Request info. This info will contain the substring that will be used to filter records. + * @param {array} strAttributes Name of model's attributes + * @return {object} Search argument object as needed in the schema to retrieve filtered records from a given model. + */ + search = function(req, strAttributes) { + let selectOpts = {} + if (req.query.filter) { + let fieldClauses = [] + strAttributes.forEach(function(x) { + let fieldWhereClause = {} + if (x !== "id") { + fieldWhereClause[x] = { + $like: "%" + req.query.filter + "%" + } + fieldClauses = fieldClauses.concat([fieldWhereClause]) + } else { + if (/^\d+$/.test(req.query.filter)) { + fieldWhereClause[x] = req.query.filter + fieldClauses = fieldClauses.concat([fieldWhereClause]) + } + } + }) + selectOpts['where'] = { + $or: fieldClauses + } + } + return selectOpts + } + + +// includeAssociations = function (req) { +// return req.query.excludeAssociations ? {} : { +// include: [{ +// all: true +// }] +// } +// } + +/** + * searchPaginate - Creates one object mergin search, sort, and paginate arguments + * + * @param {object} req Request info. + * @param {array} strAttributes Name of model's attributes. + * @return {object} General argument for filtering models in sequelize. + */ +searchPaginate = function(req, strAttributes) { + return objectAssign( + search(req, strAttributes), + sort(req), + paginate(req) + //,includeAssociations(req) + ); +} + +/** + * vueTable - Creates object needed to display a vue-table in a vuejs SPA + * + * @param {object} req Request info. + * @param {object} model Sequelize model which records are intended to be displayed in the vue-table. + * @param {array} strAttributes Name of model's attributes. + * @return {object} Info for displaying vue-table in a vuejs SPA, including info for automatic pagination. + */ +module.exports.vueTable = function(req, model, strAttributes) { + let searchOptions = search(req, strAttributes) + let searchSortPagIncl = searchPaginate( req, strAttributes ) + let queries = [] + queries.push(model.count(searchOptions)) + queries.push(model.findAll(searchSortPagIncl)) + return Promise.all(queries).then( + function(res) { + let searchRes = res[0] + let paginatedSearchRes = res[1] + let lastPage = math.ceil(searchRes / req.query.per_page) + return { + data: paginatedSearchRes, + total: searchRes, + per_page: req.query.per_page, + current_page: req.query.page, + 'from': (req.query.page - 1) * req.query.per_page + 1, + 'to': math.min(searchRes, req.query.page * req.query.per_page), + last_page: lastPage, + prev_page_url: (req.query.page == 1) ? null : prevNextPageUrl( + req, true), + next_page_url: (req.query.page == lastPage) ? null : prevNextPageUrl( + req, false) + } + }) + } + + + /** + * modelAttributes - Return info about each column in the model's table + * + * @param {Object} model Sequelize model from which the info will be given. + * @return {Array} Array of objects, each object contains info for each attribute in the model + */ + modelAttributes = function(model) { + return model.sequelize.query( + "SELECT column_name, data_type, is_nullable, column_default " + + "FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '" + + model.tableName + "'", { + type: model.sequelize.QueryTypes.SELECT + } + ) + } + + //attributes to discard + discardModelAttributes = ['createdAt', 'updatedAt'] + + + /** + * filterModelAttributesForCsv - Filter attributes from a given model +f * + * @param {Object} model Sequelize model from which the attributes will be filtered + * @param {Array} discardAttrs Array of attributes to discard + * @return {Array} Filtered attributes + */ + filterModelAttributesForCsv = function(model, discardAttrs) { + discardAttrs = discardAttrs || discardModelAttributes + modelPrimaryKey = model.primaryKeyField + if (modelPrimaryKey) + discardAttrs = discardAttrs.concat([modelPrimaryKey]) + return modelAttributes(model).then(function(x) { + return x.filter(function(i) { + return discardAttrs.indexOf(i.column_name) < 0 + }) + }) + } + + + /** + * csvTableTemplate - Returns template of model, i.e. header of each column an its type + * + * @param {Object} model Sequelize model from which the template will be returned. + * @param {Array} discardAttrs Attributes to discard from the template + * @return {Array} Array of strings, one for header and one for the attribute't type. + */ + module.exports.csvTableTemplate = function(model, discardAttrs) { + return filterModelAttributesForCsv(model, + discardAttrs).then(function(x) { + csvHeader = [] + csvExmplRow = [] + x.forEach(function(i) { + csvStr = i.data_type + if (i.is_nullable.toLowerCase() === 'false' || i.is_nullable.toLowerCase() === + 'no' || i.is_nullable === 0) + csvStr += ",required" + if (i.column_default) + csvStr += ",default:" + i.column_default + csvHeader = csvHeader.concat([i.column_name]) + csvExmplRow = csvExmplRow.concat([csvStr]) + }) + return [csvHeader.join(','), csvExmplRow.join(',')] + }) + } diff --git a/utils/helpers-acl.js b/utils/helpers-acl.js new file mode 100644 index 0000000..fa930a9 --- /dev/null +++ b/utils/helpers-acl.js @@ -0,0 +1,22 @@ +const secret = 'something-secret'; +const jwt = require('jsonwebtoken'); + +//TODO: Use this routines through all the code to have them in one place + +module.exports = { + getTokenFromContext: function (context) { + let token_bearer = context.request.headers["authorization"]; + let token = token_bearer.replace("Bearer ",""); + let decoded = jwt.verify(token, secret); + return decoded; + }, + + //INFO: can be useful for tests + /*generateDummyToken: function (email) { + return jsonwebtoken.sign({ + id: 1, + email: email, + roles: "admin" + }, 'something-secret', { expiresIn: '1h' }); + }*/ +}; \ No newline at end of file diff --git a/utils/join-models.js b/utils/join-models.js new file mode 100644 index 0000000..8c2cd8d --- /dev/null +++ b/utils/join-models.js @@ -0,0 +1,599 @@ + +// TODO: JOIN: do not print anything if there is no data found, print that there is no data found + + +//TODO: Web service generates a class object that is incompatible with +//TODO: sequelize object. All types of models shell have the same interface to +//TODO: proceed with generic JOIN. That's clearly a new issue. + + +const _ = require('lodash'); +const path = require('path'); +const models = require(path.join(__dirname, '..', 'models_index.js')); +const resolvers = require(path.join(__dirname, '..', 'resolvers', 'index.js')); +const inflection = require('inflection'); +const checkAuthorization = require('./check-authorization'); +const schema = require('./graphql_schema'); +let LinkedList = require('linked-list'); + + + +/** + * + * INPUT FORMAT DESCRIPTION + * + * The "modelAdjacencies" input parameter is an ordered array of JSON objects that describe a JOIN chain. + * Below goes an example of the currently supported parameter set: + * + * { + * + * outputFormat = CSV // (REQUIRED) For the moment can be TEST, CSV or JSON only, see + * // the corresponding module exports + * + * + * modelAdjacencies = // (REQUIRED) + * [ + * { + * "name" : "individual", // Name of the model as it appears in the corresponding index.js + * + * + * "association_name" : "transcript_counts", + * // (REQUIRED) There can be more than one association between two models, + * // the way to differ between these associations is an "as_name" + * // used by sequelize. This name is used by codegen to create resolvers and + * // is used here to find them. + * + * + * + * "attributes" : [ // (OPTIONAL - not implemented) The resolvers does not give possibility + * // to filter out unnecessary columns of the table. However, is is easy + * // to implement this functionality inside a "constructRow" function. This way + * // it can be possible to create different cut-offs of the database at the + * // presentation level and resolve the data analysis problem at a low cost. + * "name", + * "createdAt" + * ], + * + * <, "search" : {...}, "order" : {...}>// (OPTIONAL) Can be specified to filter records at the head of the + * // JOIN chain. + * + * + * }, + * { + * "name" : "transcript_count", + * // The last element of the association chain does not require an "assoc" + * // structure, it has no sense here and will be ignored if present. + * + * "search" : { // (OPTIONAL) In the case when as_type of the previous element is "hasMany" or + * // "belongsToMany", there can be more than one "transcript_count" record + * "field" : "name", // associated with the same "individual". + * "value" : { // The "transcript_count" records can be filtered and ordered + * "value" : "%A%" // correspondingly to these "search" and "order" parameters. In the case of + * }, // "hasOne" or "belongsToOne" as_type of the "individual", the "search" and + * "operator" : "like" // "order" parameters will be ignored. + * }, + * + * order: [{field: name, order: DESC}] // (OPTIONAL) Ordering of the associated "transcript_count" records. + * } + * ] + * } + * + * PERMISSIONS + * + * The user role should have a "batch_download" permission for all models defined in the + * "modelAdjacencies" input parameter. + * + */ + +class JoinModels { + + /** + * Internal class parameters + */ + constructor(context){ + + // a linked list to be initialized from the input adjacency array + // this list will always keep the current model and the next model that is more useful than + // a plain array + this.list = new LinkedList; + + // request context + this.context = context; + } + + /** + * joinModels - is a function called by express server directly. This function execution can take + * a long time so it should not be blocking and should not produce long call stack chains + + * @param modelAdjacencies a number of parameters in JSON format that define a JOIN request (see below) + * @param httpWritableStream a writable http stream + * @returns {Promise} generally a void function + */ + + async run(params, httpWritableStream) { + + let modelAdjacencies = params.modelAdjacencies; + if ( ! modelAdjacencies || modelAdjacencies.length === 0) + throw Error(`modelAdjacencies array is undefined`); + + // a linked list to be initialized from the input adjacency array + // this list will always keep the current model and the next model that is more useful than + // a plain array + //let list = new LinkedList; + + for(let model_adj of modelAdjacencies) { + let item = new LinkedList.Item(); + item.model_adj = model_adj; + this.list.append(item); + } + + // iterate over the list and add some useful information to it's elements + let cur = this.list.head; + do{ + // check for permission to make batch_download for all models listed + if( ! await checkAuthorization(this.context, cur.model_adj.name, 'batch_download')) + return new Error(`You don't have authorization to perform batch download for the ${cur.model_adj.name} model`); + + // required on this step input data validation + if( ! cur.model_adj.name ) throw Error(`Model name is not defined in ${JSON.stringify(cur.model_adj)}`); + if( ! models[cur.model_adj.name] ) throw Error(`Model with name ${cur.model_adj.name} not exist`); + + // store raw names of the model attributes if not given at input + let all_attributes = schema.getModelFieldByAnnotation(cur.model_adj.name,'@original-field'); + if( ! cur.model_adj.attributes ) { + cur.model_adj.attributes = all_attributes; + }else{ + // check that specified attributes are correct + for (let attribute_name of cur.model_adj.attributes) + if( ! all_attributes.includes(attribute_name)) + throw Error(`Requested attribute '${attribute_name}' is not defined in the model '${cur.model_adj.name}'`); + } + + // current data (is initialized by the 'func_find' call) + cur.model_adj.data = null; + + // in the case when SELECT query that is formed by func_find method can return + // a collection of database records, those can be filtered and ordered accordingly + // to "find_params" structure. This structure also includes "offset" initialized to 0 + cur.model_adj.search_params = this.defineSearchParams(cur); + + // function that searches by criteria and offset instances of the given model_adj + cur.model_adj.func_find = this.defineFindFunction(cur); + + }while(null !== (cur = cur.next)); + + + // http send stream header + let timestamp = new Date().getTime(); + httpWritableStream.writeHead(200, {'Content-Type': 'application/force-download', + 'Content-disposition': `attachment; filename = ${timestamp}.json`}); + + + /* + This is the main introspection loop. On each iteration the user would receive + a new data raw. Accordingly to implementation of the constructRow function there + is a possibility to generate different output formats, hide unnecessary columns, etc. + This functionality is out of the scope of the current class and the constructRow + implementation has to be overloaded to output real data. See the child classes to get + more information. + */ + while(true){ + + // entering into the iterations from the head element + cur = this.list.head; + + try { + + while(true){ + let rollback = false; + + // query the database (see defineFindFunction for details) + cur = await cur.model_adj.func_find(cur, this.context); + + // no data found for the cur element of association chain => print, rollback or exit + if(cur.model_adj.data === null){ + + // cur element was visited for the first time: augment offset and print the line + if(cur.model_adj.search_params.pagination.offset === 0){ + cur = this.augmentOffsetFlushTrailing(cur); + break; + + // cur element was visited before and has no data + }else{ + + // head has no more data, terminate + if(cur.prev === null){ + return; + + // cur has no data and was already printed + // goto prev, augment it's offset and try again + }else{ + cur = cur.prev; + cur = this.augmentOffsetFlushTrailing(cur); + rollback = true; + } + } + } + + // the last element was reached and it has data != null + if(cur.next === null){ + cur = this.augmentOffsetFlushTrailing(cur); + break; + } + + // if it's not a rollback run - explore the next element + if(!rollback) + cur = cur.next; + } + + /* + Send joined data raw to the end-user accordingly to the constructRow implementation. + + It should be stressed, that after the first element with data == null, all subsequent elements + have no valid data. Also, as long as offsets are used to check if a given element was + already visited (printed) or not, the offsets should not be modified within constructRow function, + and it's interpretation is not direct. + */ + + let row_string = this.constructRow(this.list.head); + await httpWritableStream.write(row_string); + + }catch(err){ + /* + We can't throw an error to Express server at this stage because the response Content-Type + was already sent. So we can try to attach it to the end of file. + */ + console.log(err); + await httpWritableStream.write(`{error : ${err.message}}\n`); + return; + } + } + }; + + + /** + * defineFindFunction - Function use offset to retrieve corresponding data for the current list element according + * to the current offset. This function will renew the cur.model_adj.data and augment + * the cur.model_adj.offset field. If there is no data for the current offset, the + * cur.model_adj.data will be set to null. + * + * It is assumed, that cur->prev element has already initialized it's data field. If cur->prev is null, it means that + * we are working with the list head. If after calling this function, the cur.model_adj.data is null, + * it means that there is nothing mode to do, and the JOIN process has successfully completed. + * + * @param {object} cur is a current element of the linked-list + * @return {function} returns a function used inside generic algorithm + */ + + defineFindFunction(cur){ + + + if(cur.prev === null){ + + // cur is the head element of the list + return async function(cur, context){ + + // for head getter function has to be estimated just once + if( ! cur.model_adj.func_getter) + cur.model_adj.func_getter = resolvers[inflection.pluralize(cur.model_adj.name)]; + + // get record from database for the given offset + // an output is an array that have one or zero elements + cur.model_adj.data = await cur.model_adj.func_getter(cur.model_adj.search_params, context); + + if( cur.model_adj.data.length === 0 ) { + cur.model_adj.data = null; + }else{ + cur.model_adj.data = cur.model_adj.data[0]; + } + + return cur; + } + + } else { + + /* + Here an explicit check is applied to detect for the association getter function in the cur.prev data model. + At the same time this is a validator (see the "else" option). + */ + + let model_prev = models[cur.prev.model_adj.name]; + + const as_name = cur.prev.model_adj.association_name; + if( ! as_name ) throw Error('"assoc" structure is required, see the docs'); + + //<%- nameLc -%>.prototype.<%=associations_one[i].name%> + let func_toOneGetter = model_prev.prototype[as_name]; + + //<%- nameLc -%>.prototype.<%=associations_temp[i].name%>Filter + let func_toManyGetter = model_prev.prototype[`${as_name}Filter`]; + + if(typeof func_toOneGetter === "function"){ + + // there is just one cur element can be found from the cur.prev that + // corresponds to the hasOne or belongsTo of the prev->cur association type + return async function(cur, context){ + const as_name = cur.prev.model_adj.association_name; + + if(cur.model_adj.search_params.pagination.offset > 0){ + cur.model_adj.data = null; + }else{ + cur.model_adj.data = await cur.prev.model_adj.data[as_name]({},context); + if(!cur.model_adj.data) + cur.model_adj.data = null; + } + + return cur; + } + } else if(typeof func_toManyGetter === "function"){ + + return async function(cur, context){ + + // get record from database for the current offset (it comes inside cur.model_adj.search_params data structure) + // an output is an array that would have one (because limit is always 1) or zero elements (if nothing was found) + cur.model_adj.data = await cur.prev.model_adj.data[`${inflection.pluralize(cur.model_adj.name)}Filter`](cur.model_adj.search_params, context); + + // set data to null explicitly or remove an array wrapper (anyway there is just one element) + if( cur.model_adj.data.length === 0 ){ + cur.model_adj.data = null; + }else{ + cur.model_adj.data = cur.model_adj.data[0]; + } + + return cur; + } + } else{ + /* + If you get this error, it means that there is no explicit link between cur.prev and cur elements. + For example, assume that model A belongsTo model B. However, the madel B does not have a corresponding + hasMany or hasOne association with A. If you try to make a JOIN in the order B -> A, you will get + this "No association" exception. However, if you JOIN these models in the order A -> B, the corresponding + association resolver will be found. + + Currently all associations are inverse, so this error should never happen. + */ + + throw Error(`No association from ${cur.prev.model_adj.name} to ${cur.model_adj.name} was detected,` + + `please check for ${as_name} or ${as_name}Filter functions`); + } + + } + + }; + + + + + + /** + * defineSearchParams - a helper function fills up a serach_params data structure. It's 'search' and 'order' + * elements would never change during the given transmission session. However the pagination + * parameter is important. The limit shell always be 1, and the offset is internal parameter of + * the current algorithm. It is prohibited to alter offset values from the outside world. + * + * @param {object} cur is a current element of the linked-list + * @return {object} returns a structure of search params + */ + defineSearchParams(cur){ + let search_params = {}; + + search_params.pagination = { + offset : 0, + limit : 1 + }; + + if( cur.model_adj.search ) + search_params.search = cur.model_adj.search; + + if( cur.model_adj.order ) + search_params.order = cur.model_adj.order; + + return search_params; + }; + + + + + /** + * augmentOffsetFlushTrailing - Helper function is used to augment offset of the "cur" element. + * In this case offsets and data of the all trailing elements became invalid + * and shell be flushed. + * + * @param {object} cur is a current element of the linked-list + * @return {object} returns a cur element with offset augmented that points on to the cleaned up tail + */ + augmentOffsetFlushTrailing(cur){ + cur.model_adj.search_params.pagination.offset++; + let next = cur.next; + while(next !== null){ + next.model_adj.search_params.pagination.offset = 0; + next.model_adj.data = null; + next = next.next; + } + return cur; + }; + + + /** + * constructRow - basic implementation of the constructRow function that prints model names and id's + *of the found elements. It is useful for testing purposes. + *... + *individual[id:458] ->transcript_count[id:6] + *individual[id:459] ->transcript_count[id:2] + *individual[id:460] + *individual[id:461] + * + * @param {object} head head of the linked list + * @return {string} joined line + */ + constructRow(head){ + let str = ""; + + let cur = head; + do{ + str = str.concat(`${cur.model_adj.name}[`); + str = str.concat(`id:${cur.model_adj.data.id}] `); + + if(cur.next !== null && cur.next.model_adj.data === null) + break; + + if(cur.next !== null) + str = str.concat("->"); + + }while(null !== (cur = cur.next)); + + str = str.concat("\n"); + + return str; + } + + +}; + + + +// ********************************************************************************************************************* + + + +class JoinModelsJSON extends JoinModels{ + + /** + * Internal class parameters + */ + constructor(context){ + super(context); + } + + /** + * constructRow - create text string for the joined line in JSON format + * + * Example output for filtered columns: + * ... + * {"transcript_count.id":8,"individual.id":463} + * {"transcript_count.id":9,"individual.id":null} + * {"transcript_count.id":10,"individual.id":462} + * ... + * + * @param {object} head head of the linked list + * @return {string} a text string that will be sent to the service client + */ + + constructRow(head){ + + let cur = head; + let joined_data = {}; + do{ + + let data = {}; + + if(cur.model_adj.data === null){ + for (let attr of cur.model_adj.attributes) { + data[cur.model_adj.name + "." + attr] = null; + } + }else{ + data = _.pick(cur.model_adj.data, cur.model_adj.attributes); + + for(let old_key of Object.keys(data)){ + let new_key = cur.model_adj.name + "." + old_key; + Object.defineProperty(data, new_key, + Object.getOwnPropertyDescriptor(data, old_key)); + delete data[old_key]; + } + } + + Object.assign(joined_data, data); + + }while(null !== (cur = cur.next)); + + return JSON.stringify(joined_data) + "\n"; + }; +}; + + +class JoinModelsCSV extends JoinModels{ + /** + * Internal class parameters + */ + constructor(context){ + super(context); + + // in CSV format first line is the title + this.csv_header = true; + } + + + /** + * constructRow - create text string for the joined line in CSV format + * + * Example output for filtered columns: + * + * transcript_count.id,individual.id + * 8,463 + * 9,null + * 10,462 + * ... + * + * @param {object} head head of the linked list + * @return {string} a text string that will be sent to the service client + */ + constructRow(head){ + + let cur = head; + + let str = ""; + let header = ""; + + do{ + + for (let attr of cur.model_adj.attributes){ + + if(this.csv_header) + header += cur.model_adj.name + "." + attr + ","; + + if(cur.model_adj.data === null || cur.model_adj.data[attr] === null){ + str += `"NULL",`; + }else{ + str += `"${cur.model_adj.data[attr]}"` + ","; + } + } + + }while(null !== (cur = cur.next)); + + if(this.csv_header){ + this.csv_header = false; + header = header.replace(/.$/,"\n"); + header += str; + str = header; + } + + str = str.replace(/.$/,"\n"); + + return str; + }; +} + + + +module.exports.JoinModels = JoinModels; +module.exports.JoinModelsJSON = JoinModelsJSON; +module.exports.JoinModelsCSV = JoinModelsCSV; + +/********************************************************** + +CURL tests (copy-paste to console): + +curl -d '{"outputFormat" : "JSON", "modelAdjacencies" : [ { "name" : "individual", "association_name" : "transcript_counts" }, { "name" : "transcript_count"} ]}' -H "Content-Type: application/json" http://localhost:3000/join + +curl -d '{"outputFormat" : "JSON", "modelAdjacencies" : [ { "name" : "transcript_count", "association_name" : "individual" }, { "name" : "individual"} ]}' -H "Content-Type: application/json" http://localhost:3000/join + +curl -d '{"outputFormat" : "JSON", "modelAdjacencies" : [ { "name" : "transcript_count", "association_name" : "individual", "attributes" : ["id"] }, { "name" : "individual", "attributes" : ["id"]} ]}' -H "Content-Type: application/json" http://localhost:3000/join + + +curl -d '{"outputFormat" : "JSON", "modelAdjacencies" : [ { "name" : "individual", "attributes" : ["id", "name"], "search" : { "field" : "name", "value" : {"value" : "A"}, "operator" : "like" }, "order" : [{"field" : "name", "order" : "ASC" }] } ]}' -H "Content-Type: application/json" http://localhost:3000/join + +curl -d '{"outputFormat" : "JSON", "modelAdjacencies" : [ { "name" : "transcript_count", "association_name" : "aminoacidsequence", "attributes" : [ "id", "gene"] } , {"name": "aminoacidsequence"} ]}' -H "Content-Type: application/json" http://localhost:3000/join + +curl -d '{"outputFormat" : "JSON", "modelAdjacencies" : [ { "name": "aminoacidsequence", "attributes" : [ "id"], "association_name" : "transcript_counts"}, { "name" : "transcript_count" } ]}' -H "Content-Type: application/json" http://localhost:3000/join + +*/ diff --git a/utils/login.js b/utils/login.js new file mode 100644 index 0000000..7b2d6d4 --- /dev/null +++ b/utils/login.js @@ -0,0 +1,39 @@ +const bcrypt = require('bcrypt') +const jsonwebtoken = require('jsonwebtoken') +const path = require('path') +const user = require(path.join(__dirname, '..', 'models_index.js')).user + +module.exports = { + + /** + * login - Search for email in users table and returns a webtoken if the password is valid. + * + * @param {String} email User's email + * @param {String} password User's password + * @return {String} Webtoken with user's data encoded + */ + login: async function({ email, password }) { + + const user_data = await user.findOne({ where: { email } }) + console.log(user_data); + if (!user_data) { + throw new Error('No user with that email') + } + + const valid = (password==user_data.password); //await bcrypt.compare(password, user_data.password) + + if (!valid) { + throw new Error('Incorrect password') + } + const roles = await user_data.getRoles(); + const name_roles = roles.map( x =>{ return x.name }) + console.log("ROLES: ", name_roles); + // return json web token + return jsonwebtoken.sign({ + id: user_data.id, + email: user_data.email, + roles: name_roles + }, 'something-secret', { expiresIn: '1h' }) + } + +} diff --git a/utils/merge-schemas.js b/utils/merge-schemas.js index c0ccf7b..dc2092b 100644 --- a/utils/merge-schemas.js +++ b/utils/merge-schemas.js @@ -1,6 +1,13 @@ var { fileLoader, mergeTypes } = require('merge-graphql-schemas'); + +/** + * @function - Merge graphql schemas stored in a same directory + * + * @param {string} schemas_folder path to directory where all graphql schemas are stored. + * @return {string} Merged graphql schema. + */ module.exports = function( schemas_folder ) { const typesArray = fileLoader( schemas_folder); let merged = mergeTypes(typesArray); diff --git a/utils/search-argument.js b/utils/search-argument.js index bb69e50..d52ab95 100644 --- a/utils/search-argument.js +++ b/utils/search-argument.js @@ -1,16 +1,33 @@ -/* - Class to parse search argument for any model -*/ -module.exports = class searchArg{ +/** + * search Class to parse search argument for any model and translate it so sequelize model will accept it + */ +module.exports = class search{ - constructor({field, value, operator, searchArg}){ + + /** + * constructor - Creates an instace with the given arguments + * + * @param {string} field field to filter. + * @param {object} value value contains type(i.e. array, string) and actual value to match in the filter. + * @param {string} operator operator used to perform the filter. + * @param {object} search recursive search instance. + * @return {object} instace of search class. + */ + constructor({field, value, operator, search}){ this.field = field; this.value = this.constructor.parseValue(value); this.operator = operator; - this.searchArg = searchArg + this.search = search } + + /** + * @static parseValue - Creates the proper type(either array or string) of the value that user wants to filter. + * + * @param {object} val value object to parse. + * @return {(array|string|number)} Parsed value + */ static parseValue(val){ if(val!==undefined) { @@ -23,32 +40,38 @@ module.exports = class searchArg{ } } + + /** + * toSequelize - Convert recursive search instance to search object that sequelize will accept as input. + * + * @return {object} Translated search instance into sequelize object format. + */ toSequelize(){ - let searchArgsInSequelize = {}; + let searchsInSequelize = {}; - if(this.searchArg === undefined && this.field === undefined) + if(this.search === undefined && this.field === undefined) { - searchArgsInSequelize['$'+this.operator] = this.value; + searchsInSequelize['$'+this.operator] = this.value; - }else if(this.searchArg === undefined) + }else if(this.search === undefined) { - searchArgsInSequelize[this.field] = { + searchsInSequelize[this.field] = { ['$'+this.operator] : this.value }; }else if(this.field === undefined){ - searchArgsInSequelize['$'+this.operator] = this.searchArg.map(sa => { - let new_sa = new searchArg(sa); + searchsInSequelize['$'+this.operator] = this.search.map(sa => { + let new_sa = new search(sa); return new_sa.toSequelize(); }); }else{ - searchArgsInSequelize[this.field] = { - ['$'+this.operator] : this.searchArg.map(sa => { - let new_sa = new searchArg(sa); + searchsInSequelize[this.field] = { + ['$'+this.operator] : this.search.map(sa => { + let new_sa = new search(sa); return new_sa.toSequelize(); }) } } - return searchArgsInSequelize; + return searchsInSequelize; } }; diff --git a/utils/simple-export.js b/utils/simple-export.js new file mode 100644 index 0000000..ce575e7 --- /dev/null +++ b/utils/simple-export.js @@ -0,0 +1,95 @@ +const path = require('path'); +const resolvers = require(path.join(__dirname, '..', 'resolvers', 'index.js')); +const inflection = require('inflection'); +const schema = require('./graphql_schema'); + +getAttributes = function( model_name ){ + return schema.getModelFieldByAnnotation(model_name, '@original-field'); +} + +crateHeaderCSV = function(attributes){ + let str_header = ""; + attributes.forEach( att =>{ + str_header+= att+","; + } ) + str_header= str_header.replace(/.$/,"\n"); + + return str_header; +} + +asyncForEach = async function(array, callback) { + for (let index = 0; index < array.length; index++) { + await callback(array[index], index, array); + } +} + +jsonToCSV = function(row_data, attributes){ + let str_csv = ""; + attributes.forEach( att => { + if(row_data[att]===null || row_data[att] === undefined){ + str_csv+='NULL.'; + }else { + str_csv+= row_data[att]+","; + } + }) + + str_csv= str_csv.replace(/.$/,"\n"); + return str_csv; +} + +// wait ms milliseconds +function wait(ms) { + return new Promise(r => setTimeout(r, ms)); +} + + +module.exports = async function(context, body_info, writableStream ){ + //get resolver name for model + let model_name = body_info.model; + let getter_resolver = inflection.pluralize(model_name.slice(0,1).toLowerCase() + model_name.slice(1, model_name.length)); + + //get count resolver + let count_resolver = 'count'+inflection.pluralize(model_name.slice(0,1).toUpperCase() + model_name.slice(1, model_name.length)); + let total_records = await resolvers[count_resolver]({}, context); + console.log("TOTAL NUMBER OF RECORDS TO STREAM: ", total_records); + + //pagination + let batch_step = { + limit: 1, + offset: 0 + } + + // http send stream header + let timestamp = new Date().getTime(); + writableStream.writeHead(200, {'Content-Type': 'application/force-download', + 'Content-disposition': `attachment; filename = ${timestamp}.csv`}); + + //get attributes names + let attributes = getAttributes(model_name); + + //write csv header + let csv_header = crateHeaderCSV(attributes); + await writableStream.write(csv_header); + + while(batch_step.offset < total_records){ + + try{ + data = await resolvers[getter_resolver]({pagination: batch_step},context); + + await asyncForEach(data, async (record) =>{ + let row = jsonToCSV(record.dataValues, attributes); + await writableStream.write(row); + }) + batch_step.offset = batch_step.offset + batch_step.limit; + }catch(err){ + /* + We can't throw an error to Express server at this stage because the response Content-Type + was already sent. So we can try to attach it to the end of file. + */ + console.log(err); + await writableStream.write(`{error : ${err.message}}\n`); + return; + } + + } +} diff --git a/utils/testSequelizeDbServerAvailable.js b/utils/testSequelizeDbServerAvailable.js new file mode 100644 index 0000000..6ae5e63 --- /dev/null +++ b/utils/testSequelizeDbServerAvailable.js @@ -0,0 +1,15 @@ +#!/usr/bin/env node + +const path = require('path') +const Sequelize = require(path.join(__dirname, '..', 'connection.js')) + +async function checkConnection() { + try { + await Sequelize.authenticate() + return process.exit(0) + } catch (exception) { + return process.exit(1) + } +} + +checkConnection() diff --git a/utils/validatorUtil.js b/utils/validatorUtil.js new file mode 100644 index 0000000..7e8b19e --- /dev/null +++ b/utils/validatorUtil.js @@ -0,0 +1,110 @@ +const gd = require('graphql-iso-date') +const Ajv = require('ajv') + +/** + * ifHasValidatorFunctionInvoke - checks if data model has a validator function with + * the given name, and apply that function + * + * @param {string} validatorFunction Name of the validator function + * @param {object} dataModel The empty data model object + * @param {object} data JSON data to be inserted into the dataModel + * @return {Promise} The result of invoking the respective validator, or + * undefined if no validator was found to be registered + * + */ + +module.exports.ifHasValidatorFunctionInvoke = async function( validatorFunction, dataModel, data) { + if (typeof dataModel.prototype[validatorFunction] === "function") { + try{ + return await dataModel.prototype[validatorFunction](data); + }catch( err) { + throw err; + } + } +}; + +/** + * Adds AJV asynchronous keywords to the argument AJV instance that define ISO + * Date, ISO Time, and ISO DateTime strings or the respective GraphQL instances + * (see package 'graphql-iso-date'). Use in a schema as in the following + * example: let schema = { '$async': true, properties: { startDate: { isoDate: + * true } } } + * + * @param {object} ajv - An instance of AJV (see package 'ajv' for details. + * + * @return {object} the modified instance of ajv. As Javascript uses references + * this return value can be ignored, as long as the original argument is kept + * and used. + */ +module.exports.addDateTimeAjvKeywords = function(ajv) { + ajv.addKeyword('isoDate', { + async: true, + compile: function(schema, parentSchema) { + return async function(data) { + try { + gd.GraphQLDate.serialize(data); + return true + } catch (e) { + return new Promise(function(resolve, reject) { + return reject(new Ajv.ValidationError([{ + keyword: 'isoDate', + message: 'Must be a GraphQLDate instance or a ISO Date formatted string (e.g. "1900-12-31").', + params: { + 'keyword': 'isoDate' + } + }])) + }) + } + } + }, + errors: true + }) + + ajv.addKeyword('isoTime', { + async: true, + compile: function(schema, parentSchema) { + return async function(data) { + try { + gd.GraphQLTime.serialize(data); + return true + } catch (e) { + return new Promise(function(resolve, reject) { + return reject(new Ajv.ValidationError([{ + keyword: 'isoTime', + message: 'Must be a GraphQLTime instance or a ISO Time formatted string (e.g. "13:56:45Z" or "13.56.45.1982Z").', + params: { + 'keyword': 'isoTime' + } + }])) + }) + } + } + }, + errors: true + }) + + ajv.addKeyword('isoDateTime', { + async: true, + compile: function(schema, parentSchema) { + return async function(data) { + try { + gd.GraphQLTime.serialize(data); + return true + } catch (e) { + return new Promise(function(resolve, reject) { + return reject(new Ajv.ValidationError([{ + keyword: 'isoDateTime', + message: 'Must be a GraphQLDateTime instance or a ISO Date-Time formatted string (e.g. "1900-12-31T23:59:59Z" or "1900-12-31T23:59:59.1982Z").', + params: { + 'keyword': 'isoDateTime' + } + }])) + }) + } + } + }, + errors: true + }) + + return ajv +}