From c71ae8c4c92a2bd228d08e0ecbd3f40a25a72d6a Mon Sep 17 00:00:00 2001 From: Diego Giovane Pasqualin <dpasqualin@c3sl.ufpr.br> Date: Wed, 13 Jan 2016 15:15:42 -0200 Subject: [PATCH] =?UTF-8?q?Cobertura=20de=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 17 + .gitlab-ci.yml | 44 +- .jscsrc | 21 + .jshintrc | 63 ++ LICENSE | 674 ++++++++++++++++++ api/controllers/locations.js | 185 +++++ api/controllers/users.js | 190 +++++ api/middleware/mongodb.js | 44 ++ api/models/location.js | 46 ++ api/models/user.js | 129 ++++ api/router-v1.js | 38 + api/specs/examples/body/user.json | 6 + .../examples/collections/dimensions.json | 0 api/specs/examples/collections/locations.json | 26 + api/specs/examples/collections/metrics.json | 0 api/specs/examples/collections/points.json | 0 api/specs/examples/collections/tags.json | 0 api/specs/examples/collections/users.json | 31 + api/specs/examples/data.json | 0 api/specs/examples/items/location.json | 8 + api/specs/examples/items/point.json | 0 api/specs/examples/items/tag.json | 0 api/specs/examples/items/user.json | 11 + api/specs/examples/report.json | 0 api/specs/schemas/body/user.json | 25 + api/specs/schemas/collections/dimensions.json | 0 api/specs/schemas/collections/locations.json | 6 + api/specs/schemas/collections/metrics.json | 0 api/specs/schemas/collections/points.json | 0 api/specs/schemas/collections/tags.json | 0 api/specs/schemas/collections/users.json | 6 + api/specs/schemas/data.json | 0 api/specs/schemas/items/location.json | 29 + api/specs/schemas/items/point.json | 0 api/specs/schemas/items/tag.json | 0 api/specs/schemas/items/user.json | 32 + api/specs/schemas/message.json | 15 + api/specs/schemas/report.json | 0 api/specs/simmc-api-v1.raml | 612 ++++++++++++++++ config/example.yaml | 4 + gulpfile.js | 181 +++++ lib/config-parser.js | 54 ++ lib/error-handler.js | 77 ++ lib/filter-parser.js | 84 +++ lib/fixtures.js | 51 ++ lib/logger.js | 166 +++++ package.json | 64 ++ server.js | 78 ++ test/api/controllers/locations.spec.js | 219 ++++++ test/api/controllers/users.spec.js | 42 ++ test/common.js | 38 + test/fixtures/locations.json | 71 ++ 52 files changed, 3361 insertions(+), 26 deletions(-) create mode 100644 .editorconfig create mode 100644 .jscsrc create mode 100644 .jshintrc create mode 100644 LICENSE create mode 100644 api/controllers/locations.js create mode 100644 api/controllers/users.js create mode 100644 api/middleware/mongodb.js create mode 100644 api/models/location.js create mode 100644 api/models/user.js create mode 100644 api/router-v1.js create mode 100644 api/specs/examples/body/user.json create mode 100644 api/specs/examples/collections/dimensions.json create mode 100644 api/specs/examples/collections/locations.json create mode 100644 api/specs/examples/collections/metrics.json create mode 100644 api/specs/examples/collections/points.json create mode 100644 api/specs/examples/collections/tags.json create mode 100644 api/specs/examples/collections/users.json create mode 100644 api/specs/examples/data.json create mode 100644 api/specs/examples/items/location.json create mode 100644 api/specs/examples/items/point.json create mode 100644 api/specs/examples/items/tag.json create mode 100644 api/specs/examples/items/user.json create mode 100644 api/specs/examples/report.json create mode 100644 api/specs/schemas/body/user.json create mode 100644 api/specs/schemas/collections/dimensions.json create mode 100644 api/specs/schemas/collections/locations.json create mode 100644 api/specs/schemas/collections/metrics.json create mode 100644 api/specs/schemas/collections/points.json create mode 100644 api/specs/schemas/collections/tags.json create mode 100644 api/specs/schemas/collections/users.json create mode 100644 api/specs/schemas/data.json create mode 100644 api/specs/schemas/items/location.json create mode 100644 api/specs/schemas/items/point.json create mode 100644 api/specs/schemas/items/tag.json create mode 100644 api/specs/schemas/items/user.json create mode 100644 api/specs/schemas/message.json create mode 100644 api/specs/schemas/report.json create mode 100644 api/specs/simmc-api-v1.raml create mode 100644 config/example.yaml create mode 100644 gulpfile.js create mode 100644 lib/config-parser.js create mode 100644 lib/error-handler.js create mode 100644 lib/filter-parser.js create mode 100644 lib/fixtures.js create mode 100644 lib/logger.js create mode 100644 package.json create mode 100755 server.js create mode 100644 test/api/controllers/locations.spec.js create mode 100644 test/api/controllers/users.spec.js create mode 100644 test/common.js create mode 100644 test/fixtures/locations.json diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b02617b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.json] +indent_size = 2 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 066328f..af1c9fa 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,29 +1,21 @@ -# Limitando quais branches terão a build construída +image: node:4.2 -# 'only' e 'except' permitem limitar quando builds são executadas a partir -# de nomes de branches ou tags. Eles são 'inclusivos', ou seja, se um branch -# aparece em 'only' ele será executado, independente de aparecer em 'except' -# ou não. -# Expressões regulares são aceitas. +services: + - mongo:latest -# Roda a build somente se o branch começar com issue- -job1: - script: - - echo "Building" - # Gera build para todos os branches que começam com issue- - only: - - /^issue-.*$/ - # 'branches' é uma palavra-chave que significa 'todos os branches' - # Então essa linha garante que a build não será executada para nenhum - # branch que não casar com o 'only' - except: - - branches +before_script: + - npm config set cache /cache/npm + - npm install --silent -g gulp + - npm install --silent + - cp config/example.yaml config/test.yaml + - sed -i 's/database\x3a[ ]*\x27test\x27/database\x3a \x27simmc_test\x27/' config/test.yaml + - sed -i 's/host\x3a[ ]*\x27localhost\x27/host\x3a \x27mongo\x27/' config/test.yaml -# Roda a build em todos os branches menos no master -job2: - script: - - echo "Building" - only: - - branches - except: - - master +check: + type: test + script: + - gulp lint + - gulp test + tags: + - node + - mongo diff --git a/.jscsrc b/.jscsrc new file mode 100644 index 0000000..1b57e58 --- /dev/null +++ b/.jscsrc @@ -0,0 +1,21 @@ +{ + "preset": "airbnb", + "validateIndentation": 4, + "requireTrailingComma": false, + "disallowTrailingComma": true, + "requireCurlyBraces": [ + "if", + "else", + "for", + "while", + "do", + "try", + "catch" + ], + "requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties", + "disallowKeywordsOnNewLine": [], + "maximumLineLength": 80, + "requirePaddingNewLinesAfterBlocks": { + "allExcept": ["inCallExpressions", "inNewExpressions", "inArrayExpressions", "inProperties"] + } +} diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..58b3402 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,63 @@ +{ + /* + * ENVIRONMENTS + * ================= + */ + + // Define globals exposed by modern browsers. + "browser": false, + + // Define globals exposed by Node.js. + "node": true, + + // Allow ES6. + "esnext": true, + + /* + * ENFORCING OPTIONS + * ================= + */ + + // Prohibit use of == and != in favor of === and !==. + "eqeqeq": true, + + // Enforce tab width of 2 spaces. + "indent": 4, + + // Prohibit use of a variable before it is defined. + "latedef": false, + + // Require capitalized names for constructor functions. + "newcap": true, + + // Enforce use of single quotation marks for strings. + "quotmark": "single", + + // Prohibit use of explicitly undeclared variables. + "undef": true, + + // Warn when variables are defined but never used. + "unused": "vars", + + /* + * RELAXING OPTIONS + * ================= + */ + + // Suppress warnings about == null comparisons. + "eqnull": true, + + // Add global variables for Mocha and Chai + "globals": { + "describe": false, + "xdescribe": false, + "ddescribe": false, + "it": false, + "xit": false, + "iit": false, + "beforeEach": false, + "afterEach": false, + "before": false, + "after": false + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..36ab0a1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. {http://fsf.org/} + 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. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + 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 {http://www.gnu.org/licenses/}. + +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: + + solace-test Copyright (C) 2013 Eduardo Luis Buratti + 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 +{http://www.gnu.org/licenses/}. + + 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 +{http://www.gnu.org/philosophy/why-not-lgpl.html}. diff --git a/api/controllers/locations.js b/api/controllers/locations.js new file mode 100644 index 0000000..d42a274 --- /dev/null +++ b/api/controllers/locations.js @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana + * + * This file is part of simmc. + * + * simmc 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. + * + * simmc 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 simmc. If not, see <http://www.gnu.org/licenses/>. + */ + +var FilterParser = require('filter-parser'); +var createError = require('http-errors'); + +module.exports = { + list: list, + index: index, + read: read +}; + +var filterParser = new FilterParser({ + identifierParser: function(ident) { + // convert the identifier id into _id + if (ident === 'id') { + ident = '_id'; + } + + // only accept a few valid identifiers + if ((ident !== '_id') && + (ident !== 'name') && + (ident !== 'adminLevel') && + (ident !== 'extras.city_code')) { + throw new Error('Invalid identifier ' + ident); + } + + return ident; + } +}); + +function mapLocation(doc) { + return { + id: parseInt(doc.id), + score: doc.score, + name: doc.name, + adminLevel: doc.adminLevel, + border: doc.border, + subDivisions: doc.subDivisions, + extras: doc.extras + }; +} + +function list(req, res, next) { + var Location = req.db.model('Location'); + + // retrieve query parameters + var perPage = req.query.per_page || 10; + var page = req.query.page || 1; + var search = req.query.query; + var filters = req.query.filters; + + // build the query + var qQuery = {}; + var qProjection = { _id: 1, name: 1, adminLevel: 1, extras: 1 }; + var qSort = { _id: 1 }; + + if (filters) { + try { + qQuery = filterParser.parse(filters); + } + catch (err) { + next(createError.BadRequest( + 'Invalid filter expression. Check your syntax and try again.')); + return; + } + } + + if (search) { + qQuery.$text = { $search: search }; + qProjection.score = { $meta: 'textScore' }; + qSort = { score: { $meta: 'textScore' } }; + } + + var q = Location + .find(qQuery, qProjection) + .sort(qSort) + .skip((page - 1) * perPage) + .limit(perPage); + + // execute the query + q.exec(function(err, docs) { + if (err) { + req.log.error(err); + next(createError.BadRequest( + 'Unfortunately, the request you are making cannot be ' + + 'fulfilled by the server.')); + return; + } + + var locations = []; + docs.forEach(function(doc) { + locations.push(mapLocation(doc)); + }); + + res.status(200).json(locations); + }); +} + +function index(req, res, next) { + var Location = req.db.model('Location'); + + var q = Location + .find({}, { _id: 1, adminLevel: 1, name: 1 }) + .sort({ _id: 1 }); + + // execute the query + q.exec(function(err, docs) { + /* istanbul ignore if */ + if (err) { + req.log.error(err); + next(createError.InternalServerError()); + return; + } + + var result = { + keys: ['adminLevel', 'id', 'name'], + index: {} + }; + + docs.forEach(function(doc) { + if (!result.index[doc.adminLevel]) { + result.index[doc.adminLevel] = {}; + } + + result.index[doc.adminLevel][doc.id] = doc.name; + }); + + res.status(200).json(result); + }); +} + +function read(req, res, next) { + var Location = req.db.model('Location'); + + var locationId = req.params.locationId; + + var projection = { + _id: 1, + name: 1, + adminLevel: 1, + extras: 1 + }; + if (req.query.border) { + projection.border = 1; + } + + if (req.query.sub_divisions) { + projection.subDivisions = 1; + } + + Location.findOne({_id: locationId}, projection, function(err, doc) { + /* istanbul ignore if */ + if (err) { + req.log.error(err); + next(createError.InternalServerError()); + return; + } + + if (!doc) { + next(createError.NotFound( + 'Location \'' + locationId + '\' could not be found.')); + return; + } + + res.status(200).json(mapLocation(doc)); + }); +} diff --git a/api/controllers/users.js b/api/controllers/users.js new file mode 100644 index 0000000..68790a5 --- /dev/null +++ b/api/controllers/users.js @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana + * + * This file is part of simmc. + * + * simmc 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. + * + * simmc 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 simmc. If not, see <http://www.gnu.org/licenses/>. + */ + +module.exports = { + list: list, + create: create, + read: read, + update: update, + delete: del +}; + +function mapUser(doc) { + var user = { + id: String(doc.id), + email: doc.email, + firstname: doc.firstname, + lastname: doc.lastname, + tags: doc.tags, + created_at: doc.created_at, + updated_at: doc.updated_at + }; + + return user; +} + +function list(req, res) { + var User = req.db.model('User'); + + var perPage = req.query.per_page || 10; + var page = req.query.page || 1; + var offset = (page - 1) * perPage; + + var q = User + .find() + .skip(offset) + .limit(perPage); + + q.exec(function(err, docs) { + if (err) { + console.error(err); + res.status(500).json({ message: 'Database query failed.' }); + return; + } + + var users = []; + docs.forEach(function(doc) { + users.push(mapUser(doc)); + }); + + res.json(users); + }); +} + +function create(req, res) { + var User = req.db.model('User'); + + var userEmail = req.body.email; + var userPassword = req.body.password; + var userFirstname = req.body.firstname; + var userLastname = req.body.lastname; + var userTags = req.body.tags; + + // add comment + var doc = new User(); + doc.email = userEmail; + doc.password = userPassword; + doc.firstname = userFirstname; + doc.lastname = userLastname; + doc.tags = userTags; + doc.save(function(err) { + if (err) { + console.error(err); + res.status(500).json({ + message: 'Failed to save user to the database.' + }); + return; + } + + res.status(200).json(mapUser(doc)); + }); +} + +function read(req, res) { + var User = req.db.model('User'); + + var userId = req.params.userId; + + User.findOne({_id: userId}, function(err, doc) { + if (err) { + console.error(err); + res.status(500).json({ message: 'Database query failed.' }); + return; + } + + if (!doc) { + res.status(404).json({ message: 'User could not be found.' }); + return; + } + + res.status(200).json(mapUser(doc)); + }); +} + +function update(req, res) { + var User = req.db.model('User'); + + var userId = req.params.userId; + + var userEmail = req.body.email; + var userPassword = req.body.password; + var userFirstname = req.body.firstname; + var userLastname = req.body.lastname; + var userTags = req.body.tags; + + User.findOne({_id: userId}, function(err, doc) { + if (err) { + console.error(err); + res.status(500).json({ message: 'Database query failed.' }); + return; + } + + if (!doc) { + res.status(404).json({ message: 'User could not be found.' }); + return; + } + + doc.email = userEmail || doc.email; + doc.password = userPassword || doc.password; + doc.firstname = userFirstname || doc.firstname; + doc.lastname = userLastname || doc.lastname; + doc.tags = userTags || doc.tags; + doc.save(function(err) { + if (err) { + console.error(err); + res.status(500).json({ + message: 'Failed to save user to the database.' + }); + return; + } + + res.status(200).json(mapUser(doc)); + }); + }); +} + +function del(req, res) { + var User = req.db.model('User'); + + var userId = req.params.userId; + + User.findOne({_id: userId}, function(err, doc) { + if (err) { + console.error(err); + res.status(500).json({ message: 'Database query failed.' }); + return; + } + + if (!doc) { + res.status(404).json({ message: 'User could not be found.' }); + return; + } + + doc.remove(function(err) { + if (err) { + console.error(err); + res.status(500).json({ message: 'Database query failed.' }); + return; + } + + res.status(200).json({ message: 'User sucessufully removed.' }); + }); + }); +} diff --git a/api/middleware/mongodb.js b/api/middleware/mongodb.js new file mode 100644 index 0000000..e2639dc --- /dev/null +++ b/api/middleware/mongodb.js @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana + * + * This file is part of simmc. + * + * simmc 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. + * + * simmc 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 simmc. If not, see <http://www.gnu.org/licenses/>. + */ + +var mongoose = require('mongoose'); +var glob = require('glob'); +var path = require('path'); + +module.exports = function(config) { + var connectString = 'mongodb://' + config.get('mongodb.host') + ':' + + config.get('mongodb.port') + '/' + + config.get('mongodb.database'); + + mongoose.connect(connectString); + mongoose.connection.on('error', function(err) { + throw err; + }); + + glob.sync(path.resolve(__dirname, '../models/*.js')) + .forEach(function(file) { + require(file)(); + }); + + return function(req, res, next) { + req.db = mongoose; + next(); + }; +}; diff --git a/api/models/location.js b/api/models/location.js new file mode 100644 index 0000000..fa7143a --- /dev/null +++ b/api/models/location.js @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana + * + * This file is part of simmc. + * + * simmc 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. + * + * simmc 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 simmc. If not, see <http://www.gnu.org/licenses/>. + */ + +var mongoose = require('mongoose'); + +module.exports = function() { + var locationSchema = mongoose.Schema({ + _id: {type: Number, required: true}, + name: {type: String, required: true}, + adminLevel: {type: Number, required: true}, + border: {type: Object, required: true}, + subDivisions: {type: Object}, + extras: {type: mongoose.Schema.Types.Mixed} + }); + + // Workaround Alert: + // The following virtual is only needed as a placeholder for the + // calculated score of a text search on this collection. If this virtual + // is ommited, mongoose hides the 'score' field on the final document + // returned on queries. + locationSchema.virtual('score'); + + locationSchema.index({ adminLevel: 1 }); + + locationSchema.index({ name: 'text', 'extras.city_code': 'text' }, + { default_language: 'portuguese' }); + + return mongoose.model('Location', locationSchema); +}; diff --git a/api/models/user.js b/api/models/user.js new file mode 100644 index 0000000..13f6a19 --- /dev/null +++ b/api/models/user.js @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana + * + * This file is part of simmc. + * + * simmc 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. + * + * simmc 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 simmc. If not, see <http://www.gnu.org/licenses/>. + */ + +var mongoose = require('mongoose'); +var bcrypt = require('bcrypt'); + +var BCRYPT_SALT_WORK_FACTOR = 10; +var MAX_NUMBER_OF_TAGS = 256; + +module.exports = function() { + var userSchema = mongoose.Schema({ + email: {type: String, required: true, unique: true }, + password: {type: String, required: true }, + firstname: {type: String}, + lastname: {type: String}, + tags: [String], + created_at: {type: Date }, + updated_at: {type: Date } + }); + + userSchema.pre('save', function(next) { + var _this = this; + var now = new Date(); + + // set/update the created_at and updated_at fields + _this.updated_at = now; + if (!_this.created_at) { + _this.created_at = now; + } + + // hash the password (only if has been modified or is new) + if (!_this.isModified('password')) { + next(); + } else { + // generate a salt + bcrypt.genSalt(BCRYPT_SALT_WORK_FACTOR, function(err, salt) { + if (err) { + return next(err); + } + + // hash the password along with our new salt + bcrypt.hash(_this.password, salt, function(err, hash) { + if (err) { + return next(err); + } + + // override the cleartext password with the hashed one + _this.password = hash; + next(); + }); + }); + } + }); + + function validateEmail(value) { + if ((value.length < 4) || (value.length > 255)) { + return false; + } + + if (!value.match(/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)) { + return false; + } + + return true; + } + + function validatePassword(value) { + if ((value.length < 8) || (value.length > 256)) { + return false; + } + + if (!value.match(/^[a-zA-Z0-9$-\/:-?{-~!\"^_`\[\]]*$/)) { + return false; + } + + return true; + } + + function validateTags(value) { + if (value.length > MAX_NUMBER_OF_TAGS) { + return false; + } + + for (var i = 0; i < value.length; i++) { + if (!value[i].match(/^[A-Za-z]+[A-Za-z0-9.-_]+$/)) { + return false; + } + + if (value[i].length > 28) { + return false; + } + } + + return true; + } + + function validateName(value) { + if ((value.length < 3) || (value.length > 28)) { + return false; + } + + return true; + } + + userSchema.path('email').validate(validateEmail); + userSchema.path('password').validate(validatePassword); + userSchema.path('tags').validate(validateTags); + userSchema.path('firstname').validate(validateName); + userSchema.path('lastname').validate(validateName); + + return mongoose.model('User', userSchema); +}; diff --git a/api/router-v1.js b/api/router-v1.js new file mode 100644 index 0000000..ec5967d --- /dev/null +++ b/api/router-v1.js @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana + * + * This file is part of simmc. + * + * simmc 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. + * + * simmc 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 simmc. If not, see <http://www.gnu.org/licenses/>. + */ + +var osprey = require('osprey'); + +// import controllers +var users = require('./controllers/users'); +var locations = require('./controllers/locations'); + +var router = module.exports = osprey.Router(); + +router.get('/users', users.list); +router.post('/users', users.create); +router.get('/users/{userId}', { userId: { type: 'string' } }, users.read); +router.put('/users/{userId}', { userId: { type: 'string' } }, users.update); +router.delete('/users/{userId}', { userId: { type: 'string' } }, users.delete); + +router.get('/locations', locations.list); +router.get('/locations/_index', locations.index); +router.get('/locations/{locationId}', + { locationId: { type: 'integer' } }, locations.read); diff --git a/api/specs/examples/body/user.json b/api/specs/examples/body/user.json new file mode 100644 index 0000000..5a25512 --- /dev/null +++ b/api/specs/examples/body/user.json @@ -0,0 +1,6 @@ +{ + "email": "email@something.com", + "password": "somerandompassword", + "firstname": "Name", + "tags": ["SomeProject", "AnotherProject"] +} diff --git a/api/specs/examples/collections/dimensions.json b/api/specs/examples/collections/dimensions.json new file mode 100644 index 0000000..e69de29 diff --git a/api/specs/examples/collections/locations.json b/api/specs/examples/collections/locations.json new file mode 100644 index 0000000..3bb295f --- /dev/null +++ b/api/specs/examples/collections/locations.json @@ -0,0 +1,26 @@ +[ + { + "id": "11", + "name": "RONDÔNIA", + "adminLevel": 4, + "extras": {} + }, + { + "id": "14", + "name": "RORAIMA", + "adminLevel": 4, + "extras": {} + }, + { + "id": "13", + "name": "AMAZONAS", + "adminLevel": 4, + "extras": {} + }, + { + "id": "16", + "name": "AMAPÁ", + "adminLevel": 4, + "extras": {} + } +] diff --git a/api/specs/examples/collections/metrics.json b/api/specs/examples/collections/metrics.json new file mode 100644 index 0000000..e69de29 diff --git a/api/specs/examples/collections/points.json b/api/specs/examples/collections/points.json new file mode 100644 index 0000000..e69de29 diff --git a/api/specs/examples/collections/tags.json b/api/specs/examples/collections/tags.json new file mode 100644 index 0000000..e69de29 diff --git a/api/specs/examples/collections/users.json b/api/specs/examples/collections/users.json new file mode 100644 index 0000000..40b2e55 --- /dev/null +++ b/api/specs/examples/collections/users.json @@ -0,0 +1,31 @@ +[ + { + "id": "5639ff8eeca3b4f8334e9917", + "email": "email@something.com", + "firstname": "Name", + "tags": [ + "SomeProject", + "AnotherProject" + ], + "created_at": "2015-11-04T12:52:30.781Z", + "updated_at": "2015-11-04T12:52:30.781Z" + }, + { + "id": "558acc151806abf713891b6c", + "email": "another@user.me", + "tags": [], + "created_at": "2015-06-24T15:26:13.281Z", + "updated_at": "2015-06-24T15:26:13.281Z" + }, + { + "id": "5638a4c39517775d37a506b1", + "email": "yet@another.biz", + "firstname": "John", + "lastname": "Doe", + "tags": [ + "SomeProject" + ], + "created_at": "2015-11-03T12:12:51.855Z", + "updated_at": "2015-11-03T12:12:51.855Z" + } +] diff --git a/api/specs/examples/data.json b/api/specs/examples/data.json new file mode 100644 index 0000000..e69de29 diff --git a/api/specs/examples/items/location.json b/api/specs/examples/items/location.json new file mode 100644 index 0000000..cb94b59 --- /dev/null +++ b/api/specs/examples/items/location.json @@ -0,0 +1,8 @@ +{ + "id": "3528858", + "name": "MARAPOAMA", + "adminLevel": 8, + "extras": { + "city_code": "MARP" + } +} diff --git a/api/specs/examples/items/point.json b/api/specs/examples/items/point.json new file mode 100644 index 0000000..e69de29 diff --git a/api/specs/examples/items/tag.json b/api/specs/examples/items/tag.json new file mode 100644 index 0000000..e69de29 diff --git a/api/specs/examples/items/user.json b/api/specs/examples/items/user.json new file mode 100644 index 0000000..09868df --- /dev/null +++ b/api/specs/examples/items/user.json @@ -0,0 +1,11 @@ +{ + "id": "5639ff8eeca3b4f8334e9917", + "email": "email@something.com", + "firstname": "Name", + "tags": [ + "SomeProject", + "AnotherProject" + ], + "created_at": "2015-11-04T12:52:30.781Z", + "updated_at": "2015-11-04T12:52:30.781Z" +} diff --git a/api/specs/examples/report.json b/api/specs/examples/report.json new file mode 100644 index 0000000..e69de29 diff --git a/api/specs/schemas/body/user.json b/api/specs/schemas/body/user.json new file mode 100644 index 0000000..e1d5f81 --- /dev/null +++ b/api/specs/schemas/body/user.json @@ -0,0 +1,25 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema", + "id": "http://jsonschema.net", + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "firstname": { + "type": "string" + }, + "lastname": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + } + } +} diff --git a/api/specs/schemas/collections/dimensions.json b/api/specs/schemas/collections/dimensions.json new file mode 100644 index 0000000..e69de29 diff --git a/api/specs/schemas/collections/locations.json b/api/specs/schemas/collections/locations.json new file mode 100644 index 0000000..d782710 --- /dev/null +++ b/api/specs/schemas/collections/locations.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema", + "id": "http://jsonschema.net", + "type": "array", + "items": { "$ref": "file:api/specs/schemas/items/location.json" } +} diff --git a/api/specs/schemas/collections/metrics.json b/api/specs/schemas/collections/metrics.json new file mode 100644 index 0000000..e69de29 diff --git a/api/specs/schemas/collections/points.json b/api/specs/schemas/collections/points.json new file mode 100644 index 0000000..e69de29 diff --git a/api/specs/schemas/collections/tags.json b/api/specs/schemas/collections/tags.json new file mode 100644 index 0000000..e69de29 diff --git a/api/specs/schemas/collections/users.json b/api/specs/schemas/collections/users.json new file mode 100644 index 0000000..3b582f7 --- /dev/null +++ b/api/specs/schemas/collections/users.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema", + "id": "http://jsonschema.net", + "type": "array", + "items": { "$ref": "file:api/specs/schemas/items/user.json" } +} diff --git a/api/specs/schemas/data.json b/api/specs/schemas/data.json new file mode 100644 index 0000000..e69de29 diff --git a/api/specs/schemas/items/location.json b/api/specs/schemas/items/location.json new file mode 100644 index 0000000..2a91d21 --- /dev/null +++ b/api/specs/schemas/items/location.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema", + "id": "http://jsonschema.net", + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "adminLevel": { + "type": "integer" + }, + "border": { + "type": "object" + }, + "subDivisions": { + "type": "array", + "items": { + "type": "object" + } + }, + "extras": { + "type": "object" + } + }, + "required": ["id", "name", "adminLevel"] +} diff --git a/api/specs/schemas/items/point.json b/api/specs/schemas/items/point.json new file mode 100644 index 0000000..e69de29 diff --git a/api/specs/schemas/items/tag.json b/api/specs/schemas/items/tag.json new file mode 100644 index 0000000..e69de29 diff --git a/api/specs/schemas/items/user.json b/api/specs/schemas/items/user.json new file mode 100644 index 0000000..276f018 --- /dev/null +++ b/api/specs/schemas/items/user.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema", + "id": "http://jsonschema.net", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "email": { + "type": "string" + }, + "firstname": { + "type": "string" + }, + "lastname": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "required": ["id", "email"] +} diff --git a/api/specs/schemas/message.json b/api/specs/schemas/message.json new file mode 100644 index 0000000..b414486 --- /dev/null +++ b/api/specs/schemas/message.json @@ -0,0 +1,15 @@ +{ + "$schema": "http://json-schema.org/draft-03/schema", + "id": "http://jsonschema.net", + "type": "object", + "properties": { + "id": { + "type": "string", + "required": true + }, + "message": { + "type": "string", + "required": true + } + } +} diff --git a/api/specs/schemas/report.json b/api/specs/schemas/report.json new file mode 100644 index 0000000..e69de29 diff --git a/api/specs/simmc-api-v1.raml b/api/specs/simmc-api-v1.raml new file mode 100644 index 0000000..04e564c --- /dev/null +++ b/api/specs/simmc-api-v1.raml @@ -0,0 +1,612 @@ +#%RAML 0.8 + +title: SIMMC API +version: v1 +baseUri: http://simmc.c3sl.ufpr.br/api/{version} +mediaType: application/json + +securitySchemes: + - oauth_2_0: + description: | + OAuth2 is a protocol that lets apps request authorization to + private details in the system while avoiding the use of passwords. + This is preferred over Basic Authentication because tokens can be + limited to specific types of data, and can be revoked by users at + any time. + type: OAuth 2.0 + describedBy: + headers: + Authorization: + description: | + Used to send a valid OAuth 2 access token. Do not use + together with the "access_token" query string parameter. + type: string + queryParameters: + access_token: + description: | + Used to send a valid OAuth 2 access token. Do not use + together with the "Authorization" header. + type: string + responses: + 401: + description: | + Bad or expired token. This can happen if access token + has expired or has been revoked by the user. + body: + application/json: + schema: !include schemas/message.json + example: | + { + id: "invalid_oauth_token", + message: "Bad or expired token. This can happen if access token has expired or has been revoked by the user." + } + 403: + description: | + Bad OAuth2 request (wrong consumer key, bad nonce, + expired timestamp, ...). + body: + application/json: + schema: !include schemas/message.json + example: | + { + id: "invalid_oauth_request", + message: "Bad OAuth2 request (wrong consumer key, bad nonce, expired timestamp, ...)." + } + settings: + authorizationUri: http://simmc.c3sl.ufpr.br/oauth/authorize + accessTokenUri: http://simmc.c3sl.ufpr.br/oauth/access_token + authorizationGrants: [ code, token ] + scopes: + - "user" + - "user:email" + +resourceTypes: + - base: + get?: &common + responses: + 403: + description: API rate limit exceeded. + headers: + X-RateLimit-Limit: + type: integer + X-RateLimit-Remaining: + type: integer + X-RateLimit-Reset: + type: integer + body: + application/json: + schema: !include schemas/message.json + example: | + { + id: "too_many_requests", + message: "API Rate limit exceeded." + } + post?: *common + put?: *common + delete?: *common + - collection: + type: base + get?: + description: | + List all of the <<resourcePathName>> (with optional + filtering). + responses: + 200: + description: | + A list of <<resourcePathName>>. + body: + application/json: + schema: <<collectionSchema>> + example: <<collectionExample>> + post?: + description: | + Create a new <<resourcePathName|!singularize>>. + responses: + 201: + description: | + Sucessfully created a new + <<resourcePathName|!singularize>>. + headers: + Location: + description: | + A link to the newly created + <<resourcePathName|!singularize>>. + type: string + 409: + description: | + Failed to create a new + <<resourcePathName|!singularize>> because a conflict + with an already existing + <<resourcePathName|!singularize>> was detected. + body: + application/json: + schema: !include schemas/message.json + example: | + { + "id": "already_exists", + "message": "The <<resourcePathName|!singularize>> could not be created due to a conflict with an already existing <<resourcePathName|!singularize>>." + } + - item: + type: base + get?: + description: | + Return a single <<resourcePathName|!singularize>>. + responses: + 200: + description: | + A single <<resourcePathName|!singularize>>. + body: + application/json: + schema: <<itemSchema>> + example: <<itemExample>> + 404: + description: | + The <<resourcePathName|!singularize>> could not be + found. + body: + application/json: + schema: !include schemas/message.json + example: | + { + "id": "not_found", + "message": "The <<resourcePathName|!singularize>> could not be found." + } + put?: + description: | + Update a <<resourcePathName>>. + responses: + 204: + description: | + The <<resourcePathName|!singularize>> was updated. + 404: + description: | + The <<resourcePathName|!singularize>> could not be + found. + body: + application/json: + schema: !include schemas/message.json + example: | + { + "id": "not_found", + "message": "The <<resourcePathName|!singularize>> could not be found." + } + 409: + description: | + Failed to update the <<resourcePathName|!singularize>> + because a conflict with another + <<resourcePathName|!singularize>> was detected. + body: + application/json: + schema: !include schemas/message.json + example: | + { + "id": "already_exists", + "message": "Failed to update the <<resourcePathName|!singularize>> because a conflict with another <<resourcePathName|!singularize>> was detected." + } + patch?: + description: | + Partially update a <<resourcePathName>>. + responses: + 204: + description: | + The <<resourcePathName|!singularize>> was updated. + 404: + description: | + The <<resourcePathName|!singularize>> could not be + found. + body: + application/json: + schema: !include schemas/message.json + example: | + { + "id": "not_found", + "message": "The <<resourcePathName|!singularize>> could not be found." + } + 409: + description: | + Failed to update the <<resourcePathName|!singularize>> + because a conflict with another + <<resourcePathName|!singularize>> was detected. + body: + application/json: + schema: !include schemas/message.json + example: | + { + "id": "already_exists", + "message": "Failed to update the <<resourcePathName|!singularize>> because a conflict with another <<resourcePathName|!singularize>> was detected." + } + delete?: + description: | + Removes a <<resourcePathName>>. + responses: + 204: + description: | + The <<resourcePathName|!singularize>> was removed. + 404: + description: | + The <<resourcePathName|!singularize>> could not be + found. + body: + application/json: + schema: !include schemas/message.json + example: | + { + "id": "not_found", + "message": "The <<resourcePathName|!singularize>> could not be found." + } + - index: + type: base + get?: + description: | + Return an index on the <<resourcePathName>> collection. + responses: + 200: + description: | + An index on the <<resourcePathName>> collection. + body: + application/json: + +traits: + - paged: + queryParameters: + page: + description: Specify the page that you want to retrieve + type: integer + default: 1 + example: 1 + per_page: + description: The number of items to return per page + type: integer + minimum: 1 + maximum: 50 + default: 10 + example: 20 + - searchable: + queryParameters: + query: + description: | + Query string that filters the data returned for your + request. + type: string + - filtered: + queryParameters: + filters: + description: | + Filters that restrict the data returned for your request. + type: string + +/users: + description: | + Management of users and their tags. + securedBy: [ oauth_2_0 ] + type: + collection: + collectionSchema: !include schemas/collections/users.json + collectionExample: !include examples/collections/users.json + get: + is: [ paged, searchable, filtered ] + post: + body: + application/json: + schema: !include schemas/body/user.json + example: !include examples/body/user.json + /{userId}: + securedBy: [ oauth_2_0 ] + type: + item: + itemSchema: !include schemas/items/user.json + itemExample: !include examples/items/user.json + uriParameters: + userId: + description: User ID + type: string + get: + put: + body: + application/json: + schema: !include schemas/body/user.json + example: !include examples/body/user.json + delete: + +/locations: + description: | + A Location represents the topological and geographical information about + a single region in space and it may contain other locations. Each location + has an administrative level (analogous to OpenStreetMap's administrative + level) which represents it's hierarchical level. For example, if we + want to represent a single state, by OpenSteetMap standards, we + would have a Location with administrative level 4. All the cities + belonging to it would also be locations, but with administrative level 8. + The only way to figure out who is the parent of a Location is by using + it's ID number. Therefore, this ID number must be hierarchical, which means + that all children of a Location must have their ID number prefixed with + it's own ID. For example, by knowing that the ID number of a Location is + 15309, it is possible to figure out that it's parent ID is 1530 (and so on). + type: + collection: + collectionSchema: !include schemas/collections/locations.json + collectionExample: !include examples/collections/locations.json + get: + is: [ paged, searchable, filtered ] + description: List all the locations available. + /_index: + type: index + get: + /{locationId}: + type: + item: + itemSchema: !include schemas/items/location.json + itemExample: !include examples/items/location.json + uriParameters: + locationId: + description: Location ID + type: integer + get: + description: | + Return a single Location by it's ID. It's geographical + information (in GeoJSON format, embeded in the document) + also gets returned if the parameter "border" is set to "true". + If the parameter "sub_divisions" is set to "true", the + geographical information of it's direct children will also be + returned. + queryParameters: + border: + description: | + Whether or not to return the GeoJSON of the Location's + geometric border. + type: boolean + sub_divisions: + description: | + Whether or not to return the GeoJSON of the Location's + direct children borders (i.e., if the location is a + state, the borders of each city inside the state will be + returned). + type: boolean + +/points: + description: | + Points are the basic unit of monitoring. Each point is associated + with a project and a specific location. + securedBy: [ null, oauth_2_0 ] + type: + collection: + collectionSchema: !include schemas/collections/points.json + collectionExample: !include examples/collections/points.json + get: + is: [ paged, searchable, filtered ] + post: + /{id}: + securedBy: [ null, oauth_2_0 ] + type: + item: + itemSchema: !include schemas/items/point.json + itemExample: !include examples/items/point.json + uriParameters: + id: + description: Point ID + type: integer + get: + put: + patch: + delete: + +/tags: + description: | + A Tag can be used to group multiple access points. This is useful for + aggregating data and also for security purpouses. Users may have a set + of tags that allow them to access certain access points if the tags + match. + securedBy: [ null, oauth_2_0 ] + type: + collection: + collectionSchema: !include schemas/collections/tags.json + collectionExample: !include examples/collections/tags.json + get: + /{tag_id}: + securedBy: [ null, oauth_2_0 ] + type: + item: + itemSchema: !include schemas/items/tag.json + itemExample: !include examples/items/tag.json + uriParameters: + tag_id: + description: Unique tag identifier + type: string + get: + put: + patch: + delete: + +/metrics: + description: | + A Metric represents a statistic that can be queried to generate reports. + This collection allows the user to list all the metrics available in the + system and their descriptions. + securedBy: [ null, oauth_2_0 ] + type: + collection: + collectionSchema: !include schemas/collections/metrics.json + collectionExample: !include examples/collections/metrics.json + get: + +/dimensions: + description: | + A Dimension allows the data to be aggregated by one or more columns. + This collection allows the user to list all the dimensions available in + the system and their descriptions. + securedBy: [ null, oauth_2_0 ] + type: + collection: + collectionSchema: !include schemas/collections/dimensions.json + collectionExample: !include examples/collections/dimensions.json + get: + +/data: + description: | + This is the main part of the API. You may query it for report + data by specifying metrics (at least one). You may also supply + additional query parameters such as dimensions, filters, and + start/end dates to refine your query. + type: base + get: + is: [ filtered ] + queryParameters: + metrics: + description: | + A list of comma-separated metrics. + type: string + required: true + example: "met:daysSinceLastContact,met:estimatedNetworkBandwidth" + dimensions: + description: | + A list of comma-separated dimensions. + type: string + required: true + example: "dim:project,dim:point" + start-date: + description: | + Start date for fetching data. Requests can specify a + start date formatted as YYYY-MM-DD, or as a relative date + (e.g., today, yesterday, or NdaysAgo where N is a positive + integer). + type: string + required: false + pattern: "[0-9]{4}-[0-9]{2}-[0-9]{2}|today|yesterday|[0-9]+(daysAgo)" + example: 7daysAgo + end-date: + description: | + End date for fetching data. Requests can specify a + end date formatted as YYYY-MM-DD, or as a relative date + (e.g., today, yesterday, or NdaysAgo where N is a positive + integer). + type: string + required: false + pattern: "[0-9]{4}-[0-9]{2}-[0-9]{2}|today|yesterday|[0-9]+(daysAgo)" + example: yesterday + filters: + description: | + Filters that restrict the data returned for your request. + type: string + example: "dim:location(4).id%3D%3D10723" + sort: + description: | + A list of comma-separated dimensions and metrics + indicating the sorting order and sorting direction for + the returned data. + type: string + example: "dim:project" + responses: + 200: + description: | + Query successfully executed. Data is returned in a table format. + body: + application/json: + schema: !include schemas/data.json + example: !include examples/data.json + 400: + description: | + The supplied query is invalid. Specified metric or dimension + doesn't exist, incorrect formatting for a filter, unnaceptable + date range, etc. + body: + application/json: + schema: !include schemas/message.json + example: | + { + "id": "metric_not_found", + "message": "The specified metric 'met:electricCharge' could not be found." + } + +/reports: + type: base + get: + description: | + This API may be used to generated predefined (by the developer) + reports in various formats, like PDF, CSV, etc. + queryParameters: + reportName: + description: The name of report template to use. + type: string + reportFormat: + description: The format to export the report to. + type: string + responses: + 200: + description: | + Report build started successfully. + body: + application/json: + schema: !include schemas/report.json + example: !include examples/report.json + 400: + description: | + The supplied reportName is invalid or missing. + body: + application/json: + schema: !include schemas/message.json + example: | + { + "id": "report_not_found", + "message": "The specified report 'gesacDifferentAlert' could not be found." + } + /{build_id}: + uriParameters: + build_id: + description: | + The ID of the build task as returned for the build request. + type: string + get: + description: | + Query the status of a report build task. + responses: + 200: + description: | + Returns the status of the build task. + body: + application/json: + schema: !include schemas/report.json + example: !include examples/report.json + 400: + description: | + The supplied build_id is invalid or missing. + body: + application/json: + schema: !include schemas/message.json + example: | + { + "id": "task_not_found", + "message": "The specified build task 'a88c307d6' could not be found." + } + +/collect: + description: | + This API may be used to send data to the monitoring system. There are a + few available data types (like network bandwidth usage, machine + inventory, etc.) and each of them requires a specific format for the + data being sent. + type: base + post: + queryParameters: + type: + description: | + The type of data that is being sent. Available options are: + "networkBandwidth", "inventory", "userHistory". + type: string + required: true + example: "networkBandwidth" + responses: + 200: + description: | + Data has been successfully received and stored by the server. + 400: + description: | + An error has been found in your request. You may review your + request and the data that is being sent and try again later. + body: + application/json: + schema: !include schemas/message.json + example: | + { + "id": "invalid_attribute", + "message": "Invalid attribute \"memory\" for data type \"networkBandwidth\"." + } diff --git a/config/example.yaml b/config/example.yaml new file mode 100644 index 0000000..4ff2d57 --- /dev/null +++ b/config/example.yaml @@ -0,0 +1,4 @@ +mongodb: + host: 'localhost' + port: 27017 + database: 'test' diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..8171660 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,181 @@ +'use strict'; + +var gulp = require('gulp'); +var gutil = require('gulp-util'); +var raml = require('gulp-raml'); +var rename = require('gulp-rename'); +var jshint = require('gulp-jshint'); +var size = require('gulp-size'); +var jscs = require('gulp-jscs'); +var stylish = require('gulp-jscs-stylish'); +var mocha = require('gulp-mocha'); +var istanbul = require('gulp-istanbul'); +var nodemon = require('gulp-nodemon'); + + +var path = require('path'); +var raml2html = require('raml2html'); +var through = require('through2'); +var yaml = require('js-yaml'); + +var srcFiles = [ + 'api/**/*.js', + 'server.js', + 'gulpfile.js', + 'lib/**/*.js', + 'test/**/*.js' +]; + +function handleError(err) { + console.error(err.toString()); + process.exit(1); +} + +function generateDoc(options) { + var simplifyMark = function(mark) { + if (mark) { + mark.buffer = mark.buffer + .split('\n', mark.line + 1)[mark.line] + .trim(); + } + }; + + if (!options) { + options = {}; + } + + switch (options.type) { + case 'json': + options.config = { + template: function(obj) { + return JSON.stringify(obj, null, 2); + } + }; + break; + case 'yaml': + options.config = { + template: function(obj) { + return yaml.safeDump(obj, { + skipInvalid: true + }); + } + }; + break; + default: + options.type = 'html'; + if (!options.config) { + options.config = raml2html.getDefaultConfig( + options.https, + options.template, + options.resourceTemplate, + options.itemTemplate + ); + } + } + + if (!options.extension) { + options.extension = '.' + options.type; + } + + var stream = through.obj(function(file, enc, done) { + var fail = function(message) { + done(new gutil.PluginError('raml2html', message)); + }; + + if (file.isBuffer()) { + var cwd = process.cwd(); + process.chdir(path.resolve(path.dirname(file.path))); + raml2html + .render(file.contents, options.config) + .then(function(output) { + process.chdir(cwd); + stream.push(new gutil.File({ + base: file.base, + cwd: file.cwd, + path: gutil.replaceExtension( + file.path, options.extension), + contents: new Buffer(output) + })); + done(); + }, + function(error) { + process.chdir(cwd); + simplifyMark(error.context_mark); + simplifyMark(error.problem_mark); + process.nextTick(function() { + fail(JSON.stringify(error, null, 2)); + }); + } + ); + } + else if (file.isStream()) { + fail('Streams are not supported: ' + file.inspect()); + } + else if (file.isNull()) { + fail('Input file is null: ' + file.inspect()); + } + }); + + return stream; +} + +gulp.task('raml', function() { + gulp.src('api/specs/*.raml') + .pipe(raml()) + .on('error', handleError) + .pipe(raml.reporter('default')); +}); + +gulp.task('doc', function() { + return gulp.src('api/specs/*.raml') + .pipe(generateDoc()) + .on('error', handleError) + .pipe(rename({ extname: '.html' })) + .pipe(gulp.dest('doc/build')); +}); + +gulp.task('pre-test', function() { + return gulp.src(['api/**/*.js']) + .pipe(istanbul()) + .pipe(istanbul.hookRequire()); +}); + +gulp.task('test', ['pre-test'], function() { + return gulp.src('test/api/**/*.spec.js', {read: false}) + .pipe(mocha({ + require: ['./test/common.js'], + reporter: 'spec', + ui: 'bdd', + recursive: true, + colors: true, + timeout: 60000, + slow: 300, + delay: true + })) + .pipe(istanbul.writeReports()) + .once('error', function() { + process.exit(1); + }) + .once('end', function() { + process.exit(); + }); +}); + +gulp.task('lint', function() { + return gulp.src(srcFiles) + .pipe(jshint()) + .pipe(jscs()) + .pipe(stylish.combineWithHintResults()) + .pipe(jshint.reporter('jshint-stylish')) + .pipe(size()); +}); + +gulp.task('check', ['raml', 'lint', 'test']); + +gulp.task('develop', function() { + return nodemon({ + script: 'server.js', + ext: 'js', + tasks: ['lint'] + }); +}); diff --git a/lib/config-parser.js b/lib/config-parser.js new file mode 100644 index 0000000..cda1d15 --- /dev/null +++ b/lib/config-parser.js @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana + * + * This file is part of simmc. + * + * simmc 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. + * + * simmc 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 simmc. If not, see <http://www.gnu.org/licenses/>. + */ + +var fs = require('fs'); +var yaml = require('js-yaml'); +var _ = require('lodash'); + +module.exports = { + readFromFile: readFromFile +}; + +function readFromFile(file) { + if (!fs.existsSync(file)) { + throw new Error('Missing configuration file \'' + file + '\'.'); + } + + var conf = yaml.safeLoad(fs.readFileSync(file, 'utf8')); + return new Config(conf); +} + +function Config(conf) { + this._config = _.defaultsDeep(conf, { + mongodb: { + host: 'localhost', + port: 27017, + database: '' + } + }); + + this.get = function(path) { + return _.get(this._config, path); + }; + + this.set = function(path, value) { + _.set(this._config, path, value); + }; +} diff --git a/lib/error-handler.js b/lib/error-handler.js new file mode 100644 index 0000000..616abd4 --- /dev/null +++ b/lib/error-handler.js @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana + * + * This file is part of simmc. + * + * simmc 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. + * + * simmc 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 simmc. If not, see <http://www.gnu.org/licenses/>. + */ + +var statuses = require('statuses'); + +module.exports = errorHandler; + +function toIdentifier(str) { + return str.split(' ').map(function(token) { + return token.slice(0, 1).toUpperCase() + token.slice(1); + }).join('').replace(/[^ _0-9a-z]/gi, ''); +} + +function errorHandler(err, req, res, next) { + if (res.headersSent) { + // If headers are already sent, use the default express error + // handler, or else the connection will be closed by express + // and the request will be considered failed. + return next(err); + } + + err = formatError(err); + res.status(err.status); + res.json(err); +} + +function formatError(err) { + var res = { + status: 500, + id: 'InternalServerError', + message: 'An internal server error has occurred.' + + ' Sorry for the inconvenience.' + }; + + try { + if (typeof err !== 'object') { + return res; + } + + if (typeof err.status !== 'undefined') { + res.status = err.status; + } + + if (typeof err.id !== 'undefined') { + res.id = err.id; + } + else { + res.id = toIdentifier(statuses[res.status]); + } + + if ((err.message) && (err.expose)) { + res.message = err.message; + } + } + catch (err) { + // do nothing + } + + return res; +} diff --git a/lib/filter-parser.js b/lib/filter-parser.js new file mode 100644 index 0000000..9cf1e76 --- /dev/null +++ b/lib/filter-parser.js @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana + * + * This file is part of simmc. + * + * simmc 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. + * + * simmc 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 simmc. If not, see <http://www.gnu.org/licenses/>. + */ + +var jsep = require('jsep'); + +module.exports = FilterParser; + +function FilterParser(options) { + options = options || {}; + + this._parseIdentifier = options.identifierParser || + this.DefaultIdentifierParser; + this._parseOperator = options.operatorParser || + this.DefaultOperatorParser; +} + +FilterParser.prototype.parse = function(filters) { + var parseTree = jsep(filters); + return this._searchParseTree(parseTree); +}; + +FilterParser.prototype._searchParseTree = function(elem) { + switch (elem.type) { + case 'BinaryExpression': + var operator = this._parseOperator(elem.operator); + + var left = this._searchParseTree(elem.left); + var right = this._searchParseTree(elem.right); + + var ret = {}; + + if ((operator === '$or') || (operator === '$and')) { + ret[operator] = [left, right]; + } + else { + ret[left] = {}; + ret[left][operator] = right; + } + + return ret; + case 'UnaryExpression': + return { $not: this._searchParseTree(elem.argument) }; + case 'Identifier': + return this._parseIdentifier(elem.name); + case 'Literal': + return elem.value; + } +}; + +FilterParser.prototype.DefaultOperatorParser = function(op) { + switch (op) { + case '==': return '$eq'; + case '>': return '$gt'; + case '>=': return '$gte'; + case '<': return '$lt'; + case '<=': return '$lte'; + case '!=': return '$ne'; + case '|': return '$or'; + case '&': return '$and'; + case '!': return '$not'; + default: throw new Error('Unknown operator token: ' + op); + } +}; + +FilterParser.prototype.DefaultIdentifierParser = function(ident) { + return ident; +}; diff --git a/lib/fixtures.js b/lib/fixtures.js new file mode 100644 index 0000000..3244c6a --- /dev/null +++ b/lib/fixtures.js @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana + * + * This file is part of simmc. + * + * simmc 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. + * + * simmc 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 simmc. If not, see <http://www.gnu.org/licenses/>. + */ + +var fs = require('fs'); + +module.exports = { + load: loadFixtures +}; + +function loadFixtures(model, filename, callback) { + fs.readFile(filename, 'utf8', function(err, data) { + if (err) { + callback(err); + return; + } + + try { + var docs = JSON.parse(data); + } + catch (err) { + callback(err); + return; + } + + model.remove({}, function(err) { + if (err) { + callback(err); + return; + } + + model.create(docs, callback); + }); + }); +} diff --git a/lib/logger.js b/lib/logger.js new file mode 100644 index 0000000..89e828e --- /dev/null +++ b/lib/logger.js @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana + * + * This file is part of simmc. + * + * simmc 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. + * + * simmc 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 simmc. If not, see <http://www.gnu.org/licenses/>. + */ + +var bunyan = require('bunyan'); +var cuid = require('cuid'); +var fs = require('fs'); +var PrettyStream = require('bunyan-prettystream'); +var onFinished = require('on-finished'); +var onHeaders = require('on-headers'); + +// Make sure the logs dir exist +try { + fs.mkdirSync('logs'); +} +catch (e) { + if (e.code !== 'EEXIST') { + throw e; + } +} + +// Setup streams +var streams = []; + +// Default logger (info and above to file) +streams.push({ + level: 'info', + type: 'file', + path: 'logs/default.log' +}); + +// Log debug to file +streams.push({ + level: 'debug', + type: 'file', + path: 'logs/debug.log' +}); + +// if the process is running on a TTY (not piped), pretty print debug to stdout +if (process.stdout.isTTY) { + // wrap stdout in this PrettyStream to print log lines in a + // human-readable format + var prettyStdOut = new PrettyStream(); + prettyStdOut.pipe(process.stdout); + + streams.push({ + level: 'debug', + type: 'stream', + stream: prettyStdOut + }); +} + +// Create the default logger +var logger = module.exports = bunyan.createLogger({ + name: 'default', + streams: streams, + serializers: { + req: requestSerializer, + res: responseSerializer + } +}); + +// Access logger (log express requests) +logger.accessLogger = function(env) { + if (env === 'test') { + return function(req, res, next) { + // create a dummy logger that doesn't produce any output + req.log = bunyan.createLogger({ + name: 'access', + streams: [] + }); + next(); + }; + } + + return function(req, res, next) { + // insert the current time into the request + // (for later calculation of total reponse time). + req._startAt = process.hrtime(); + req._startTime = new Date(); + + // also, set response start time to undefined + // later this will be set to the time of the start of the response + res._startAt = undefined; + res._startTime = undefined; + + // Insert a log object into the request. + // The routes can use this object to log messages and all + // of them can be traced to the request (by req_id). + req.log = logger.child({ req_id: cuid.slug() }); + + // record response start + onHeaders(res, function() { + this._startAt = process.hrtime(); + this._startTime = new Date(); + }); + + // log when response finished + onFinished(res, function() { + var diff = process.hrtime(req._startAt); + var mili = (diff[0] * 1000) + (diff[1] / 1000000); + res.responseTime = mili.toFixed(1) + ' ms'; + + var msg = req.method + ' ' + req.url + ' ' + res.statusCode; + req.log.debug({req: req, res: res}, msg); + }); + + // Continue to the next handler + next(); + }; +}; + +// Serialize a request (remove unuseful information from requests, +// log only the necessary). +function requestSerializer(req) { + if ((typeof req !== 'object') || (req === null)) { + return req; + } + + var headers = req.headers || {}; + + return { + method: req.method, + url: req.url, + headers: { // selective choice of headers (no cookies) + Host: headers.host, + Connection: headers.connection, + Accept: headers.accept, + Referer: headers.referer, + 'Cache-Control': headers['cache-control'], + 'User-Agent': headers['user-agent'], + 'Accept-Encoding': headers['accept-encoding'], + 'Accept-Language': headers['accept-language'] + }, + remoteAddress: req.ips[0] || req.ip || null + }; +} + +// Serialize a reponse (remove unuseful information from responses +// and calculate the reponse time). +function responseSerializer(res) { + if ((typeof res !== 'object') || (res === null)) { + return res; + } + + return { + statusCode: res.statusCode, + responseTime: res.responseTime + }; +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..cf09a5d --- /dev/null +++ b/package.json @@ -0,0 +1,64 @@ +{ + "name": "simmc-api", + "version": "0.1.0", + "private": "true", + "description": "SIMMC API", + "homepage": "http://simmc.c3sl.ufpr.br", + "author": "Centro de Computação Científica e Software Livre - C3SL <contato@c3sl.ufpr.br>", + "license": "GPL-3.0", + "repository": { + "type": "git", + "url": "https://gitlab.c3sl.ufpr.br/minicom/simmc.git" + }, + "main": "server.js", + "dependencies": { + "app-module-path": "^1.0.4", + "bcrypt": "^0.8.5", + "body-parser": "^1.14.1", + "bunyan": "^1.5.1", + "bunyan-prettystream": "^0.1.3", + "cuid": "^1.3.8", + "express": "^4.13.3", + "glob": "^5.0.15", + "gulp": "^3.9.0", + "gulp-raml": "^0.1.3", + "gulp-rename": "^1.2.2", + "gulp-util": "^3.0.7", + "http-errors": "^1.3.1", + "js-yaml": "^3.4.3", + "jsep": "^0.3.0", + "lodash": "^3.10.1", + "mongoose": "^4.2.3", + "morgan": "^1.6.1", + "on-finished": "^2.3.0", + "on-headers": "^1.0.1", + "osprey": "0.2.0-beta.6", + "osprey-method-handler": "0.8.1", + "raml-parser": "^0.8.12", + "raml2html": "^2.1.0", + "statuses": "^1.2.1", + "through2": "^2.0.0", + "vinyl": "^1.1.0" + }, + "devDependencies": { + "chai": "^3.4.0", + "chai-things": "^0.2.0", + "gulp-coverage": "^0.3.38", + "gulp-istanbul": "^0.10.2", + "gulp-jscs": "^3.0.2", + "gulp-jscs-stylish": "^1.2.1", + "gulp-jshint": "^1.12.0", + "gulp-mocha": "^2.1.3", + "gulp-nodemon": "^2.0.4", + "gulp-size": "^2.0.0", + "gulp-util": "^3.0.7", + "istanbul": "^0.4.0", + "jshint-stylish": "^2.0.1", + "mocha": "^2.3.3", + "supertest": "^1.1.0" + }, + "scripts": { + "test": "gulp test", + "start": "node server.js" + } +} diff --git a/server.js b/server.js new file mode 100755 index 0000000..6f476e4 --- /dev/null +++ b/server.js @@ -0,0 +1,78 @@ +#!/usr/bin/env node +/* + * Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana + * + * This file is part of simmc. + * + * simmc 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. + * + * simmc 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 simmc. If not, see <http://www.gnu.org/licenses/>. + */ + +// Add the ./lib directory to require's search path to facilitate requiring +// libraries later on (avoiding the require('../../../../library.js') problem). +require('app-module-path').addPath(__dirname + '/lib'); + +// external libraries +var osprey = require('osprey'); +var express = require('express'); +var path = require('path'); +var ramlParser = require('raml-parser'); + +// internal libraries (from ./lib) +var configParser = require('config-parser'); +var errorHandler = require('error-handler'); +var logger = require('logger'); + +var app = module.exports = express(); + +// parse and load configuration +var config = configParser.readFromFile( + path.resolve(__dirname, './config/' + app.get('env') + '.yaml')); +app.set('port', process.env.PORT || config.get('port') || 3000); + +// add an access logger middleware +app.use(logger.accessLogger(app.get('env'))); + +// include middlewares +app.use(require('./api/middleware/mongodb.js')(config)); + +// load router +var router = require('./api/router-v1.js'); + +// parse the RAML spec and load osprey middleware +ramlParser.loadFile(path.join(__dirname, 'api/specs/simmc-api-v1.raml')) + .then(function(raml) { + app.use('/api/v1', + osprey.security(raml), + osprey.server(raml), + router, + errorHandler); + + if (!module.parent) { + var port = app.get('port'); + app.listen(port); + + if (app.get('env') === 'development') { + logger.info('Server listening on port ' + port + '.'); + } + } + else { + // signalize to the test suite that the server is ready to be tested + app.ready = true; + } + }, + function(err) { + logger.error('RAML Parsing Error: ' + err.message); + process.exit(1); + }); diff --git a/test/api/controllers/locations.spec.js b/test/api/controllers/locations.spec.js new file mode 100644 index 0000000..ef28775 --- /dev/null +++ b/test/api/controllers/locations.spec.js @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana + * + * This file is part of simmc. + * + * simmc 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. + * + * simmc 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 simmc. If not, see <http://www.gnu.org/licenses/>. + */ + +/* global server:false */ +/* jscs:disable maximumLineLength */ +/* jshint expr:true */ + +var request = require('supertest'); +var chai = require('chai'); +var expect = chai.expect; +chai.use(require('chai-things')); + +var path = require('path'); +var mongoose = require('mongoose'); +var fixtures = require('fixtures'); + +describe('locations api', function() { + before(function(done) { + var Location = mongoose.model('Location'); + + fixtures.load(Location, + path.resolve(__dirname, '../../fixtures/locations.json'), done); + }); + + it('should return the 10 first locations ordered by id', function(done) { + request(server) + .get('/api/v1/locations') + .expect('Content-Type', /json/) + .expect(200) + .expect(function(res) { + expect(res.body).to.be.an('array'); + expect(res.body).to.have.length(10); + }) + .end(done); + }); + + it('should return 35 locations per page when requested', function(done) { + request(server) + .get('/api/v1/locations?per_page=35') + .expect('Content-Type', /json/) + .expect(200) + .expect(function(res) { + expect(res.body).to.be.an('array'); + expect(res.body).to.have.length(35); + }) + .end(done); + }); + + it('should return locations filtered by administrative level', function(done) { + request(server) + .get('/api/v1/locations?filters=adminLevel%3D%3D8') + .expect('Content-Type', /json/) + .expect(200) + .expect(function(res) { + expect(res.body).to.be.an('array'); + expect(res.body).to.have.length(10); + expect(res.body).all.to.have.property('adminLevel', 8); + }) + .end(done); + }); + + it('should return locations filtered by administrative level and id number', function(done) { + request(server) + .get('/api/v1/locations?filters=adminLevel%3D%3D4%26id%3E%3D30%26id%3C40') + .expect('Content-Type', /json/) + .expect(200) + .expect(function(res) { + expect(res.body).to.be.an('array'); + expect(res.body).to.not.be.empty; + expect(res.body).all.to.have.property('adminLevel', 4); + expect(res.body).all.to.have.property('id'); + res.body.forEach(function(location) { + expect(location.id).to.be.at.least(30); + expect(location.id).to.be.below(40); + }); + }) + .end(done); + }); + + it('should return locations that match the query "curitiba"', function(done) { + request(server) + .get('/api/v1/locations?query=curitiba') + .expect('Content-Type', /json/) + .expect(200) + .expect(function(res) { + expect(res.body).to.be.an('array'); + expect(res.body).to.not.be.empty; + }) + .end(done); + }); + + it('should return BadRequest for an invalid filter expression (bad operator)', function(done) { + request(server) + .get('/api/v1/locations?filters=adminLevel%5E8') + .expect('Content-Type', /json/) + .expect(400) + .end(done); + }); + + it('should return BadRequest for an invalid filter expression (bad identifier)', function(done) { + request(server) + .get('/api/v1/locations?filters=invalidIdent%3D%3D8') + .expect('Content-Type', /json/) + .expect(400) + .end(done); + }); + + it('should return BadRequest for an invalid filter expression (bad expression)', function(done) { + request(server) + .get('/api/v1/locations?filters=adminLevel%3D%3DadminLevel') + .expect('Content-Type', /json/) + .expect(400) + .end(done); + }); + + it('should return an index with all locations', function(done) { + request(server) + .get('/api/v1/locations/_index') + .expect('Content-Type', /json/) + .expect(200) + .expect(function(res) { + expect(res.body).to.be.an('object'); + expect(res.body).to.have.property('keys'); + expect(res.body).to.have.property('index'); + expect(res.body.keys).to.be.eql(['adminLevel', 'id', 'name']); + expect(res.body.index).to.be.an('object'); + expect(res.body.index).to.have.property('8'); + expect(res.body.index[8]).to.have.property('1100908'); + expect(res.body.index[8][1100908]).to.be.equal('CASTANHEIRAS'); + }) + .end(done); + }); + + it('should return the location corresponding to the state Paraná', function(done) { + request(server) + .get('/api/v1/locations/41') + .expect('Content-Type', /json/) + .expect(200) + .expect(function(res) { + expect(res.body).to.be.an('object'); + expect(res.body).to.have.property('name', 'PARANÁ'); + expect(res.body).to.have.property('adminLevel', 4); + expect(res.body).to.not.have.property('border'); + expect(res.body).to.not.have.property('subDivisions'); + }) + .end(done); + }); + + it('should return the location corresponding to the state Paraná with subDivisions', function(done) { + request(server) + .get('/api/v1/locations/41?sub_divisions=true') + .expect('Content-Type', /json/) + .expect(200) + .expect(function(res) { + expect(res.body).to.be.an('object'); + expect(res.body).to.have.property('name', 'PARANÁ'); + expect(res.body).to.have.property('adminLevel', 4); + expect(res.body).to.not.have.property('border'); + expect(res.body).to.have.property('subDivisions'); + }) + .end(done); + }); + + it('should return the location corresponding to the city Curitiba', function(done) { + request(server) + .get('/api/v1/locations/4106902') + .expect('Content-Type', /json/) + .expect(200) + .expect(function(res) { + expect(res.body).to.be.an('object'); + expect(res.body).to.have.property('name', 'CURITIBA'); + expect(res.body).to.have.property('adminLevel', 8); + expect(res.body).to.not.have.property('border'); + expect(res.body).to.not.have.property('subDivisions'); + }) + .end(done); + }); + + it('should return the location corresponding to the city Curitiba with border', function(done) { + request(server) + .get('/api/v1/locations/4106902?border=1') + .expect('Content-Type', /json/) + .expect(200) + .expect(function(res) { + expect(res.body).to.be.an('object'); + expect(res.body).to.have.property('name', 'CURITIBA'); + expect(res.body).to.have.property('adminLevel', 8); + expect(res.body).to.have.property('border'); + expect(res.body).to.not.have.property('subDivisions'); + }) + .end(done); + }); + + it('should return 404 for a location that does not exist', function(done) { + request(server) + .get('/api/v1/locations/4117013') + .expect('Content-Type', /json/) + .expect(404) + .end(done); + }); + +}); diff --git a/test/api/controllers/users.spec.js b/test/api/controllers/users.spec.js new file mode 100644 index 0000000..9143899 --- /dev/null +++ b/test/api/controllers/users.spec.js @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana + * + * This file is part of simmc. + * + * simmc 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. + * + * simmc 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 simmc. If not, see <http://www.gnu.org/licenses/>. + */ + +/* global server:false */ +/* jscs:disable maximumLineLength */ +/* jshint expr:true */ + +var request = require('supertest'); +var chai = require('chai'); +var expect = chai.expect; +chai.use(require('chai-things')); + +describe('users api', function() { + it('should return all users', function(done) { + request(server) + .get('/api/v1/users') + .expect('Content-Type', /json/) + .expect(200) + .end(function(err, res) { + expect(err).to.not.exist; + + done(); + }); + }); +}); diff --git a/test/common.js b/test/common.js new file mode 100644 index 0000000..c466c09 --- /dev/null +++ b/test/common.js @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 Centro de Computacao Cientifica e Software Livre + * Departamento de Informatica - Universidade Federal do Parana + * + * This file is part of simmc. + * + * simmc 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. + * + * simmc 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 simmc. If not, see <http://www.gnu.org/licenses/>. + */ + +/* global server:false, run:false */ + +process.env.NODE_ENV = 'test'; + +global.server = require('../server'); + +// Wait for the server to be fully initialized before triggering the start of +// the tests (by calling run()). +(function checkServer() { + if (server.ready) { + run(); + return; + } + + setTimeout(function() { + checkServer(); + }, 100); +})(); diff --git a/test/fixtures/locations.json b/test/fixtures/locations.json new file mode 100644 index 0000000..04dd1a2 --- /dev/null +++ b/test/fixtures/locations.json @@ -0,0 +1,71 @@ +[ + { "_id" : 11, "name" : "RONDÔNIA", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 14, "name" : "RORAIMA", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 13, "name" : "AMAZONAS", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 16, "name" : "AMAPÁ", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 12, "name" : "ACRE", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 15, "name" : "PARÁ", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 22, "name" : "PIAUÍ", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 17, "name" : "TOCANTINS", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 24, "name" : "RIO GRANDE DO NORTE", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 25, "name" : "PARAÍBA", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 27, "name" : "ALAGOAS", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 28, "name" : "SERGIPE", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 21, "name" : "MARANHÃO", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 23, "name" : "CEARÁ", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 26, "name" : "PERNAMBUCO", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 29, "name" : "BAHIA", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 32, "name" : "ESPIRITO SANTO", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 31, "name" : "MINAS GERAIS", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 33, "name" : "RIO DE JANEIRO", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 35, "name" : "SÃO PAULO", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 41, "name" : "PARANÁ", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 50, "name" : "MATO GROSSO DO SUL", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 42, "name" : "SANTA CATARINA", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 53, "name" : "DISTRITO FEDERAL", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 43, "name" : "RIO GRANDE DO SUL", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 51, "name" : "MATO GROSSO", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 52, "name" : "GOIÁS", "adminLevel" : 4, "extras" : { }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 2409605, "name" : "PEDRA PRETA", "adminLevel" : 8, "extras" : { "city_code" : "PAP" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 2111904, "name" : "SUCUPIRA DO NORTE", "adminLevel" : 8, "extras" : { "city_code" : "SUC" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 2401701, "name" : "BOM JESUS", "adminLevel" : 8, "extras" : { "city_code" : "BOU" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 2109239, "name" : "PRESIDENTE MÉDICI", "adminLevel" : 8, "extras" : { "city_code" : "PEMD" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 2406403, "name" : "LAGOA DE VELHOS", "adminLevel" : 8, "extras" : { "city_code" : "LEL" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 1505601, "name" : "PEIXE-BOI", "adminLevel" : 8, "extras" : { "city_code" : "PXB" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 2414456, "name" : "TRIUNFO POTIGUAR", "adminLevel" : 8, "extras" : { "city_code" : "TRIU" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 1500107, "name" : "ABAETETUBA", "adminLevel" : 8, "extras" : { "city_code" : "ABT" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 1500131, "name" : "ABEL FIGUEIREDO", "adminLevel" : 8, "extras" : { "city_code" : "AFGO" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 1500206, "name" : "ACARÁ", "adminLevel" : 8, "extras" : { "city_code" : "AKA" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 1500305, "name" : "AFUÁ", "adminLevel" : 8, "extras" : { "city_code" : "AFU" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 1500347, "name" : "ÁGUA AZUL DO NORTE", "adminLevel" : 8, "extras" : { "city_code" : "AGAN" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 1500404, "name" : "ALENQUER", "adminLevel" : 8, "extras" : { "city_code" : "ALQ" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 1500503, "name" : "ALMEIRIM", "adminLevel" : 8, "extras" : { "city_code" : "AMM" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 1500701, "name" : "ANAJÁS", "adminLevel" : 8, "extras" : { "city_code" : "AAJ" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 1500800, "name" : "ANANINDEUA", "adminLevel" : 8, "extras" : { "city_code" : "AIU" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 1500909, "name" : "AUGUSTO CORRÊA", "adminLevel" : 8, "extras" : { "city_code" : "AGU" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 1500859, "name" : "ANAPU", "adminLevel" : 8, "extras" : { "city_code" : "ANAY" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 1500958, "name" : "AURORA DO PARÁ", "adminLevel" : 8, "extras" : { "city_code" : "AUPA" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 1500602, "name" : "ALTAMIRA", "adminLevel" : 8, "extras" : { "city_code" : "ATM" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4100103, "name" : "ABATIÁ", "adminLevel" : 8, "extras" : { "city_code" : "AAT" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4100202, "name" : "ADRIANÓPOLIS", "adminLevel" : 8, "extras" : { "city_code" : "ADP" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4100301, "name" : "AGUDOS DO SUL", "adminLevel" : 8, "extras" : { "city_code" : "ADU" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4100400, "name" : "ALMIRANTE TAMANDARÉ", "adminLevel" : 8, "extras" : { "city_code" : "ATT" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4100459, "name" : "ALTAMIRA DO PARANÁ", "adminLevel" : 8, "extras" : { "city_code" : "AMP" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4100509, "name" : "ALTÔNIA", "adminLevel" : 8, "extras" : { "city_code" : "AOA" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4100608, "name" : "ALTO PARANÁ", "adminLevel" : 8, "extras" : { "city_code" : "APA" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4100707, "name" : "ALTO PIQUIRI", "adminLevel" : 8, "extras" : { "city_code" : "ATQ" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4100806, "name" : "ALVORADA DO SUL", "adminLevel" : 8, "extras" : { "city_code" : "AVL" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4100905, "name" : "AMAPORÃ", "adminLevel" : 8, "extras" : { "city_code" : "APX" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4101002, "name" : "AMPÉRE", "adminLevel" : 8, "extras" : { "city_code" : "AEE" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4101051, "name" : "ANAHY", "adminLevel" : 8, "extras" : { "city_code" : "AAH" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4101101, "name" : "ANDIRÁ", "adminLevel" : 8, "extras" : { "city_code" : "AND" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4101150, "name" : "ÂNGULO", "adminLevel" : 8, "extras" : { "city_code" : "AWG" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4101200, "name" : "ANTONINA", "adminLevel" : 8, "extras" : { "city_code" : "AON" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4101309, "name" : "ANTÔNIO OLINTO", "adminLevel" : 8, "extras" : { "city_code" : "AOL" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4101408, "name" : "APUCARANA", "adminLevel" : 8, "extras" : { "city_code" : "APU" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4101507, "name" : "ARAPONGAS", "adminLevel" : 8, "extras" : { "city_code" : "APS" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4101606, "name" : "ARAPOTI", "adminLevel" : 8, "extras" : { "city_code" : "APO" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4101655, "name" : "ARAPUÃ", "adminLevel" : 8, "extras" : { "city_code" : "AAPA" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 1100908, "name" : "CASTANHEIRAS", "adminLevel" : 8, "extras" : { "city_code" : "CSTH" }, "subDivisions": { "features": [] }, "border": { "features": [] } }, + { "_id" : 4106902, "name" : "CURITIBA", "adminLevel" : 8, "extras" : { "city_code" : "CTA" }, "subDivisions": { "features": [] }, "border": { "features": [] } } +] -- GitLab