├── .gitignore ├── GiantSwarm └── guesser.json ├── LICENSE ├── README.md ├── docker ├── arangodb │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── guesser-foxx.zip │ └── scripts │ │ └── start.sh └── node │ ├── Dockerfile │ ├── Makefile │ ├── README.md │ ├── guesser-foxx.zip │ ├── guesser-node.tar │ └── scripts │ ├── install.sh │ └── start.sh ├── guesser.js ├── iojs ├── README.md ├── guesser_server.js ├── package.json └── static │ ├── base.css │ ├── bower.json │ ├── guesser_controller.js │ └── index.html ├── manifest.json └── scripts ├── setup.js └── teardown.js /.gitignore: -------------------------------------------------------------------------------- 1 | iojs/node_modules 2 | iojs/static/bower_components 3 | -------------------------------------------------------------------------------- /GiantSwarm/guesser.json: -------------------------------------------------------------------------------- 1 | { 2 | "app_name": "guesser", 3 | "services": [ 4 | { 5 | "service_name": "guesser-game", 6 | "components": [ 7 | { 8 | "component_name": "guesser-front-end", 9 | "image": "arangodb/example-guesser", 10 | "ports": [ "8000/tcp" ], 11 | "dependencies": [ 12 | { "name": "guesser-back-end", "port": 8529 } 13 | ], 14 | "domains": { "guesser.gigantic.io": "8000" } 15 | }, 16 | { 17 | "component_name": "guesser-back-end", 18 | "image": "arangodb/example-guesser-db", 19 | "ports": [ "8529/tcp" ] 20 | } 21 | ] 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building a self-learning game with ArangoDB, io.js/NodeJS & AngularJS in half a day. 2 | 3 | ## Introduction and Overview 4 | 5 | In this tutorial we want to illustrate a possible architecture of a web application using AngularJS for the frontend in the browser, io.js as application server and ArangoDB as backend database. We are particularly focusing on ArangoDB and its Foxx microservice framework, and only briefly show the io.js and AngularJS parts. In particular, this is not intended to be an AngularJS or io.js tutorial. We even use some shortcuts that one would usually not deploy in production to keep the app simple. Nevertheless, the architecture of the application is in principle suitable as a blueprint for an actual, larger web application. 6 | 7 | We are using io.js in the example. However the project will work using node.js as well. 8 | 9 | All necessary steps will be described in detail and you can follow the evolution of the system by looking at each stage without typing code yourself. 10 | 11 | Our web application will be a guessing game, in which the computer tries to guess a thing or animal you think of by asking a series of questions, for which you provide the answers. Finally, the computer will try to guess what you thought of, if it is wrong, it tries to learn from the experience, such that it can be successful the next time round. If you want to give it a try, surf to 12 | 13 | 14 | 15 | The data from this "machine learning" is kept persistent in the database. We use a single collection storing the questions as well as the guesses, which are organized in a binary tree. Each question node has a left and a right child for the two different answers, and each guess is a leaf in the tree. We do not use a graph data model for this since our queries will only be single document lookups. 16 | 17 | During the game the app will simply follow one path in the tree from the root to a leaf, depending on the answers by the user. It will then try a guess, if this is not correct, it will ask the user to provide the right answer and a new question to distinguish that answer from the guess, and finally change the tree, completing the learning process. 18 | 19 | The whole creation of the application works in ten basic steps: 20 | 21 | 1. [Fork the `git` repository and checkout the initial step.][1] 22 | 2. [Install application dependencies][2] 23 | 3. [Create a minimal web server using the `npm` module `express` and serve a welcome view][3] 24 | 4. [Install ArangoDB and set up a place for Foxx app development][4] 25 | 5. [Create a minimal Foxx app for the database services][5] 26 | 6. [Organize the setup and tear down: create a collection with initial data][6] 27 | 7. [Create the question asking view][7] 28 | 8. [Create the guessing view][8] 29 | 9. [Create the learning view][9] 30 | 10. [Deploy the Foxx app in ArangoDB][10] 31 | 32 | You will be able to follow the proceedings, because in the `git` repository that you clone in Step 1 there is a tag for the state after each of the 10 steps, such that you can look at all the code without typing a single line of source code. In the end you will be able to adapt the whole system easily to your own situation. 33 | 34 | ## The Creation 35 | 36 | Please note that to be able to run the application at any step you will at least have to do "bower install" in iojs/static/ and "npm install" in iojs/. They will install external dependencies which are NOT contained in the repository. 37 | 38 | ### Prerequisites 39 | 40 | #### Install io.js 41 | 42 | Make sure you have io.js (or node.js) installed. We will refer to "iojs" throughout this tutorial. However if you rather want to use node just replace "iojs" with "node" in any commands. 43 | 44 | To install io.js download the appropriate package for your operating system from 45 | 46 | 47 | 48 | and install it. We will assume that iojs will be in your $PATH for this tutorial. 49 | 50 | #### Install bower 51 | 52 | Bower will be used to manage the frontend dependencies. 53 | 54 | Install it using "npm install -g bower". Depending on your system you might have to execute this using sudo (-g installs it globally on your system). 55 | 56 | 57 | 58 | ### 1\. Fork the git repository and checkout the initial step. 59 | 60 | Simply do 61 | 62 | git clone https://github.com/ArangoDB/guesser.git 63 | cd guesser 64 | git checkout step1 65 | 66 | 67 | This will create a directory `guesser` in your current directory and will checkout the state after Step 1, you will only see a license file and a `README.md`. 68 | 69 | 70 | 71 | ### 2\. Install application dependencies 72 | 73 | To create the io.js backend application 74 | 75 | mkdir iojs 76 | cd iojs 77 | npm init -y 78 | npm install --save express arangojs promise concat-stream 79 | mkdir static 80 | 81 | The node package manager (npm) needs a file called `package.json` to work. This file is used to describe the project and its dependencies. We are using npms defaults during init to get the project running as quickly as possible. Have a look at [npm's documentation][11] for an in-depth explanation of the package format. 82 | 83 | The install command downloaded all necessary dependencies to develop a base io.js web application which uses arangodb as a database. 84 | 85 | We will put static content served by our io.js app in the `static` folder. 86 | 87 | To manage our frontend dependencies we will be using bower. 88 | 89 | Just as npm it has its own package format (on which we won't go into detail here). 90 | 91 | Execute 92 | 93 | cd static 94 | bower init 95 | 96 | This will create a bower.json package file in the static directory. For now don't bother about the details and simply press enter during the init wizard process. You should however check out [bowers documentation later][12]. 97 | 98 | To install AngularJS using bower execute: 99 | 100 | bower install --save angularjs 101 | 102 | This will create a directory "bower_components" and angularjs should be installed there. 103 | 104 | git checkout step2 105 | 106 | to see the state of our project after this step. Note that if you actually performed the commands in this step, then the `git checkout step2` will complain that it cannot overwrite some files. Simply delete them and do the `git checkout step2` again. 107 | 108 | 109 | 110 | ### 3\. Create a minimal web server using the `npm` module `express` and serve a welcome view 111 | 112 | We are now in a position to create a minimal web server serving some static content showing a welcome view. To this end, we create the files 113 | 114 | iojs/guesser_server.js 115 | iojs/static/index.html 116 | iojs/static/base.css 117 | iojs/static/guesser_controller.js 118 | 119 | 120 | The guesser server is an io.js application using the `express` package to serve some static pages for now. See the [AngularJS][13] and [express][14] manuals for explanations. Here it shall suffice to say that the file `index.html` contains the HTML for the single page, it includes the CSS style sheet `base.css`, the AngularJS library file `angular.min.js` and the controller file `guesser_controller.js`. The latter contains the JavaScript code that is executed in the web browser to take care of the actual user interface, and later to perform AJAX calls back to the io.js server. 121 | 122 | Use 123 | 124 | git checkout step3 125 | 126 | 127 | to see the state of our project after this step. After this, you can simply execute 128 | 129 | cd iojs 130 | iojs guesser_server.js 131 | 132 | 133 | to start a web server on `port 8000` of your local machine. If you visit 134 | 135 | [http://localhost:8000/][15] 136 | 137 | (replace `localhost` by the name or IP address of your server if you try this on a remote machine), you should see the starting page with a blue background and a single input field for your name. The button will not yet respond. 138 | 139 | To get a feeling of how easy it is to configure an `express` server, here is the code that organizes the delivery of a single static page: 140 | 141 | ```javascript 142 | var express = require('express'); 143 | var app = express(); 144 | app.use(express.static(__dirname + "/static")); 145 | ``` 146 | 147 | and here is the code that creates the actual web server: 148 | 149 | ```javascript 150 | var server = app.listen(8000, function () { 151 | var host = server.address().address 152 | var port = server.address().port 153 | console.log('Guesser app server listening at http://%s:%s', host, port) 154 | }); 155 | ``` 156 | 157 | The HTML page is a standard one with a few AngularJS directives, the AngularJS controller is as yet only a stub. 158 | 159 | 160 | 161 | ### 4\. Install ArangoDB and set up a place for Foxx app development 162 | 163 | Download and install ArangoDB as described on the page 164 | 165 | 166 | 167 | To setup a place to develop a Foxx app, simply create a directory hierarchy like the following anywhere in you file system: 168 | 169 | mkdir apps # this is now our 170 | cd apps 171 | mkdir -p databases/_system 172 | 173 | 174 | and either put the `guesser` repository in the `_system` folder or create a symbolic link by 175 | 176 | ln -s /databases/_system/guesser 177 | 178 | 179 | Finally, edit the ArangoDB configuration file `arangod.conf` (which usually resides in `/etc/arangodb/arangod.conf`) and add a line 180 | 181 | dev-app-path = 182 | 183 | 184 | in the `javascript` section. Restart ArangoDB after this change, on Linux, for example, you do 185 | 186 | sudo service arangodb restart 187 | 188 | for this. Use 189 | 190 | git checkout step4 191 | 192 | 193 | to see the state of our project after this step, note that nothing in the project has changed for this step. 194 | 195 | ***Note*: You can also start arangodb with a custom dev-app-path by invoking `arangod --javascript.dev-app-path=`. 196 | 197 | 198 | 199 | ### 5\. Create a minimal Foxx app for the database services 200 | 201 | We create the files 202 | 203 | manifest.json 204 | guesser.js 205 | 206 | Use 207 | 208 | git checkout step5 209 | 210 | 211 | to see the state of our project after this step. The file `manifest.json` is the starting point for any Foxx app. It contains version, author, license and repository information, and tells ArangoDB what other files are relevant. Here, we install a "controller" in the form of the file `guesser.js`. It is responsible to define new HTTP routes and contains the JavaScript code to be executed for them. In this step, we define a single new route `/hello` and install an HTTP `GET` method for it that simply serves a constant JSON document. 212 | 213 | This is achieved by the following code in `guesser.js`: 214 | 215 | ```javascript 216 | (function () { 217 | "use strict"; 218 | var Foxx = require("org/arangodb/foxx"), 219 | log = require("console").log, 220 | controller = new Foxx.Controller(applicationContext); 221 | // Example route: 222 | controller.get('/hello', function (req, res) { 223 | res.json({"Hello": "world"}); 224 | }); 225 | }()); 226 | ``` 227 | 228 | This initializes the controller and installs a single route for an `HTTP GET` request. You can test this route by pointing your browser to 229 | 230 | 231 | 232 | You should see a single JSON document like this: 233 | 234 | ```json 235 | { "Hello": "world" } 236 | ``` 237 | 238 | If this does not work right away, you might want to try to restart the database server using 239 | 240 | sudo service arangodb restart 241 | 242 | as before, since then ArangoDB will discover your Foxx app for the first time. Also make sure in your config is correct. A symbolic link databases/_system/guesser pointing to your guesser project directory containing the manifest.json should be available there. 243 | 244 | Note that so far ArangoDB serves your Foxx app in the development mode, which means that for each request all necessary files are read in from scratch and you do not have to restart every time. We will later see how to switch this off for production use. 245 | 246 | 247 | 248 | ### 6\. Organize the setup and tear down: create a collection with initial data 249 | 250 | In this step we create the files 251 | 252 | scripts/setup.js 253 | scripts/teardown.js 254 | 255 | 256 | to create a collection in the database and fill it with initial data, when the Foxx app is installed. Use 257 | 258 | git checkout step6 259 | 260 | 261 | to see the state of our project after this step. We install setup and teardown scripts in the manifest file, these are executed when the Foxx app is installed and deinstalled respectively. We create a single collection, note that we are using a mechanism in the Foxx application context to derive a name for the collection that is specific for this app by doing 262 | 263 | var collname = applicationContext.collectionName("questions"); 264 | 265 | 266 | Obviously, because Foxx controllers run in the database server, we have direct, high-performance access to the data. In the setup script we also create the first three documents in the `questions` collection. They make a small binary tree with one internal node and two leaves. 267 | 268 | To trigger the execution of the `setup.js` script, just reload the above page 269 | 270 | 271 | 272 | After that, point your browser to 273 | 274 | 275 | 276 | and choose the tab `Collections` to inspect the contents of the newly created collection with the name `dev_guesser_questions`. In the root node, you see a question and in the two leaves you see two guesses. 277 | 278 | 279 | 280 | ### 7\. Create the question asking view 281 | 282 | It is now time to add the main view to our io.js app, namely the one that asks a question. To this end, we edit the files 283 | 284 | iojs/static/index.html 285 | iojs/static/guesser_controller.js 286 | iojs/guesser_server.js 287 | 288 | 289 | Use 290 | 291 | git checkout step7 292 | 293 | 294 | to see the state of our project after this step. 295 | 296 | In the first we simply add a new `div` tag with a different condition using the `ng-switch-when` attribute (see the AngularJS documentation for an explanation). The scope variable "view" will serve as a simple router variable here. Furthermore, we add a few click actions, which are implemented in the file `guesser_controller.js`. The `restart()` function there does an AJAX call to our io.js server getting the root document of the tree via the route `/get/root`. The `update()` function updates the variables in `$scope` to configure the question asking view. Finally, the `answer(newkey)` function performs a step in the search tree by asking for another document to be fetched, again with an AJAX call. This is all standard AngularJS with AJAX calls, so we do not show the code here. 297 | 298 | ***Note*: In the AngularJS application we are creating our own, simple routing. AngularJS provides a built-in routing module and there are also third party routers available. We are using a simple self made routing system to keep things simple as we won't need many routes. 299 | 300 | The service in the io.js server is implemented in the file `guesser_server.js`, which is executed by io.js. We simply have to add a callback under the route `/get/:key`, where the `:key` is a placeholder for any string. In this callback we use the standard ArangoDB API and the `JavaScript/io.js/node.js` driver to fetch a certain document. The result is returned as HTTP response. 301 | 302 | At this stage the game is nearly working, it asks questions. However, the view for the guesses is not yet done, so we will create his in the next step. To try out what we have so far, start the io.js server with 303 | 304 | cd iojs 305 | iojs guesser_server.js 306 | 307 | 308 | and then point your browser to 309 | 310 | [http://localhost:8000/][15] 311 | 312 | 313 | 314 | ### 8\. Create the guessing view 315 | 316 | This is now rather straightforward, we simply change the files 317 | 318 | iojs/static/index.html 319 | iojs/static/guesser_controller.js 320 | 321 | 322 | Use 323 | 324 | git checkout step8 325 | 326 | 327 | to see the state of our project after this step. We have another view, controlled by another value of our handcrafted `$scope.view` router variable. We implement one more controller click action, namely the `yes()` function, which simply shows a short statement with a link back to the beginning. You can try the app again as before, only the "No" button in the end does not yet work. 328 | 329 | 330 | 331 | ### 9\. Create the learning view 332 | 333 | Finally, we have to create the view with which the computer can learn. This is the most interesting one, it needs support in the following files: 334 | 335 | iojs/static/index.html 336 | iojs/static/guesser_controller.js 337 | iojs/guesser_server.js 338 | guesser.js 339 | 340 | 341 | which covers the whole supply chain of our app. Use 342 | 343 | git checkout step9 344 | 345 | 346 | to see the state of our project after this step. 347 | 348 | As usual, we add a new view in the file `index.html`, and it is backed by two click actions in `guesser_controller.js` in form of the `no()` and `submit()` functions. The former simply switches the view to the learning view, filling in some data in the browser variables. The user can then enter what s/he had thought of, a question to distinguish it from the last guess and the two possible answers. After clicking on `Submit` the `submit()` function is called. After some checks, it essentially puts together a single JSON as input for an HTTP `PUT` AJAX request that changes the guessing tree as described above. This AJAX call is implemented in the io.js server in `guesser_server.js`. 349 | 350 | We could have implemented this call using the standard database API and the ArangoDB driver. However, we wanted to illustrate the concept of additional user defined services implemented in the database server, which is the whole point of the Foxx framework. Therefore, the io.js server only implements a very short trampoline function and simply forwards the `PUT` request to the Foxx app. Here is the code: 351 | 352 | 353 | ```javascript 354 | // This is just a trampoline to the Foxx app: 355 | var ep = db.endpoint(); 356 | app.put("/put", function (req, res) { 357 | req.pipe(concat( function(body) { 358 | // check out body-parser for an express middleware which 359 | // handles json automatically 360 | ep.put("/dev/guesser/put", JSON.parse(body.toString()), 361 | function(err, x) { 362 | if (err) { 363 | err.error = true; 364 | delete err.response; 365 | res.send(err); 366 | } 367 | else { 368 | res.send(x.body); 369 | } 370 | }); 371 | })); 372 | }); 373 | ``` 374 | 375 | The actual implementation of this `PUT` request is then in `guesser.js` in the Foxx app, which is a bit longer. 376 | 377 | The callback function for the `/put` route simply executes a transaction on the database, changing the tree in one go. This is crucial to ensure that the data structure is never corrupted. We use a transaction, because this guarantees atomic and isolated operation for all manipulations. Furthermore, we check that the current revision of the leaf node is still the same as when we fetched it from the database. This ensures that the change can only go through if nobody else changed the tree in this place in the meantime. Here is the code from `guesser/guesser.js`: 378 | 379 | 380 | ```javascript 381 | controller.put('/put', function (req, res) { 382 | log("put called"); 383 | var db = require("internal").db; 384 | var b = req.body(); 385 | try { 386 | db._executeTransaction( { 387 | collections: { 388 | write: [collName] 389 | }, 390 | action: function () { 391 | var oldLeaf = coll.document(b.oldLeaf); 392 | if (oldLeaf._rev !== b.oldLeafRev) { 393 | log("Leaf was already changed!"); 394 | throw {"error":true, "errorMessage": "Leaf was already changed"}; 395 | } 396 | var oldParent = coll.document(oldLeaf.parent); 397 | b.newQuestion.parent = oldLeaf.parent; 398 | var newQuestion = coll.insert(b.newQuestion); 399 | b.newLeaf.parent = newQuestion._key; 400 | var newLeaf = coll.insert(b.newLeaf); 401 | coll.update(newQuestion._key, { goto2: newLeaf._key }); 402 | coll.update(oldLeaf._key, {parent: newQuestion._key}); 403 | if (oldParent.goto1 === b.oldLeaf) { 404 | coll.update(oldParent._key, { goto1: newQuestion._key }); 405 | } 406 | else if (oldParent.goto2 === b.oldLeaf) { 407 | coll.update(oldParent._key, { goto2: newQuestion._key }); 408 | } 409 | else { 410 | throw "Murks"; 411 | } 412 | }, 413 | }); 414 | } 415 | catch (e) { 416 | res.json(e); 417 | return; 418 | } 419 | res.json({"error":false}); 420 | }); 421 | ``` 422 | 423 | The game is now fully functional and playing it will actually increase the tree and thus the knowledge of the game. 424 | 425 | For illustration purposes, we have additionally implemented a `GET` method in the Foxx app, which is currently unused. One could exchange the `/get/:key` callback in `guesser_server.js` by a trampoline similar to the one for the `/put` callback. Then one could add more schema validation and enforcement in the Foxx app and completely switch off the standard API, thereby increasing security and correctness. 426 | 427 | 428 | 429 | ### 10\. Deploy the Foxx app in ArangoDB 430 | 431 | Now that the application works, it is time to switch off the development mode and deploy the Foxx app in ArangoDB for production. This is fortunately very easy, and there are several methods available. First of all, remove the entry 432 | 433 | dev-app-path = 434 | 435 | in `arangod.conf` again and restart the ArangoDB server: 436 | 437 | sudo service arangodb restart 438 | 439 | We first describe the open source method using a public `github` 440 | repository. Since the whole Foxx app resides in a `github` repository, you can simply point your browser to 441 | 442 | 443 | 444 | choose the "Applications" tab and click on the big `Add Application` button. Use the `github` tab and enter `ArangoDB/guesser` under "Repository", leave `master` under "Version". After you click "Install" you are prompted for a mount point, choose `/guesser` and click "Configure". After that, the "guesser" app should appear under "Applications". You can test whether or not this worked by pointing your browser to 445 | 446 | 447 | 448 | which should give you the initial question as a JSON document. 449 | 450 | If you do not keep your Foxx app in a public github repository then you can also deploy it using a local zip file. Create a zip archive of the guesser repository that contains the folder `guesser` on the top level and upload it using the zip tab behind the `Add Application` button described above. 451 | 452 | Regardless of which deployment method you used, you now have to adjust the file 453 | 454 | iojs/guesser_server.js 455 | 456 | in two places: one is the name of the collection, which is now `guesser_questions` rather than `dev_guesser_questions`. The second place is the route of the Foxx `PUT` request which is now `/guesser/put` rather than `/dev/guesser/put`. As a best practive we can leverage the `env` variable of express which defaults to `development` and can be controlled by invoking io.js like this: ``NODE_ENV=production iojs guesser_server.js`` 457 | 458 | Use 459 | 460 | git checkout step10 461 | 462 | 463 | to see the state of our project after this step. 464 | 465 | 466 | 467 | ## Deployment in production 468 | 469 | All that is needed to deploy our app for production is the following: 470 | 471 | 1. Install io.js, npm and ArangoDB as described above in Steps 2 and 4 (leave out the part about the ArangoDB development mode) 472 | 2. Deploy the Foxx app as described in Step 10. 473 | 3. Install frontend and backend dependencies as described in Step 2. 474 | 4. Deploy the io.js application together with the static web content. 475 | 476 | To simplify the fourth step we have already provided the necessary 477 | entries in `package.json` to publish the whole io.js app as an `npm` 478 | module. Since we have done `npm publish` you can now simply do `npm 479 | install guesser` and get the production ready app with all dependencies. 480 | To get an idea what is necessary to achieve this, use 481 | 482 | git checkout step11 483 | 484 | We have also set up an infrastructure to deploy the whole app as [Docker 485 | containers][16]. We do not describe this here, but you can see the 486 | necessary files by doing 487 | 488 | git checkout step12 489 | 490 | and read about the details in [this recipe in the ArangoDB Cookbook][17]. 491 | 492 | ## Architectural considerations 493 | 494 | We would like to point out that although this is a nearly minimal application without sophisticated error handling, security measures and web prettiness, it nevertheless has all architectural features of a proper web application. There is a clear separation of the persistence layer and the application server, the web client in the browser only talks to the io.js server and not directly to the database. All interfaces are well-defined and use HTTP. 495 | 496 | Therefore, it is in principle possible to exchange any of the three parts and replace it with another technology. In an upcoming post I will for example explain how to replace the io.js middleware by an approach using a classical web server and PHP. Obviously, the front end code works in different browsers and it would be relatively simple to write an app for a mobile device replacing the browser part. 497 | 498 | This separation helps also with the scalability, because one can scale the database layer independently from the application server layer. If many requests are coming in, one can easily deploy multiple io.js servers and put a load balancer in front of them. Since they are stateless, this allows for extremely easy and fast scalability. If the requests to the database are the bottleneck, one can start to use sharding and scale the persistence layer up as needed. 499 | 500 | From a security perspective, the whole setup is quite satisfactory, since all accesses to the database are via two well-defined routes, which would actually allow to switch off the standard API of the database for increased security. This way, the amount of code one would have to scrutinize is very small. 501 | 502 | However we made a few shortcuts in the application structure: You probably don't want to put the bower.json directly into static directory (needs a .bowerrc to work) or you would implement a build process which removes the file in production. 503 | 504 | ## Authors 505 | 506 | This tutorial and the corresponding code base was written by Max 507 | Neunhöffer [neunhoef](https://github.com/neunhoef) and 508 | Andreas Streichardt [m0ppers](https://github.com/m0ppers). 509 | 510 | ## License 511 | 512 | This code is distributed under the 513 | [Apache License](http://www.apache.org/licenses/LICENSE-2.0). 514 | 515 | [1]: #One 516 | [2]: #Two 517 | [3]: #Three 518 | [4]: #Four 519 | [5]: #Five 520 | [6]: #Six 521 | [7]: #Seven 522 | [8]: #Eight 523 | [9]: #Nine 524 | [10]: #Ten 525 | [11]: https://docs.npmjs.com/ 526 | [12]: http://bower.io/ 527 | [13]: https://docs.angularjs.org/api 528 | [14]: http://expressjs.com/4x/api.html 529 | [15]: http://localhost:8000 530 | [16]: https://www.docker.com/ 531 | [17]: https://docs.arangodb.com/cookbook/UsingArangoDBNodeJSDocker.html 532 | -------------------------------------------------------------------------------- /docker/arangodb/Dockerfile: -------------------------------------------------------------------------------- 1 | # use base node.js 2 | FROM arangodb/arangodb 3 | 4 | # maintainer info 5 | MAINTAINER Frank Celler 6 | 7 | # copy the startup script 8 | ADD ./scripts /scripts 9 | 10 | # copy the install files 11 | ADD ./guesser-foxx.zip /install/guesser-foxx.zip 12 | 13 | # expose data, apps and logs 14 | VOLUME ["/data", "/apps", "/apps-dev", "/logs"] 15 | 16 | # database is running on port 8529 17 | EXPOSE 8529 18 | 19 | # start the node server 20 | CMD [ "/scripts/start.sh" ] 21 | -------------------------------------------------------------------------------- /docker/arangodb/Makefile: -------------------------------------------------------------------------------- 1 | setup: 2 | @echo "Creating FOXX application file" 3 | rm -f guesser-foxx.zip 4 | cd ../.. && zip docker/arangodb/guesser-foxx.zip guesser.js manifest.json scripts/setup.js scripts/teardown.js README.md LICENSE 5 | -------------------------------------------------------------------------------- /docker/arangodb/README.md: -------------------------------------------------------------------------------- 1 | # Guesser Game 2 | 3 | This docker container is an example on how to set-up an application 4 | with a node.js and an ArangoDB component. It sets up the database 5 | container for the guesser game. Please check out 6 | 7 | TBA 8 | 9 | for details. 10 | -------------------------------------------------------------------------------- /docker/arangodb/guesser-foxx.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arangodb/guesser/3b0b69367ac71c8fb12c9b8363182e499b965d6a/docker/arangodb/guesser-foxx.zip -------------------------------------------------------------------------------- /docker/arangodb/scripts/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # fireup the database 5 | echo "--> starting ArangoDB" 6 | /usr/sbin/arangod \ 7 | --uid arangodb \ 8 | --gid arangodb \ 9 | --database.directory /data \ 10 | --javascript.app-path /apps \ 11 | --log.file /logs/arangodb.log \ 12 | --temp-path /tmp/arangodb \ 13 | $D1 $D2 \ 14 | "$@" & 15 | 16 | # wait a while until it is available 17 | echo "--> waiting for ArangoDB to become ready" 18 | sleep 30 19 | 20 | # and install the foxx 21 | if foxx-manager list | fgrep guesser; then 22 | echo "--> guesser game is already installed" 23 | else 24 | echo "--> installing guesser game" 25 | 26 | foxx-manager fetch zip /install/guesser-foxx.zip 27 | foxx-manager mount guesser /guesser 28 | foxx-manager setup /guesser 29 | fi 30 | 31 | echo "--> ready for business" 32 | wait 33 | -------------------------------------------------------------------------------- /docker/node/Dockerfile: -------------------------------------------------------------------------------- 1 | # use base node.js 2 | FROM node:argon 3 | 4 | # maintainer info 5 | MAINTAINER Frank Celler 6 | 7 | # copy the startup script 8 | ADD ./scripts /scripts 9 | 10 | # copy the install files 11 | ADD ./guesser-foxx.zip /install/guesser-foxx.zip 12 | ADD ./guesser-node.tar /install/guesser-node 13 | 14 | # install ubuntu package for arangosh 15 | RUN /scripts/install.sh 16 | 17 | # application is running on port 8000 18 | EXPOSE 8000 19 | 20 | # start the node server 21 | CMD [ "/scripts/start.sh" ] 22 | -------------------------------------------------------------------------------- /docker/node/Makefile: -------------------------------------------------------------------------------- 1 | setup: 2 | @echo "Creating FOXX application file" 3 | rm -f guesser-foxx.zip 4 | cd ../.. && zip docker/node/guesser-foxx.zip guesser.js manifest.json scripts/setup.js scripts/teardown.js README.md LICENSE 5 | 6 | @echo "Creating NODE application file" 7 | rm -f guesser-node.tar 8 | cd ../../iojs && tar cvf ../docker/node/guesser-node.tar guesser_server.js package.json README.md static 9 | -------------------------------------------------------------------------------- /docker/node/README.md: -------------------------------------------------------------------------------- 1 | # Guesser Game 2 | 3 | This docker container is an example on how to set-up an application 4 | with a node.js and an ArangoDB component. Please check out 5 | 6 | TBA 7 | 8 | for details. 9 | -------------------------------------------------------------------------------- /docker/node/guesser-foxx.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arangodb/guesser/3b0b69367ac71c8fb12c9b8363182e499b965d6a/docker/node/guesser-foxx.zip -------------------------------------------------------------------------------- /docker/node/guesser-node.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arangodb/guesser/3b0b69367ac71c8fb12c9b8363182e499b965d6a/docker/node/guesser-node.tar -------------------------------------------------------------------------------- /docker/node/scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # add arangodb source 5 | ARANGO_URL=https://www.arangodb.com/repositories/arangodb2/xUbuntu_14.04 6 | 7 | # non interactive 8 | echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections 9 | 10 | # update system 11 | echo " ---> Updating the system" 12 | apt-get -y -qq --force-yes update 13 | apt-get -y -qq install wget 14 | apt-get -y -qq install dnsutils 15 | apt-get -y -qq install apt-transport-https 16 | 17 | # install arangodb key 18 | echo " ---> Adding ArangoDB repository" 19 | echo "deb $ARANGO_URL/ /" >> /etc/apt/sources.list.d/arangodb.list 20 | wget --quiet $ARANGO_URL/Release.key 21 | apt-key add - < Release.key 22 | rm Release.key 23 | apt-get -y -qq --force-yes update 24 | 25 | # use NPM to install the guesser game 26 | echo " ---> Installing the guesser game" 27 | mkdir -p /data/node_modules 28 | cp -a /install/guesser-node /data/node_modules/guesser 29 | (cd /tmp && npm install -g bower) 30 | (cd /data/node_modules/guesser && npm install --unsafe-perm) 31 | 32 | # install arangodb 33 | echo " ---> Installing arangodb-client package" 34 | cd /tmp 35 | apt-get -y -qq --force-yes install arangodb-client 36 | 37 | # cleanup 38 | echo " ---> Cleaning up" 39 | apt-get -y -qq --force-yes clean 40 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 41 | -------------------------------------------------------------------------------- /docker/node/scripts/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if test "$nolink" = 1; then 5 | echo "Starting without a database link" 6 | export ARANGODB_SERVER=none 7 | else 8 | if [ -n "$GUESSER_BACK_END_PORT_8529_TCP_ADDR" ]; then 9 | ARANGODB_SERVER="http://${GUESSER_BACK_END_PORT_8529_TCP_ADDR}:8529" 10 | ARANGODB_ENDPOINT="$GUESSER_BACK_END_PORT_8529_TCP" 11 | elif [ -n "$DB_LINK_PORT_8529_TCP_ADDR" ]; then 12 | ARANGODB_SERVER=http://${DB_LINK_PORT_8529_TCP_ADDR}:8529 13 | ARANGODB_ENDPOINT="$DB_LINK_PORT_8529_TCP" 14 | elif [ -n "$MARATHON_APP_ID" ]; then 15 | ARANGODB_SERVER=http://${HOST}:32222 16 | ARANGODB_ENDPOINT=tcp://${HOST}:32222 17 | elif [ -z "$ARANGODB_SERVER" ] || [ -z "$ARANGODB_ENDPOINT" ]; then 18 | # sanity check 19 | export ARANGODB_SERVER=http://localhost:8529 20 | export ARANGODB_ENDPOINT=tcp://localhost:8529 21 | 22 | echo "warning: DB_LINK_PORT_8529_TCP_ADDR env variable is not set, please link the ArangoDB with '--link instancename:db-link'" 23 | env | sort 24 | exit 1 25 | fi 26 | 27 | wget ${ARANGODB_SERVER}/_api/version -q -O - 28 | while test "$?" -ne 0; do 29 | echo 30 | echo "waiting for database to become ready at $ARANGODB_SERVER" 31 | sleep 10 32 | wget ${ARANGODB_SERVER}/_api/version -q -O - 33 | done 34 | echo 35 | 36 | if test "$init" = 1; then 37 | echo "Going to initialize the database at $ARANGODB_ENDPOINT" 38 | 39 | foxx-manager --server.endpoint $ARANGODB_ENDPOINT fetch zip /install/guesser-foxx.zip 40 | foxx-manager --server.endpoint $ARANGODB_ENDPOINT mount guesser /guesser 41 | foxx-manager --server.endpoint $ARANGODB_ENDPOINT setup /guesser 42 | fi 43 | fi 44 | 45 | # switch into the guesser directory 46 | cd /data/node_modules/guesser 47 | 48 | # and start node 49 | NODE_ENV=production node -e 'require("guesser")' 50 | -------------------------------------------------------------------------------- /guesser.js: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | /// A learning guessing game 3 | /// by Max Neunhöffer 4 | /// Copyright 2014, ArangoDB GmbH, Cologne, Germany 5 | //////////////////////////////////////////////////////////////////////////////// 6 | 7 | 8 | 'use strict'; 9 | const createRouter = require('org/arangodb/foxx/router'); 10 | const router = createRouter(); 11 | 12 | module.context.use(router); 13 | const log = require("console").log; 14 | 15 | const applicationContext = module.context; 16 | 17 | var collName = applicationContext.collectionName("questions"); 18 | var coll = applicationContext.collection("questions"); 19 | 20 | // Get an entry: 21 | router.get('/get/:key', function (req, res) { 22 | log("get/"+req.pathParams["key"]+" called"); 23 | var d; 24 | try { 25 | d = coll.document(req.pathParams["key"]); 26 | res.json(d); 27 | } 28 | catch (e) { 29 | res.json(e); 30 | } 31 | }); 32 | // Post a new question and thingy: 33 | router.put('/put', function (req, res) { 34 | log("put called"); 35 | var db = require("internal").db; 36 | var b = req.body; 37 | try { 38 | db._executeTransaction( { 39 | collections: { 40 | write: [collName] 41 | }, 42 | action: function () { 43 | var oldLeaf = coll.document(b.oldLeaf); 44 | if (oldLeaf._rev !== b.oldLeafRev) { 45 | log("Leaf was already changed!"); 46 | throw {"error":true, "errorMessage": "Leaf was already changed"}; 47 | } 48 | var oldParent = coll.document(oldLeaf.parent); 49 | b.newQuestion.parent = oldLeaf.parent; 50 | var newQuestion = coll.insert(b.newQuestion); 51 | b.newLeaf.parent = newQuestion._key; 52 | var newLeaf = coll.insert(b.newLeaf); 53 | coll.update(newQuestion._key, { goto2: newLeaf._key }); 54 | coll.update(oldLeaf._key, {parent: newQuestion._key}); 55 | if (oldParent.goto1 === b.oldLeaf) { 56 | coll.update(oldParent._key, { goto1: newQuestion._key }); 57 | } 58 | else if (oldParent.goto2 === b.oldLeaf) { 59 | coll.update(oldParent._key, { goto2: newQuestion._key }); 60 | } 61 | else { 62 | throw "Murks"; 63 | } 64 | }, 65 | }); 66 | } 67 | catch (e) { 68 | res.json(e); 69 | return; 70 | } 71 | res.json({"error":false}); 72 | }) 73 | .body(['json']); 74 | 75 | -------------------------------------------------------------------------------- /iojs/README.md: -------------------------------------------------------------------------------- 1 | guesser - a guessing game that learns 2 | ===================================== 3 | 4 | This is the io.js server part of the app. The whole code including 5 | the ArangoDB Foxx app can be found at 6 | [github-repository](https://github.com/ArangoDB/guesser). 7 | 8 | Installation 9 | ------------ 10 | 11 | Install [ArangoDB](http://www.arangodb.com) and in there the Foxx app 12 | with the above github URL. Then do 13 | 14 | npm install guesser 15 | npm start guesser 16 | 17 | preferably in a screen session and visit port 8000 on the machine you 18 | are running everything on. 19 | -------------------------------------------------------------------------------- /iojs/guesser_server.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env nodejs 2 | 3 | //////////////////////////////////////////////////////////////////////////////// 4 | /// Libraries and database driver 5 | //////////////////////////////////////////////////////////////////////////////// 6 | 7 | var fs = require("fs"); 8 | var Promise = require("promise"); 9 | var concat = require("concat-stream"); 10 | 11 | var server_addr = process.env.ARANGODB_SERVER ? process.env.ARANGODB_SERVER : "http://localhost:8529"; 12 | var ignore = console.log("Using DB-Server " + server_addr); 13 | 14 | var Database = require("arangojs"); 15 | 16 | if (server_addr !== "none") { 17 | var db = new Database(server_addr); // configure server 18 | } 19 | 20 | //////////////////////////////////////////////////////////////////////////////// 21 | /// An express app: 22 | //////////////////////////////////////////////////////////////////////////////// 23 | 24 | var express = require('express'); 25 | var app = express(); 26 | 27 | // leverage NODE_ENV to determine collectionName 28 | var collectionName = "guesser_questions"; // configure collection 29 | var putRoute = "guesser"; 30 | if (app.get('env') == "development") { 31 | putRoute = "dev/" + putRoute; 32 | collectionName = "dev_" + collectionName; 33 | } 34 | 35 | var collectionPromise = new Promise(function(resolve, reject) { 36 | db.collection(collectionName, false, function(err, res) { 37 | if (err) { 38 | reject(err); 39 | } 40 | else { 41 | resolve(res); 42 | } 43 | }); 44 | }); 45 | 46 | if (server_addr !== "none") { 47 | collectionPromise.then(null, function(err) { 48 | console.log("Cannot contact the database! Terminating..."); 49 | process.exit(1); 50 | }); 51 | } 52 | 53 | //////////////////////////////////////////////////////////////////////////////// 54 | /// Static content: 55 | //////////////////////////////////////////////////////////////////////////////// 56 | 57 | app.use(express.static(__dirname + "/static")); 58 | 59 | //////////////////////////////////////////////////////////////////////////////// 60 | /// AJAX services: 61 | //////////////////////////////////////////////////////////////////////////////// 62 | 63 | app.get("/get/:key", function (req, res) { 64 | var key = req.params["key"]; 65 | collectionPromise.then(function(collection) { 66 | collection.document(key, function(err, x) { 67 | if (err) { 68 | // for production we should implement more sophisticated handling here. Like logging where appropriate etc. 69 | res.status(err.code); 70 | delete err.response 71 | res.json(err); 72 | } 73 | else { 74 | res.json(x); 75 | } 76 | }); 77 | }, null); // if this were rejected, we would be out already 78 | }); 79 | 80 | // This is just a trampoline to the Foxx app: 81 | var ep = (server_addr !== "none") ? db.route(putRoute) : undefined; 82 | 83 | app.put("/put", function (req, res) { 84 | req.pipe(concat( function(body) { 85 | // check out body-parser for a express middleware which handles json automatically 86 | ep.put("put", JSON.parse(body.toString()), 87 | function(err, x) { 88 | if (err) { 89 | err.error = true; 90 | delete err.response; 91 | res.send(err); 92 | } 93 | else { 94 | res.send(x.body); 95 | } 96 | }); 97 | } )); 98 | }); 99 | 100 | //////////////////////////////////////////////////////////////////////////////// 101 | /// Now finally make the server: 102 | //////////////////////////////////////////////////////////////////////////////// 103 | 104 | var server = app.listen(8000, function () { 105 | var host = server.address().address 106 | var port = server.address().port 107 | console.log('Guesser app server listening at http://%s:%s', host, port) 108 | }); 109 | -------------------------------------------------------------------------------- /iojs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "guesser", 3 | "version": "1.5.3", 4 | "description": "a guessing game that learns", 5 | "main": "guesser_server.js", 6 | "dependencies": { 7 | "arangojs": "^3.9.1", 8 | "bower": "^1.4.1", 9 | "concat-stream": "^1.5.0", 10 | "express": "^4.13.3", 11 | "promise": "^7.0.4" 12 | }, 13 | "scripts": { 14 | "prepublish": "cd static && bower install --config.interactive=false -s --allow-root" 15 | }, 16 | "devDependencies": { 17 | "bower": "*" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git://github.com/ArangoDB/guesser" 22 | }, 23 | "keywords": [ 24 | "guessing", 25 | "game", 26 | "machine", 27 | "learning", 28 | "binary", 29 | "tree", 30 | "ArangoDB" 31 | ], 32 | "author": { 33 | "name": "Max Neunhoeffer", 34 | "email": "max@arangodb.com", 35 | "url": "http://www-groups.mcs.st-and.ac.uk/~neunhoef" 36 | }, 37 | "readme": "README.md", 38 | "license": "Apache 2.0", 39 | "bugs": { 40 | "url": "https://github.com/ArangoDB/guesser/issues" 41 | }, 42 | "files": [ 43 | "guesser_server.js", 44 | "static", 45 | "package.json" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /iojs/static/base.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | 5 | body { 6 | background: #fbfbfb; 7 | color: #333; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-font-smoothing: antialiased; 10 | -ms-font-smoothing: antialiased; 11 | -o-font-smoothing: antialiased; 12 | font-smoothing: antialiased; 13 | font-family: Helvetica, sans-serif; 14 | font-size: 100%; 15 | /* padding: 0.25em; 16 | */ height: 100%; 17 | } 18 | 19 | .content { 20 | position: relative; 21 | min-height: 100%; 22 | width: 95%; 23 | margin: 0px auto; 24 | } 25 | 26 | .guesserContent { 27 | padding-bottom: 132px; 28 | } 29 | 30 | h1 { 31 | margin: 0px; 32 | text-align: center; 33 | padding: 1em 0 2em 0; 34 | font-size: 2.5em; 35 | line-height: 1.35em; 36 | } 37 | 38 | h2 { 39 | margin: 1em 0px 1.25em 0px; 40 | font-size: 1.5em; 41 | line-height: 1.35em; 42 | } 43 | 44 | input { 45 | font-family: Helvetica, sans-serif; 46 | font-size: 0.8em; 47 | line-height: 0.9em; 48 | padding: 2px; 49 | } 50 | 51 | button { 52 | font-family: Helvetica, sans-serif; 53 | font-size: 1.0em; 54 | line-height: 1.35em; 55 | padding: 4px; 56 | margin: 1px; 57 | } 58 | 59 | p { 60 | font-size: 1em; 61 | line-height: 1.5em; 62 | margin-top: 0.5em; 63 | margin-bottom: 0.5em; 64 | } 65 | 66 | .newQuestion { 67 | width: 20%; 68 | } 69 | 70 | footer { 71 | position: absolute; 72 | bottom: 0; 73 | font-size: 0.8em; 74 | text-align: center; 75 | width: 100%; 76 | height: 132px; 77 | } 78 | 79 | .hidden{ 80 | display: none; 81 | } 82 | 83 | 84 | @media (min-width: 800px) { 85 | .content { 86 | width: 75%; 87 | } 88 | } -------------------------------------------------------------------------------- /iojs/static/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "static", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "ignore": [ 6 | "**/.*", 7 | "node_modules", 8 | "bower_components", 9 | "test", 10 | "tests" 11 | ], 12 | "dependencies": { 13 | "angularjs": "~1.3.9" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /iojs/static/guesser_controller.js: -------------------------------------------------------------------------------- 1 | var app = angular.module("guesser", []); 2 | 3 | app.controller("guesserController", function ($scope, $http) { 4 | $scope.container = { "name": "" }; 5 | 6 | $scope.restart = function () { 7 | $http.get("get/root") 8 | .success(function(response) { 9 | $scope.current = response; 10 | }) 11 | .error(function(response) { 12 | $scope.current = {}; 13 | alert("AJAX call failed"); 14 | }); 15 | $scope.view = "welcome"; 16 | } 17 | 18 | $scope.restart(); 19 | 20 | $scope.update = function () { 21 | if (! $scope.current.isLeaf) { 22 | $scope.theQuestion = $scope.current.question; 23 | $scope.theAnswer1 = $scope.current.answer1; 24 | $scope.theAnswer2 = $scope.current.answer2; 25 | $scope.view = "question"; 26 | } 27 | else { 28 | $scope.theGuess = $scope.current.guess; 29 | $scope.guessedRight = false; 30 | $scope.view = "guess"; 31 | } 32 | } 33 | $scope.startGame = $scope.update; 34 | 35 | $scope.answer = function (newkey) { 36 | $http.get("get/"+newkey) 37 | .success(function(response) { 38 | $scope.current = response; 39 | $scope.update(); 40 | }) 41 | .error(function(response) { 42 | alert("AJAX call failed"); 43 | }); 44 | } 45 | 46 | $scope.yes = function () { 47 | $scope.guessedRight = true; 48 | } 49 | 50 | $scope.no = function () { 51 | $scope.oldThing = $scope.current.guess; 52 | $scope.submitted = false; 53 | $scope.view = "learning"; 54 | } 55 | 56 | $scope.submit = function (userThing, userQuestion, answerOld, answerNew) { 57 | if (answerOld === answerNew) { 58 | alert("Old and new answer must be different"); 59 | return; 60 | } 61 | if (userQuestion[userQuestion.length-1] !== "?") { 62 | userQuestion += "?"; 63 | } 64 | var a = { oldLeaf: $scope.current._key, 65 | oldLeafRev: $scope.current._rev, 66 | newQuestion: { 67 | question: userQuestion, 68 | answer1: answerOld, 69 | answer2: answerNew, 70 | goto1: $scope.current._key, 71 | isLeaf: false 72 | }, 73 | newLeaf: { 74 | isLeaf: true, 75 | guess: userThing 76 | } 77 | }; 78 | $http.put("put", a) 79 | .success(function(response) { 80 | if (response.error === true) { 81 | alert("Could not submit new question! "+ 82 | "This leaf was already modified!"); 83 | $scope.restart(); 84 | } 85 | else { 86 | $scope.submitted = true; 87 | } 88 | }) 89 | .error(function(response) { 90 | alert("AJAX call failed, cannot update"); 91 | }); 92 | } 93 | } ); 94 | 95 | -------------------------------------------------------------------------------- /iojs/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | guesser - a guessing game that learns 7 | 8 | 9 | 10 | 11 |
12 |
13 |

guesser - a guessing game that learns

14 | 15 |
16 | 17 | 18 |
19 |

Welcome, think of a thing or an animal, I will try to guess it!

20 | 21 |
22 |

23 | But first, enter your name: 24 | , 25 | and click 26 | 27 | when you are ready. 28 |

29 |
30 |
31 | 32 | 33 |
34 |

{{container.name}}, please answer the following question:

35 | 36 |

{{theQuestion}}

37 |

38 | 39 | 40 |

41 |
42 | 43 | 44 |
45 |

{{container.name}}, I think I know what you guessed:

46 | 47 |

Is it {{theGuess}}?

48 |

49 |

50 | 51 |

52 | Ha! Got you! Click 53 | to play again. 54 |

55 |
56 | 57 | 58 |
59 |

{{container.name}}, OK, you won. Now please help me to learn.

60 | 61 |

62 | What was it you thought of? 63 |

64 |
65 | 66 |

(please include an indefinite article "a" or "an")


67 |

68 | Please give me a question that distinguishes 69 | {{oldThing}} 70 | and {{userThing}}. 71 | Note that the question and the two answers 72 | have to be such that for any thing or animal 73 | one of the answers to the question is correct and the other 74 | is incorrect! 75 |

76 |

77 | Question: 78 |


79 |

80 | The answer for {{oldThing}} is: 81 |
82 | The answer for {{userThing}} is: 83 | 84 |


85 |

86 | 89 |

90 |
91 | 92 |
93 |

94 | Thank you very much, your new question has been submitted. Click 95 | 98 | to play again. 99 |

100 |
101 |
102 |
103 |
104 |
105 |

106 | This demo application is part of a 107 | tutorial on Github:
108 | "A guessing game that learns, written in JavaScript using 109 | AngularJs, 110 | io.js and 111 | ArangoDB"
112 | License:Apache 2.0
113 | Github:ArangoDB/guesser
114 | Authors: Max Neunhöffer and Andreas Streichardt
115 | Built with the NoSQL Database ArangoDB 116 |

117 |
118 |
119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "guesser", 3 | "version": "1.1.2", 4 | "description": "A learning guessing game", 5 | "author": "Max Neunhöffer", 6 | "license": "Apache 2 License", 7 | "scripts": { 8 | "setup": "scripts/setup.js", 9 | "teardown": "scripts/teardown.js" 10 | }, 11 | "main": "guesser.js", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/ArangoDB/guesser.git" 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /scripts/setup.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var console = require("console"), 3 | db = require("org/arangodb").db, 4 | collname = module.context.collectionName("questions"); 5 | 6 | if (db._collection(collname) === null) { 7 | var c = db._create(collname); 8 | var q = c.insert({ isLeaf: false, question: "Is it a thing or an animal?", 9 | answer1: "a thing", answer2: "an animal", 10 | goto1: "computer", goto2: "cat", _key: "root", 11 | parent: null}); 12 | c.insert({ isLeaf: true, guess: "a computer", 13 | parent: q._key, _key: "computer" }); 14 | c.insert({ isLeaf: true, guess: "a cat", 15 | parent: q._key, _key: "cat" }); 16 | } 17 | else if (module.context.isProduction) { 18 | console.warn("collection '%s' already exists. Leaving it untouched.", collname); 19 | } 20 | -------------------------------------------------------------------------------- /scripts/teardown.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | "use strict"; 3 | 4 | var db = require("org/arangodb").db, 5 | collname = applicationContext.collectionName("questions"), 6 | collection = db._collection(collname); 7 | 8 | if (collection !== null) { 9 | collection.drop(); 10 | } 11 | }()); 12 | --------------------------------------------------------------------------------